castrum 0.9.8 → 0.9.10
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/Cargo.lock +11 -1
- package/Cargo.toml +17 -3
- package/castrum.darwin-arm64.node +0 -0
- package/castrum.darwin-x64.node +0 -0
- package/castrum.linux-arm64-gnu.node +0 -0
- package/castrum.linux-arm64-musl.node +0 -0
- package/castrum.linux-x64-gnu.node +0 -0
- package/castrum.linux-x64-musl.node +0 -0
- package/castrum.linux-x64-v3-gnu.node +0 -0
- package/castrum.win32-x64-msvc.node +0 -0
- package/dist/index.d.ts +164 -4
- package/dist/index.js +434 -70
- package/docs/ENVIRONMENT.md +7 -0
- package/docs/RND-CONCURRENCY.md +20 -4
- package/docs/bun-builtins-decision-matrix.md +17 -7
- package/docs/superpowers/plans/2026-09-18-elysia-learnings-adoption.md +634 -0
- package/index.ts +5 -0
- package/package.json +2 -1
- package/rust/crypto/base64.rs +472 -57
- package/rust/crypto/hashing.rs +68 -0
- package/rust/ffi/task.rs +19 -18
- package/rust/ingress/api.rs +21 -5
- package/rust/ingress/mod.rs +7 -0
- package/rust/ingress/native_route.rs +4 -2
- package/rust/ingress/schema_cache.rs +266 -0
- package/rust/payload/sse.rs +78 -5
- package/rust/task/mod.rs +1 -0
- package/rust/task/runtime.rs +55 -7
- package/rust/task/tests.rs +98 -1
- package/rust/util/text.rs +112 -10
- package/rust/util/validation.rs +34 -0
- package/src/ingress/abort.ts +48 -0
- package/src/ingress/body.ts +104 -8
- package/src/ingress/handlers.ts +1 -0
- package/src/ingress/health.ts +22 -0
- package/src/ingress/index.ts +3 -1
- package/src/ingress/router.ts +36 -1
- package/src/ingress/routes/common.ts +9 -1
- package/src/ingress/routes/echo.ts +10 -0
- package/src/ingress/routes/json-write.ts +11 -0
- package/src/ingress/routes/native.ts +24 -5
- package/src/ingress/routes/options.ts +6 -2
- package/src/ingress/routes/responder.ts +25 -5
- package/src/ingress/routes/safe-record.ts +57 -0
- package/src/ingress/server-error.ts +111 -0
- package/src/ingress/server-node.ts +102 -23
- package/src/ingress/server.ts +140 -32
- package/src/integration/pipeline.ts +6 -0
- package/src/native/ffi/build/task.ts +2 -1
- package/src/native/ffi/types.ts +6 -1
- package/src/native/types.ts +6 -0
- package/src/runtime/detect.ts +14 -0
- package/src/rust-ffi/context.ts +46 -3
- package/src/rust-ffi/scalar/factories.ts +6 -0
- package/src/rust-ffi/scalar/interface.ts +14 -0
- package/src/rust-ffi/text.ts +5 -4
- package/src/shared/buffer-pool.ts +10 -0
- package/src/shared/memory.ts +68 -0
- package/src/shared/metrics.ts +86 -3
- package/src/shared/runtime.ts +7 -0
- package/src/task/runtime.ts +52 -9
package/dist/index.js
CHANGED
|
@@ -36,6 +36,13 @@ function detectRuntimeName() {
|
|
|
36
36
|
function isBunRuntime() {
|
|
37
37
|
return detectedRuntime === "bun";
|
|
38
38
|
}
|
|
39
|
+
function forceGc() {
|
|
40
|
+
if (detectedRuntime === "bun") {
|
|
41
|
+
Bun.gc(true);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
globalThis.gc?.();
|
|
45
|
+
}
|
|
39
46
|
var detectedRuntime;
|
|
40
47
|
var init_detect = __esm(() => {
|
|
41
48
|
detectedRuntime = detectRuntimeName();
|
|
@@ -638,10 +645,11 @@ function buildFFISymbolMap(useBufferLength) {
|
|
|
638
645
|
}
|
|
639
646
|
|
|
640
647
|
// src/shared/runtime.ts
|
|
641
|
-
var isBun;
|
|
648
|
+
var isBun, forceGc2;
|
|
642
649
|
var init_runtime = __esm(() => {
|
|
643
650
|
init_detect();
|
|
644
651
|
isBun = isBunRuntime;
|
|
652
|
+
forceGc2 = forceGc;
|
|
645
653
|
});
|
|
646
654
|
|
|
647
655
|
// src/shared/codec.ts
|
|
@@ -3609,6 +3617,24 @@ var init_options = __esm(() => {
|
|
|
3609
3617
|
});
|
|
3610
3618
|
|
|
3611
3619
|
// src/rust-ffi/context.ts
|
|
3620
|
+
function evictOldestHalf(map) {
|
|
3621
|
+
if (map.size < MIME_CACHE_MAX)
|
|
3622
|
+
return [];
|
|
3623
|
+
const drop = Math.ceil(map.size / 2);
|
|
3624
|
+
const dropped = [];
|
|
3625
|
+
for (const key of map.keys()) {
|
|
3626
|
+
if (dropped.length >= drop)
|
|
3627
|
+
break;
|
|
3628
|
+
dropped.push(key);
|
|
3629
|
+
}
|
|
3630
|
+
for (const key of dropped)
|
|
3631
|
+
map.delete(key);
|
|
3632
|
+
return dropped;
|
|
3633
|
+
}
|
|
3634
|
+
function clearMimeCaches() {
|
|
3635
|
+
mimeByText.clear();
|
|
3636
|
+
mimeStrCache.clear();
|
|
3637
|
+
}
|
|
3612
3638
|
function resolveNative(ctx, name) {
|
|
3613
3639
|
let f = nativeFnCache.get(name);
|
|
3614
3640
|
if (f === undefined) {
|
|
@@ -3637,7 +3663,6 @@ function memoizeFfi(transport) {
|
|
|
3637
3663
|
};
|
|
3638
3664
|
}
|
|
3639
3665
|
function createContext(options) {
|
|
3640
|
-
const mimeByText = new Map;
|
|
3641
3666
|
const hmacSigners = new WeakMap;
|
|
3642
3667
|
const state = {
|
|
3643
3668
|
mimeCache: options.mimeCache !== false,
|
|
@@ -3667,6 +3692,7 @@ function createContext(options) {
|
|
|
3667
3692
|
const key = normalizeExt(ext);
|
|
3668
3693
|
let val = mimeByText.get(key);
|
|
3669
3694
|
if (!val) {
|
|
3695
|
+
evictOldestHalf(mimeByText);
|
|
3670
3696
|
val = addon.mimeFromExtension(ext);
|
|
3671
3697
|
mimeByText.set(key, val);
|
|
3672
3698
|
}
|
|
@@ -3687,7 +3713,6 @@ function createContext(options) {
|
|
|
3687
3713
|
addon,
|
|
3688
3714
|
runtime: runtimeNative,
|
|
3689
3715
|
state,
|
|
3690
|
-
mimeByText,
|
|
3691
3716
|
hmacSigners,
|
|
3692
3717
|
ensurePool,
|
|
3693
3718
|
isPoolInitialized,
|
|
@@ -3697,12 +3722,14 @@ function createContext(options) {
|
|
|
3697
3722
|
hmacSigner
|
|
3698
3723
|
};
|
|
3699
3724
|
}
|
|
3700
|
-
var nativeFnCache;
|
|
3725
|
+
var nativeFnCache, MIME_CACHE_MAX = 1024, mimeByText, mimeStrCache;
|
|
3701
3726
|
var init_context = __esm(() => {
|
|
3702
3727
|
init_native2();
|
|
3703
3728
|
init_addon();
|
|
3704
3729
|
init_options();
|
|
3705
3730
|
nativeFnCache = new Map;
|
|
3731
|
+
mimeByText = new Map;
|
|
3732
|
+
mimeStrCache = new Map;
|
|
3706
3733
|
});
|
|
3707
3734
|
|
|
3708
3735
|
// src/rust-ffi/batch/build.ts
|
|
@@ -4533,6 +4560,10 @@ function buildFactories(ctx) {
|
|
|
4533
4560
|
},
|
|
4534
4561
|
rayonNumThreads() {
|
|
4535
4562
|
return asNumber(addon.rayonNumThreads());
|
|
4563
|
+
},
|
|
4564
|
+
clearSchemaCache() {
|
|
4565
|
+
const native = addon;
|
|
4566
|
+
native.clearSchemaCache?.();
|
|
4536
4567
|
}
|
|
4537
4568
|
};
|
|
4538
4569
|
}
|
|
@@ -5218,13 +5249,13 @@ var init_scalar = __esm(() => {
|
|
|
5218
5249
|
// src/rust-ffi/text.ts
|
|
5219
5250
|
function buildText(ctx) {
|
|
5220
5251
|
const { builtins, transport } = ctx.runtime;
|
|
5221
|
-
const mimeStrCache = new Map;
|
|
5222
5252
|
return {
|
|
5223
5253
|
mimeFromExtension(ext) {
|
|
5224
5254
|
let mime = mimeStrCache.get(ext);
|
|
5225
5255
|
if (mime === undefined) {
|
|
5226
5256
|
const f = transport.ffi;
|
|
5227
5257
|
mime = f ? f.mimeFromExtension(ext) ?? "application/octet-stream" : decoder.decode(ctx.cachedMime(encoder.encode(ext)));
|
|
5258
|
+
evictOldestHalf(mimeStrCache);
|
|
5228
5259
|
mimeStrCache.set(ext, mime);
|
|
5229
5260
|
}
|
|
5230
5261
|
return mime;
|
|
@@ -5549,6 +5580,15 @@ function compileNapi(descriptor) {
|
|
|
5549
5580
|
}
|
|
5550
5581
|
}
|
|
5551
5582
|
|
|
5583
|
+
// src/ingress/abort.ts
|
|
5584
|
+
var ABORT_CODE = "REQUEST_ABORTED";
|
|
5585
|
+
function isAbortError(err) {
|
|
5586
|
+
return err?.code === ABORT_CODE;
|
|
5587
|
+
}
|
|
5588
|
+
function abortResponse() {
|
|
5589
|
+
return new Response(null, { status: 499 });
|
|
5590
|
+
}
|
|
5591
|
+
|
|
5552
5592
|
// src/ingress/headers/memoized-headers.ts
|
|
5553
5593
|
var CACHE = new WeakMap;
|
|
5554
5594
|
function memoizedHeaders(entries) {
|
|
@@ -5607,7 +5647,11 @@ function buildSuccessInit(ingress, result, ctx, status = 200) {
|
|
|
5607
5647
|
return { status, headers };
|
|
5608
5648
|
}
|
|
5609
5649
|
function runBaked(ingress, opts, respond) {
|
|
5610
|
-
return (req, srv) =>
|
|
5650
|
+
return (req, srv) => {
|
|
5651
|
+
if (req.signal?.aborted)
|
|
5652
|
+
return abortResponse();
|
|
5653
|
+
return ingress.run(req, resolveIp(req, srv, opts), null, respond);
|
|
5654
|
+
};
|
|
5611
5655
|
}
|
|
5612
5656
|
// src/ingress/routes/read.ts
|
|
5613
5657
|
function readHandler(ingress, opts = {}) {
|
|
@@ -5658,6 +5702,44 @@ function bodyTooLargeError() {
|
|
|
5658
5702
|
err.code = "BODY_TOO_LARGE";
|
|
5659
5703
|
return err;
|
|
5660
5704
|
}
|
|
5705
|
+
function abortError() {
|
|
5706
|
+
const err = new Error(ABORT_CODE);
|
|
5707
|
+
err.code = ABORT_CODE;
|
|
5708
|
+
return err;
|
|
5709
|
+
}
|
|
5710
|
+
function throwIfAborted(signal) {
|
|
5711
|
+
if (signal?.aborted)
|
|
5712
|
+
throw abortError();
|
|
5713
|
+
}
|
|
5714
|
+
function createAbortGuard(signal) {
|
|
5715
|
+
if (!signal)
|
|
5716
|
+
return null;
|
|
5717
|
+
if (signal.aborted) {
|
|
5718
|
+
return { promise: Promise.reject(abortError()), cancel: () => {} };
|
|
5719
|
+
}
|
|
5720
|
+
let listener = null;
|
|
5721
|
+
const promise = new Promise((_resolve, reject) => {
|
|
5722
|
+
listener = () => reject(abortError());
|
|
5723
|
+
signal.addEventListener("abort", listener, { once: true });
|
|
5724
|
+
});
|
|
5725
|
+
return {
|
|
5726
|
+
promise,
|
|
5727
|
+
cancel: () => {
|
|
5728
|
+
if (listener !== null) {
|
|
5729
|
+
signal.removeEventListener("abort", listener);
|
|
5730
|
+
listener = null;
|
|
5731
|
+
}
|
|
5732
|
+
}
|
|
5733
|
+
};
|
|
5734
|
+
}
|
|
5735
|
+
function raceRead(read, guards) {
|
|
5736
|
+
if (guards.length === 0)
|
|
5737
|
+
return read;
|
|
5738
|
+
const racers = [read];
|
|
5739
|
+
for (const guard of guards)
|
|
5740
|
+
racers.push(guard.promise);
|
|
5741
|
+
return Promise.race(racers);
|
|
5742
|
+
}
|
|
5661
5743
|
var bodyWatchdog = (() => {
|
|
5662
5744
|
const inflight = new Set;
|
|
5663
5745
|
let timer = null;
|
|
@@ -5709,6 +5791,7 @@ function createDeadline(deadlineMs) {
|
|
|
5709
5791
|
return { promise, cancel: () => bodyWatchdog.untrack(entry) };
|
|
5710
5792
|
}
|
|
5711
5793
|
async function readBodyWithLimit(req, maxBytes, guard, timeoutMs) {
|
|
5794
|
+
throwIfAborted(req.signal);
|
|
5712
5795
|
const deadline = timeoutMs && timeoutMs > 0 ? timeoutMs : 0;
|
|
5713
5796
|
const declared = req.headers.get("content-length");
|
|
5714
5797
|
if (declared !== null) {
|
|
@@ -5718,45 +5801,67 @@ async function readBodyWithLimit(req, maxBytes, guard, timeoutMs) {
|
|
|
5718
5801
|
throw bodyTooLargeError();
|
|
5719
5802
|
}
|
|
5720
5803
|
if (deadline <= 0) {
|
|
5721
|
-
const
|
|
5722
|
-
if (
|
|
5723
|
-
|
|
5804
|
+
const buffered = peekBufferedBody(req);
|
|
5805
|
+
if (buffered !== false && "bytes" in buffered) {
|
|
5806
|
+
throwIfAborted(req.signal);
|
|
5807
|
+
if (guard && buffered.bytes.byteLength > maxBytes) {
|
|
5808
|
+
throw bodyTooLargeError();
|
|
5809
|
+
}
|
|
5810
|
+
return buffered.bytes;
|
|
5811
|
+
}
|
|
5812
|
+
const abort = createAbortGuard(req.signal);
|
|
5813
|
+
try {
|
|
5814
|
+
const bytes = await raceRead(buffered === false ? readBodyBytes(req) : buffered, abort !== null ? [abort] : []);
|
|
5815
|
+
throwIfAborted(req.signal);
|
|
5816
|
+
if (guard && bytes.byteLength > maxBytes) {
|
|
5817
|
+
throw bodyTooLargeError();
|
|
5818
|
+
}
|
|
5819
|
+
return bytes;
|
|
5820
|
+
} finally {
|
|
5821
|
+
abort?.cancel();
|
|
5724
5822
|
}
|
|
5725
|
-
return bytes;
|
|
5726
5823
|
}
|
|
5727
5824
|
const buffered = peekBufferedBody(req);
|
|
5728
5825
|
if (buffered === false) {
|
|
5729
5826
|
const dl = createDeadline(deadline);
|
|
5827
|
+
const abort = createAbortGuard(req.signal);
|
|
5730
5828
|
try {
|
|
5731
|
-
const bytes = await
|
|
5829
|
+
const bytes = await raceRead(readBodyBytes(req), abort !== null ? [dl, abort] : [dl]);
|
|
5830
|
+
throwIfAborted(req.signal);
|
|
5732
5831
|
if (guard && bytes.byteLength > maxBytes) {
|
|
5733
5832
|
throw bodyTooLargeError();
|
|
5734
5833
|
}
|
|
5735
5834
|
return bytes;
|
|
5736
5835
|
} finally {
|
|
5737
5836
|
dl.cancel();
|
|
5837
|
+
abort?.cancel();
|
|
5738
5838
|
}
|
|
5739
5839
|
}
|
|
5740
5840
|
if ("bytes" in buffered) {
|
|
5841
|
+
throwIfAborted(req.signal);
|
|
5741
5842
|
if (guard && buffered.bytes.byteLength > maxBytes) {
|
|
5742
5843
|
throw bodyTooLargeError();
|
|
5743
5844
|
}
|
|
5744
5845
|
return buffered.bytes;
|
|
5745
5846
|
}
|
|
5746
5847
|
const dl = createDeadline(deadline);
|
|
5848
|
+
const abort = createAbortGuard(req.signal);
|
|
5747
5849
|
try {
|
|
5748
|
-
const bytes = await
|
|
5850
|
+
const bytes = await raceRead(buffered, abort !== null ? [dl, abort] : [dl]);
|
|
5851
|
+
throwIfAborted(req.signal);
|
|
5749
5852
|
if (guard && bytes.byteLength > maxBytes) {
|
|
5750
5853
|
throw bodyTooLargeError();
|
|
5751
5854
|
}
|
|
5752
5855
|
return bytes;
|
|
5753
5856
|
} finally {
|
|
5754
5857
|
dl.cancel();
|
|
5858
|
+
abort?.cancel();
|
|
5755
5859
|
}
|
|
5756
5860
|
}
|
|
5757
5861
|
}
|
|
5758
5862
|
const body = req.body;
|
|
5759
5863
|
if (!body) {
|
|
5864
|
+
throwIfAborted(req.signal);
|
|
5760
5865
|
return EMPTY_BODY;
|
|
5761
5866
|
}
|
|
5762
5867
|
const reader = body.getReader();
|
|
@@ -5764,9 +5869,15 @@ async function readBodyWithLimit(req, maxBytes, guard, timeoutMs) {
|
|
|
5764
5869
|
let total = 0;
|
|
5765
5870
|
let completed = false;
|
|
5766
5871
|
const dl = deadline > 0 ? createDeadline(deadline) : null;
|
|
5872
|
+
const abort = createAbortGuard(req.signal);
|
|
5873
|
+
const guards = [];
|
|
5874
|
+
if (dl !== null)
|
|
5875
|
+
guards.push(dl);
|
|
5876
|
+
if (abort !== null)
|
|
5877
|
+
guards.push(abort);
|
|
5767
5878
|
try {
|
|
5768
5879
|
for (;; ) {
|
|
5769
|
-
const { done, value } =
|
|
5880
|
+
const { done, value } = await raceRead(reader.read(), guards);
|
|
5770
5881
|
if (done) {
|
|
5771
5882
|
completed = true;
|
|
5772
5883
|
break;
|
|
@@ -5781,6 +5892,7 @@ async function readBodyWithLimit(req, maxBytes, guard, timeoutMs) {
|
|
|
5781
5892
|
}
|
|
5782
5893
|
} finally {
|
|
5783
5894
|
dl?.cancel();
|
|
5895
|
+
abort?.cancel();
|
|
5784
5896
|
if (!completed) {
|
|
5785
5897
|
await reader.cancel().catch(() => {});
|
|
5786
5898
|
}
|
|
@@ -6000,6 +6112,8 @@ function echoHandler(ingress, opts = {}) {
|
|
|
6000
6112
|
const bodyTimeoutMs = opts.bodyTimeoutMs ?? DEFAULT_BODY_TIMEOUT_MS;
|
|
6001
6113
|
return async (req, srv) => {
|
|
6002
6114
|
const ip = resolveIp(req, srv, opts);
|
|
6115
|
+
if (req.signal?.aborted)
|
|
6116
|
+
return abortResponse();
|
|
6003
6117
|
const prep = ingress.run(req, ip, null, (result, ctx) => {
|
|
6004
6118
|
const terminal = ingress.terminalResponse(req, result, ctx);
|
|
6005
6119
|
if (terminal)
|
|
@@ -6014,6 +6128,8 @@ function echoHandler(ingress, opts = {}) {
|
|
|
6014
6128
|
});
|
|
6015
6129
|
if (prep.terminal)
|
|
6016
6130
|
return prep.terminal;
|
|
6131
|
+
if (req.signal?.aborted)
|
|
6132
|
+
return abortResponse();
|
|
6017
6133
|
const baseHeaders = prep.headers ?? [];
|
|
6018
6134
|
const requestedContentType = safeEchoContentType(req.headers.get("content-type") ?? "application/octet-stream");
|
|
6019
6135
|
const withEchoHeaders = (contentType) => {
|
|
@@ -6045,11 +6161,15 @@ function echoHandler(ingress, opts = {}) {
|
|
|
6045
6161
|
}
|
|
6046
6162
|
try {
|
|
6047
6163
|
const bodyBytes = await readBodyWithLimit(req, maxBodyBytes, true, bodyTimeoutMs);
|
|
6164
|
+
if (req.signal?.aborted)
|
|
6165
|
+
return abortResponse();
|
|
6048
6166
|
return new Response(bodyBytes.byteLength > 0 ? bodyBytes : null, {
|
|
6049
6167
|
status: 200,
|
|
6050
6168
|
headers: withEchoHeaders(requestedContentType)
|
|
6051
6169
|
});
|
|
6052
6170
|
} catch (err) {
|
|
6171
|
+
if (isAbortError(err))
|
|
6172
|
+
return abortResponse();
|
|
6053
6173
|
const code = err?.code;
|
|
6054
6174
|
const isTooLarge = code === "BODY_TOO_LARGE";
|
|
6055
6175
|
const isTimeout = code === "REQUEST_TIMEOUT";
|
|
@@ -6106,6 +6226,8 @@ function jsonWriteHandler(ingress, opts = {}) {
|
|
|
6106
6226
|
const jsonValid = opts.jsonValid ?? ingress.jsonValid ?? defaultJsonValid;
|
|
6107
6227
|
return async (req, srv) => {
|
|
6108
6228
|
const ip = resolveIp(req, srv, opts);
|
|
6229
|
+
if (req.signal?.aborted)
|
|
6230
|
+
return abortResponse();
|
|
6109
6231
|
const contentType = req.headers.get("content-type") ?? "";
|
|
6110
6232
|
if (!isJsonContentType(contentType)) {
|
|
6111
6233
|
return fallback.run(req, ip, null, (result, ctx) => fallback.errorResponse(req, result, 415, "unsupported_media_type", "Content-Type must be application/json", ctx));
|
|
@@ -6114,6 +6236,8 @@ function jsonWriteHandler(ingress, opts = {}) {
|
|
|
6114
6236
|
try {
|
|
6115
6237
|
bodyBytes = await readBodyWithLimit(req, maxBodyBytes, true, bodyTimeoutMs);
|
|
6116
6238
|
} catch (err) {
|
|
6239
|
+
if (isAbortError(err))
|
|
6240
|
+
return abortResponse();
|
|
6117
6241
|
const code = err?.code;
|
|
6118
6242
|
const isTooLarge = code === "BODY_TOO_LARGE";
|
|
6119
6243
|
return fallback.run(req, ip, null, (result, ctx) => {
|
|
@@ -6123,6 +6247,8 @@ function jsonWriteHandler(ingress, opts = {}) {
|
|
|
6123
6247
|
return fallback.errorResponse(req, result, isTooLarge ? 413 : 408, isTooLarge ? "body_too_large" : "request_timeout", isTooLarge ? "Request body is too large" : "Request body read timed out", ctx);
|
|
6124
6248
|
});
|
|
6125
6249
|
}
|
|
6250
|
+
if (req.signal?.aborted)
|
|
6251
|
+
return abortResponse();
|
|
6126
6252
|
return ingress.run(req, ip, bodyBytes, (result, ctx) => {
|
|
6127
6253
|
const terminal = ingress.terminalResponse(req, result, ctx);
|
|
6128
6254
|
if (terminal)
|
|
@@ -6144,12 +6270,16 @@ function jsonWriteHandler(ingress, opts = {}) {
|
|
|
6144
6270
|
}
|
|
6145
6271
|
// src/ingress/routes/options.ts
|
|
6146
6272
|
function optionsHandler(ingress, opts = {}) {
|
|
6147
|
-
return (req, srv) =>
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6273
|
+
return (req, srv) => {
|
|
6274
|
+
if (req.signal?.aborted)
|
|
6275
|
+
return abortResponse();
|
|
6276
|
+
return ingress.run(req, resolveIp(req, srv, opts), null, (result, ctx) => {
|
|
6277
|
+
const terminal = ingress.terminalResponse(req, result, ctx);
|
|
6278
|
+
if (terminal)
|
|
6279
|
+
return terminal;
|
|
6280
|
+
return new Response(null, buildSuccessInit(ingress, result, ctx, 204));
|
|
6281
|
+
});
|
|
6282
|
+
};
|
|
6153
6283
|
}
|
|
6154
6284
|
// src/ingress/routes/responder.ts
|
|
6155
6285
|
init_bytes();
|
|
@@ -6315,8 +6445,8 @@ function buildIgngexTerminal(result, _ctx) {
|
|
|
6315
6445
|
}
|
|
6316
6446
|
function parseSection(json) {
|
|
6317
6447
|
try {
|
|
6318
|
-
const
|
|
6319
|
-
return
|
|
6448
|
+
const parsed = JSON.parse(json);
|
|
6449
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
6320
6450
|
} catch {
|
|
6321
6451
|
return {};
|
|
6322
6452
|
}
|
|
@@ -6328,12 +6458,16 @@ function nativeResponderRoute(ingress, responder, opts = {}) {
|
|
|
6328
6458
|
const bodyTimeoutMs = opts.bodyTimeoutMs ?? DEFAULT_BODY_TIMEOUT_MS;
|
|
6329
6459
|
return async (req, srv, _params) => {
|
|
6330
6460
|
const ip = resolveIp(req, srv, opts);
|
|
6461
|
+
if (req.signal?.aborted)
|
|
6462
|
+
return abortResponse();
|
|
6331
6463
|
const requestId = decoder.decode(generateRequestId());
|
|
6332
6464
|
let body = null;
|
|
6333
6465
|
if (readBody) {
|
|
6334
6466
|
try {
|
|
6335
6467
|
body = await readBodyWithLimit(req, maxBodyBytes, true, bodyTimeoutMs);
|
|
6336
6468
|
} catch (err) {
|
|
6469
|
+
if (isAbortError(err))
|
|
6470
|
+
return abortResponse();
|
|
6337
6471
|
const code = err.code;
|
|
6338
6472
|
const status = code === "REQUEST_TIMEOUT" ? 408 : code === "BODY_TOO_LARGE" ? 413 : 400;
|
|
6339
6473
|
const payload = JSON.stringify({ error: "Bad Request", status, code: "BAD_REQUEST" });
|
|
@@ -6341,6 +6475,8 @@ function nativeResponderRoute(ingress, responder, opts = {}) {
|
|
|
6341
6475
|
headers.set("content-type", "application/json; charset=utf-8");
|
|
6342
6476
|
return new Response(payload, { status, headers });
|
|
6343
6477
|
}
|
|
6478
|
+
if (req.signal?.aborted)
|
|
6479
|
+
return abortResponse();
|
|
6344
6480
|
}
|
|
6345
6481
|
let terminal = null;
|
|
6346
6482
|
let snapshot = null;
|
|
@@ -6375,10 +6511,30 @@ function nativeResponderRoute(ingress, responder, opts = {}) {
|
|
|
6375
6511
|
}
|
|
6376
6512
|
// src/ingress/routes/native.ts
|
|
6377
6513
|
init_bytes();
|
|
6514
|
+
|
|
6515
|
+
// src/ingress/routes/safe-record.ts
|
|
6516
|
+
var PROTO_KEY = "__proto__";
|
|
6517
|
+
function safeRecord() {
|
|
6518
|
+
return {};
|
|
6519
|
+
}
|
|
6520
|
+
function assignOwn(target, key, value) {
|
|
6521
|
+
if (key === PROTO_KEY) {
|
|
6522
|
+
Object.defineProperty(target, key, {
|
|
6523
|
+
value,
|
|
6524
|
+
enumerable: true,
|
|
6525
|
+
writable: true,
|
|
6526
|
+
configurable: true
|
|
6527
|
+
});
|
|
6528
|
+
return;
|
|
6529
|
+
}
|
|
6530
|
+
target[key] = value;
|
|
6531
|
+
}
|
|
6532
|
+
|
|
6533
|
+
// src/ingress/routes/native.ts
|
|
6378
6534
|
function pairsToRecord(pairs) {
|
|
6379
|
-
const out =
|
|
6535
|
+
const out = safeRecord();
|
|
6380
6536
|
for (const [k, v] of pairs)
|
|
6381
|
-
out
|
|
6537
|
+
assignOwn(out, k, v);
|
|
6382
6538
|
return out;
|
|
6383
6539
|
}
|
|
6384
6540
|
function nativeRouteHandler(route, responder, opts = {}) {
|
|
@@ -6390,16 +6546,22 @@ function nativeRouteHandler(route, responder, opts = {}) {
|
|
|
6390
6546
|
const terminal = (status, code, message) => Response.json({ ok: false, error: { code, message } }, { status, headers: { "content-type": "application/json" } });
|
|
6391
6547
|
return async (req, srv, _params) => {
|
|
6392
6548
|
const ip = resolveIp(req, srv, opts);
|
|
6549
|
+
if (req.signal?.aborted)
|
|
6550
|
+
return abortResponse();
|
|
6393
6551
|
const requestId = decoder.decode(generateRequestId());
|
|
6394
6552
|
let body = null;
|
|
6395
6553
|
if (readBody) {
|
|
6396
6554
|
try {
|
|
6397
6555
|
body = await readBodyWithLimit(req, maxBodyBytes, true, bodyTimeoutMs);
|
|
6398
6556
|
} catch (err) {
|
|
6557
|
+
if (isAbortError(err))
|
|
6558
|
+
return abortResponse();
|
|
6399
6559
|
const code = err.code;
|
|
6400
6560
|
const status = code === "REQUEST_TIMEOUT" ? 408 : code === "BODY_TOO_LARGE" ? 413 : 400;
|
|
6401
6561
|
return terminal(status, "BAD_REQUEST", "Request body read failed");
|
|
6402
6562
|
}
|
|
6563
|
+
if (req.signal?.aborted)
|
|
6564
|
+
return abortResponse();
|
|
6403
6565
|
}
|
|
6404
6566
|
const url = req.url;
|
|
6405
6567
|
const qIndex = url.indexOf("?");
|
|
@@ -6435,6 +6597,49 @@ function nativeRouteHandler(route, responder, opts = {}) {
|
|
|
6435
6597
|
};
|
|
6436
6598
|
}
|
|
6437
6599
|
|
|
6600
|
+
// src/ingress/server-error.ts
|
|
6601
|
+
function toError(value) {
|
|
6602
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
6603
|
+
}
|
|
6604
|
+
var INTERNAL_HEADERS = [
|
|
6605
|
+
["content-type", "application/json; charset=utf-8"]
|
|
6606
|
+
];
|
|
6607
|
+
function createServerErrorHandler(opts) {
|
|
6608
|
+
const headers = memoizedHeaders(INTERNAL_HEADERS);
|
|
6609
|
+
return (error, req) => {
|
|
6610
|
+
const err = toError(error);
|
|
6611
|
+
if (opts.onError) {
|
|
6612
|
+
try {
|
|
6613
|
+
opts.onError({ error: err, request: req });
|
|
6614
|
+
} catch {}
|
|
6615
|
+
}
|
|
6616
|
+
if (opts.logger) {
|
|
6617
|
+
try {
|
|
6618
|
+
opts.logger(JSON.stringify({
|
|
6619
|
+
level: "error",
|
|
6620
|
+
msg: "unhandled_handler_error",
|
|
6621
|
+
error: err.message
|
|
6622
|
+
}));
|
|
6623
|
+
} catch {}
|
|
6624
|
+
}
|
|
6625
|
+
return new Response(ERROR_BODIES.internal, { status: 500, headers });
|
|
6626
|
+
};
|
|
6627
|
+
}
|
|
6628
|
+
function guardRouteHandler(handler, onServerError) {
|
|
6629
|
+
return (req, srv, params) => {
|
|
6630
|
+
try {
|
|
6631
|
+
const out = handler(req, srv, params);
|
|
6632
|
+
const thenable = out;
|
|
6633
|
+
if (thenable !== null && thenable !== undefined && typeof thenable.then === "function") {
|
|
6634
|
+
return thenable.catch((e) => onServerError(e, req));
|
|
6635
|
+
}
|
|
6636
|
+
return out;
|
|
6637
|
+
} catch (e) {
|
|
6638
|
+
return onServerError(e, req);
|
|
6639
|
+
}
|
|
6640
|
+
};
|
|
6641
|
+
}
|
|
6642
|
+
|
|
6438
6643
|
// src/ingress/path-matcher.ts
|
|
6439
6644
|
function buildPathMatcher(routes) {
|
|
6440
6645
|
const exact = new Map;
|
|
@@ -6542,6 +6747,10 @@ function buildRouteHandlers(options) {
|
|
|
6542
6747
|
getIp: options.getIp,
|
|
6543
6748
|
copyBody: options.copyBody
|
|
6544
6749
|
};
|
|
6750
|
+
const onServerError = createServerErrorHandler({
|
|
6751
|
+
onError: options.onError,
|
|
6752
|
+
logger: options.logger
|
|
6753
|
+
});
|
|
6545
6754
|
const serverRoutes = {};
|
|
6546
6755
|
for (const [path, spec] of Object.entries(options.routes)) {
|
|
6547
6756
|
const routeOpts = {
|
|
@@ -6550,9 +6759,12 @@ function buildRouteHandlers(options) {
|
|
|
6550
6759
|
bodyTimeoutMs: spec.bodyTimeoutMs
|
|
6551
6760
|
};
|
|
6552
6761
|
const methods = {};
|
|
6553
|
-
|
|
6762
|
+
const hasStatic = spec.static !== undefined;
|
|
6763
|
+
if (hasStatic) {
|
|
6764
|
+
methods.GET = typeof spec.static === "function" ? guardRouteHandler(spec.static, onServerError) : spec.static;
|
|
6765
|
+
} else if (spec.read) {
|
|
6554
6766
|
if (typeof spec.read === "function") {
|
|
6555
|
-
methods.GET = spec.read;
|
|
6767
|
+
methods.GET = guardRouteHandler(spec.read, onServerError);
|
|
6556
6768
|
} else {
|
|
6557
6769
|
methods.GET = readHandler(spec.read, routeOpts);
|
|
6558
6770
|
methods.HEAD = headHandler(spec.read, routeOpts);
|
|
@@ -6570,34 +6782,40 @@ function buildRouteHandlers(options) {
|
|
|
6570
6782
|
if (spec.echo) {
|
|
6571
6783
|
methods.POST = echoHandler(spec.echo, routeOpts);
|
|
6572
6784
|
}
|
|
6573
|
-
if (spec.cookies) {
|
|
6785
|
+
if (spec.cookies && !hasStatic) {
|
|
6574
6786
|
methods.GET = readHandler(spec.cookies, routeOpts);
|
|
6575
6787
|
}
|
|
6576
6788
|
if (spec.delete) {
|
|
6577
6789
|
methods.DELETE = deleteHandler(spec.delete, routeOpts);
|
|
6578
6790
|
}
|
|
6579
6791
|
if (spec.responder) {
|
|
6580
|
-
const
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6792
|
+
const responderMethods = (spec.responder.methods ?? ["GET"]).filter((m) => !(hasStatic && m === "GET"));
|
|
6793
|
+
if (responderMethods.length > 0) {
|
|
6794
|
+
const responderRoute = nativeResponderRoute(spec.responder.ingress, spec.responder.handler, {
|
|
6795
|
+
...routeOpts,
|
|
6796
|
+
terminalStyle: spec.responder.terminalStyle,
|
|
6797
|
+
readBody: spec.responder.readBody
|
|
6798
|
+
});
|
|
6799
|
+
const guardedResponderRoute = guardRouteHandler(responderRoute, onServerError);
|
|
6800
|
+
for (const m of responderMethods) {
|
|
6801
|
+
methods[m] = guardedResponderRoute;
|
|
6802
|
+
}
|
|
6588
6803
|
}
|
|
6589
6804
|
}
|
|
6590
6805
|
if (spec.native) {
|
|
6591
|
-
const
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6806
|
+
const nativeMethods = (spec.native.methods ?? ["GET"]).filter((m) => !(hasStatic && m === "GET"));
|
|
6807
|
+
if (nativeMethods.length > 0) {
|
|
6808
|
+
const nativeRoute = nativeRouteHandler(createNativeRoute(spec.native.plan), spec.native.handler, {
|
|
6809
|
+
...routeOpts,
|
|
6810
|
+
readBody: spec.native.readBody
|
|
6811
|
+
});
|
|
6812
|
+
const guardedNativeRoute = guardRouteHandler(nativeRoute, onServerError);
|
|
6813
|
+
for (const m of nativeMethods) {
|
|
6814
|
+
methods[m] = guardedNativeRoute;
|
|
6815
|
+
}
|
|
6598
6816
|
}
|
|
6599
6817
|
}
|
|
6600
|
-
const primary = spec.delete ?? (spec.read && typeof spec.read !== "function" ? spec.read : undefined) ?? spec.cookies ?? spec.write ?? spec.echo;
|
|
6818
|
+
const primary = spec.delete ?? (!hasStatic && spec.read && typeof spec.read !== "function" ? spec.read : undefined) ?? (!hasStatic ? spec.cookies : undefined) ?? spec.write ?? spec.echo;
|
|
6601
6819
|
if (primary) {
|
|
6602
6820
|
methods.OPTIONS = optionsHandler(primary, routeOpts);
|
|
6603
6821
|
}
|
|
@@ -6610,12 +6828,17 @@ function createIngressServer(options) {
|
|
|
6610
6828
|
throw new TypeError("createIngressServer is Bun-only (it builds a Bun.serve config). " + "On Node.js use createIngressServerNode with the same route handlers.");
|
|
6611
6829
|
}
|
|
6612
6830
|
const { routes: serverRoutes, baseOpts } = buildRouteHandlers(options);
|
|
6831
|
+
const onServerError = createServerErrorHandler({
|
|
6832
|
+
onError: options.onError,
|
|
6833
|
+
logger: options.logger
|
|
6834
|
+
});
|
|
6613
6835
|
const serverOptions = {
|
|
6614
6836
|
hostname: options.hostname ?? "0.0.0.0",
|
|
6615
6837
|
port: options.port,
|
|
6616
6838
|
idleTimeout: options.idleTimeout ?? 30,
|
|
6617
6839
|
routes: serverRoutes,
|
|
6618
|
-
maxRequestBodySize: options.maxRequestBodySize ?? DEFAULT_MAX_REQUEST_BODY_SIZE
|
|
6840
|
+
maxRequestBodySize: options.maxRequestBodySize ?? DEFAULT_MAX_REQUEST_BODY_SIZE,
|
|
6841
|
+
error: (error, req) => onServerError(error, req)
|
|
6619
6842
|
};
|
|
6620
6843
|
if (options.reusePort) {
|
|
6621
6844
|
serverOptions.reusePort = true;
|
|
@@ -6670,6 +6893,13 @@ function nodeRequestToWebRequest(req) {
|
|
|
6670
6893
|
const bodyForbidden = method === "GET" || method === "HEAD";
|
|
6671
6894
|
const hasBody = requestHasBody(req);
|
|
6672
6895
|
const init = { method, headers };
|
|
6896
|
+
const abort = new AbortController;
|
|
6897
|
+
req.on("aborted", () => abort.abort());
|
|
6898
|
+
req.on("close", () => {
|
|
6899
|
+
if (!req.complete)
|
|
6900
|
+
abort.abort();
|
|
6901
|
+
});
|
|
6902
|
+
init.signal = abort.signal;
|
|
6673
6903
|
if (hasBody && !bodyForbidden) {
|
|
6674
6904
|
init.body = Readable.toWeb(req);
|
|
6675
6905
|
init.duplex = "half";
|
|
@@ -6723,8 +6953,36 @@ function drainUnreadBody(res) {
|
|
|
6723
6953
|
function requestHasBody(req) {
|
|
6724
6954
|
return Number(req.headers["content-length"] ?? 0) > 0 || req.headers["transfer-encoding"] !== undefined;
|
|
6725
6955
|
}
|
|
6956
|
+
var STATIC_RESPONSE_CACHE = new WeakMap;
|
|
6957
|
+
function materializeStaticResponse(response) {
|
|
6958
|
+
let cached = STATIC_RESPONSE_CACHE.get(response);
|
|
6959
|
+
if (cached === undefined) {
|
|
6960
|
+
cached = (async () => {
|
|
6961
|
+
const body = response.body === null ? null : new Uint8Array(await response.arrayBuffer());
|
|
6962
|
+
const headers = new Headers(response.headers);
|
|
6963
|
+
headers.delete("content-length");
|
|
6964
|
+
headers.delete("transfer-encoding");
|
|
6965
|
+
return {
|
|
6966
|
+
status: response.status,
|
|
6967
|
+
statusText: response.statusText,
|
|
6968
|
+
headers,
|
|
6969
|
+
body
|
|
6970
|
+
};
|
|
6971
|
+
})();
|
|
6972
|
+
STATIC_RESPONSE_CACHE.set(response, cached);
|
|
6973
|
+
}
|
|
6974
|
+
return cached.then((c) => new Response(c.body, {
|
|
6975
|
+
status: c.status,
|
|
6976
|
+
statusText: c.statusText,
|
|
6977
|
+
headers: new Headers(c.headers)
|
|
6978
|
+
}));
|
|
6979
|
+
}
|
|
6726
6980
|
function makeRequestListener(options, routes, baseOpts) {
|
|
6727
6981
|
const matchPath = buildPathMatcher(routes);
|
|
6982
|
+
const onServerError = createServerErrorHandler({
|
|
6983
|
+
onError: options.onError,
|
|
6984
|
+
logger: options.logger
|
|
6985
|
+
});
|
|
6728
6986
|
return async (req, res) => {
|
|
6729
6987
|
try {
|
|
6730
6988
|
const contentLengthHeader = req.headers["content-length"];
|
|
@@ -6745,10 +7003,19 @@ function makeRequestListener(options, routes, baseOpts) {
|
|
|
6745
7003
|
const pathname = new URL(webReq.url).pathname;
|
|
6746
7004
|
const method = webReq.method ?? "GET";
|
|
6747
7005
|
const matched = matchPath(pathname);
|
|
6748
|
-
const
|
|
7006
|
+
const entry = matched?.methods?.[method];
|
|
6749
7007
|
let response;
|
|
6750
|
-
if (
|
|
6751
|
-
|
|
7008
|
+
if (entry !== undefined && matched !== undefined) {
|
|
7009
|
+
if (typeof entry === "function") {
|
|
7010
|
+
response = await entry(webReq, req, matched.params);
|
|
7011
|
+
} else if (entry instanceof Response) {
|
|
7012
|
+
response = await materializeStaticResponse(entry);
|
|
7013
|
+
} else if (options.fallback !== undefined) {
|
|
7014
|
+
const fallback = fallbackHandler(options.fallback, baseOpts);
|
|
7015
|
+
response = await fallback(webReq, req);
|
|
7016
|
+
} else {
|
|
7017
|
+
response = new Response("Not Found", { status: 404 });
|
|
7018
|
+
}
|
|
6752
7019
|
} else if (options.fallback !== undefined) {
|
|
6753
7020
|
const fallback = fallbackHandler(options.fallback, baseOpts);
|
|
6754
7021
|
response = await fallback(webReq, req);
|
|
@@ -6758,21 +7025,11 @@ function makeRequestListener(options, routes, baseOpts) {
|
|
|
6758
7025
|
await writeResponse(res, response);
|
|
6759
7026
|
} catch (err) {
|
|
6760
7027
|
if (!res.headersSent) {
|
|
6761
|
-
res
|
|
6762
|
-
res.setHeader("content-type", "application/json");
|
|
6763
|
-
res.end(JSON.stringify({
|
|
6764
|
-
error: { code: "internal_error", message: "Internal Server Error" }
|
|
6765
|
-
}));
|
|
7028
|
+
await writeResponse(res, onServerError(err));
|
|
6766
7029
|
} else {
|
|
7030
|
+
onServerError(err);
|
|
6767
7031
|
res.destroy();
|
|
6768
7032
|
}
|
|
6769
|
-
if (options.onError) {
|
|
6770
|
-
try {
|
|
6771
|
-
options.onError({
|
|
6772
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
6773
|
-
});
|
|
6774
|
-
} catch {}
|
|
6775
|
-
}
|
|
6776
7033
|
}
|
|
6777
7034
|
};
|
|
6778
7035
|
}
|
|
@@ -7850,6 +8107,9 @@ class BufferPool {
|
|
|
7850
8107
|
get createdCount() {
|
|
7851
8108
|
return this.created;
|
|
7852
8109
|
}
|
|
8110
|
+
shrink() {
|
|
8111
|
+
this.free.length = 0;
|
|
8112
|
+
}
|
|
7853
8113
|
acquire(minSize = 0) {
|
|
7854
8114
|
if (this.maxInFlight > 0 && this.inFlight >= this.maxInFlight) {
|
|
7855
8115
|
throw new RangeError(`BufferPool: maxInFlight (${this.maxInFlight}) exceeded — too many ` + "buffers borrowed at once (unreleased zero-copy responses?). " + "Raise maxInFlight or consume/release responses faster.");
|
|
@@ -8614,6 +8874,9 @@ function jsonResponse(status, body) {
|
|
|
8614
8874
|
function livenessHandler() {
|
|
8615
8875
|
return (_req) => jsonResponse(200, { status: "ok" });
|
|
8616
8876
|
}
|
|
8877
|
+
function staticLiveness() {
|
|
8878
|
+
return jsonResponse(200, { status: "ok" });
|
|
8879
|
+
}
|
|
8617
8880
|
function readinessHandler(check) {
|
|
8618
8881
|
return async (_req) => {
|
|
8619
8882
|
if (!check)
|
|
@@ -8647,6 +8910,8 @@ var DEFAULT_BUCKETS = [
|
|
|
8647
8910
|
5,
|
|
8648
8911
|
10
|
|
8649
8912
|
];
|
|
8913
|
+
var MAX_SERIES_PER_METRIC = 1e4;
|
|
8914
|
+
var SERIES_DROPPED_METRIC = "castrum_metrics_series_dropped_total";
|
|
8650
8915
|
function labelKey(names, values) {
|
|
8651
8916
|
if (names.length === 0)
|
|
8652
8917
|
return "";
|
|
@@ -8659,10 +8924,25 @@ function labelKey(names, values) {
|
|
|
8659
8924
|
function escapeLabel(v) {
|
|
8660
8925
|
return v.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
|
|
8661
8926
|
}
|
|
8927
|
+
function evictOldestHalf2(map) {
|
|
8928
|
+
if (map.size < MAX_SERIES_PER_METRIC)
|
|
8929
|
+
return [];
|
|
8930
|
+
const drop = Math.ceil(map.size / 2);
|
|
8931
|
+
const dropped = [];
|
|
8932
|
+
for (const key of map.keys()) {
|
|
8933
|
+
if (dropped.length >= drop)
|
|
8934
|
+
break;
|
|
8935
|
+
dropped.push(key);
|
|
8936
|
+
}
|
|
8937
|
+
for (const key of dropped)
|
|
8938
|
+
map.delete(key);
|
|
8939
|
+
return dropped;
|
|
8940
|
+
}
|
|
8662
8941
|
function createMetrics() {
|
|
8663
8942
|
const counters = new Map;
|
|
8664
8943
|
const gauges = new Map;
|
|
8665
8944
|
const histograms = new Map;
|
|
8945
|
+
let seriesDropped = 0;
|
|
8666
8946
|
const bucketUpper = (buckets) => [
|
|
8667
8947
|
...buckets.map((b) => b),
|
|
8668
8948
|
Number.POSITIVE_INFINITY
|
|
@@ -8707,6 +8987,11 @@ function createMetrics() {
|
|
|
8707
8987
|
out.push(...renderGauge(e));
|
|
8708
8988
|
for (const e of histograms.values())
|
|
8709
8989
|
out.push(...renderHistogram(e));
|
|
8990
|
+
if (seriesDropped > 0 && !counters.has(SERIES_DROPPED_METRIC) && !gauges.has(SERIES_DROPPED_METRIC) && !histograms.has(SERIES_DROPPED_METRIC)) {
|
|
8991
|
+
out.push(`# HELP ${SERIES_DROPPED_METRIC} Total metric series evicted by the per-family cardinality cap.`);
|
|
8992
|
+
out.push(`# TYPE ${SERIES_DROPPED_METRIC} counter`);
|
|
8993
|
+
out.push(`${SERIES_DROPPED_METRIC} ${seriesDropped}`);
|
|
8994
|
+
}
|
|
8710
8995
|
return `${out.join(`
|
|
8711
8996
|
`)}
|
|
8712
8997
|
`;
|
|
@@ -8715,6 +9000,7 @@ function createMetrics() {
|
|
|
8715
9000
|
counters.clear();
|
|
8716
9001
|
gauges.clear();
|
|
8717
9002
|
histograms.clear();
|
|
9003
|
+
seriesDropped = 0;
|
|
8718
9004
|
};
|
|
8719
9005
|
return {
|
|
8720
9006
|
counter(name, help, labelNames = []) {
|
|
@@ -8731,7 +9017,13 @@ function createMetrics() {
|
|
|
8731
9017
|
return {
|
|
8732
9018
|
inc(labels, by = 1) {
|
|
8733
9019
|
const key = labelKey(names, names.map((n) => labels?.[n] ?? ""));
|
|
8734
|
-
|
|
9020
|
+
const prev = entry.values.get(key);
|
|
9021
|
+
if (prev === undefined) {
|
|
9022
|
+
seriesDropped += evictOldestHalf2(entry.values).length;
|
|
9023
|
+
entry.values.set(key, by);
|
|
9024
|
+
} else {
|
|
9025
|
+
entry.values.set(key, prev + by);
|
|
9026
|
+
}
|
|
8735
9027
|
}
|
|
8736
9028
|
};
|
|
8737
9029
|
},
|
|
@@ -8747,14 +9039,27 @@ function createMetrics() {
|
|
|
8747
9039
|
gauges.set(name, entry);
|
|
8748
9040
|
const names = entry.labelNames;
|
|
8749
9041
|
const keyOf = (labels) => labelKey(names, names.map((n) => labels?.[n] ?? ""));
|
|
9042
|
+
const add = (labels, delta) => {
|
|
9043
|
+
const key = keyOf(labels);
|
|
9044
|
+
const prev = entry.values.get(key);
|
|
9045
|
+
if (prev === undefined) {
|
|
9046
|
+
seriesDropped += evictOldestHalf2(entry.values).length;
|
|
9047
|
+
entry.values.set(key, delta);
|
|
9048
|
+
} else {
|
|
9049
|
+
entry.values.set(key, prev + delta);
|
|
9050
|
+
}
|
|
9051
|
+
};
|
|
8750
9052
|
return {
|
|
8751
9053
|
inc(labels, by = 1) {
|
|
8752
|
-
|
|
9054
|
+
add(labels, by);
|
|
8753
9055
|
},
|
|
8754
9056
|
dec(labels, by = 1) {
|
|
8755
|
-
|
|
9057
|
+
add(labels, -by);
|
|
8756
9058
|
},
|
|
8757
9059
|
set(labels, value) {
|
|
9060
|
+
if (!entry.values.has(keyOf(labels))) {
|
|
9061
|
+
seriesDropped += evictOldestHalf2(entry.values).length;
|
|
9062
|
+
}
|
|
8758
9063
|
entry.values.set(keyOf(labels), value);
|
|
8759
9064
|
}
|
|
8760
9065
|
};
|
|
@@ -8777,6 +9082,10 @@ function createMetrics() {
|
|
|
8777
9082
|
const key = labelKey(names, names.map((n) => labels?.[n] ?? ""));
|
|
8778
9083
|
let counts = entry.counts.get(key);
|
|
8779
9084
|
if (!counts) {
|
|
9085
|
+
const dropped = evictOldestHalf2(entry.counts);
|
|
9086
|
+
for (const k of dropped)
|
|
9087
|
+
entry.sums.delete(k);
|
|
9088
|
+
seriesDropped += dropped.length;
|
|
8780
9089
|
counts = new Array(entry.buckets.length + 1).fill(0);
|
|
8781
9090
|
entry.counts.set(key, counts);
|
|
8782
9091
|
}
|
|
@@ -8861,6 +9170,14 @@ function createIngressRouter(options) {
|
|
|
8861
9170
|
if (options.copyBody !== undefined)
|
|
8862
9171
|
baseOpts.copyBody = options.copyBody;
|
|
8863
9172
|
for (const [path, spec] of Object.entries(options.routes)) {
|
|
9173
|
+
if (spec.static !== undefined) {
|
|
9174
|
+
bakedRoutes[path] = {
|
|
9175
|
+
static: spec.static,
|
|
9176
|
+
maxBodyBytes: spec.maxBodyBytes,
|
|
9177
|
+
bodyTimeoutMs: spec.bodyTimeoutMs
|
|
9178
|
+
};
|
|
9179
|
+
continue;
|
|
9180
|
+
}
|
|
8864
9181
|
if (spec.raw) {
|
|
8865
9182
|
bakedRoutes[path] = { read: spec.raw };
|
|
8866
9183
|
continue;
|
|
@@ -8935,6 +9252,9 @@ function createIngressRouter(options) {
|
|
|
8935
9252
|
if (typeof methodHandler === "function") {
|
|
8936
9253
|
return methodHandler(req, srv, matched.params);
|
|
8937
9254
|
}
|
|
9255
|
+
if (methodHandler instanceof Response) {
|
|
9256
|
+
return methodHandler.clone();
|
|
9257
|
+
}
|
|
8938
9258
|
if (req.method === "OPTIONS" && typeof matched.methods.OPTIONS === "function") {
|
|
8939
9259
|
return matched.methods.OPTIONS(req, srv, matched.params);
|
|
8940
9260
|
}
|
|
@@ -10234,6 +10554,9 @@ function createPipeline(opts = {}) {
|
|
|
10234
10554
|
try {
|
|
10235
10555
|
body = await readBodyWithLimit(req, maxBodyBytes, true, bodyTimeoutMs);
|
|
10236
10556
|
} catch (err) {
|
|
10557
|
+
if (isAbortError(err)) {
|
|
10558
|
+
return { terminal: true, response: abortResponse(), result: null, ctx };
|
|
10559
|
+
}
|
|
10237
10560
|
return {
|
|
10238
10561
|
terminal: true,
|
|
10239
10562
|
response: bodyErrorResponse(ingress, req, requestId, err),
|
|
@@ -10401,6 +10724,19 @@ function cancelledError() {
|
|
|
10401
10724
|
err.name = "TaskCancelledError";
|
|
10402
10725
|
return err;
|
|
10403
10726
|
}
|
|
10727
|
+
var TASK_SUBMIT_ACCEPTED = 1;
|
|
10728
|
+
var TASK_SUBMIT_OVERLOADED = 2;
|
|
10729
|
+
function taskSubmitError(code) {
|
|
10730
|
+
if (code === TASK_SUBMIT_ACCEPTED)
|
|
10731
|
+
return null;
|
|
10732
|
+
if (code === TASK_SUBMIT_OVERLOADED) {
|
|
10733
|
+
const err = new Error("task rejected: native work queue is full");
|
|
10734
|
+
err.name = "TaskOverloadedError";
|
|
10735
|
+
err.code = "OVERLOADED";
|
|
10736
|
+
return err;
|
|
10737
|
+
}
|
|
10738
|
+
return new Error("task rejected by the native runtime");
|
|
10739
|
+
}
|
|
10404
10740
|
var shared = null;
|
|
10405
10741
|
function createTaskRuntime(options = {}) {
|
|
10406
10742
|
if (shared)
|
|
@@ -10546,10 +10882,12 @@ function createTaskRuntime(options = {}) {
|
|
|
10546
10882
|
return new Promise((resolve, reject) => {
|
|
10547
10883
|
inflight.set(id, { out: null, keep: null, resolve, reject });
|
|
10548
10884
|
retain();
|
|
10549
|
-
|
|
10885
|
+
const rejected = taskSubmitError(ffi.taskSubmit(op, args, id));
|
|
10886
|
+
if (rejected) {
|
|
10550
10887
|
inflight.delete(id);
|
|
10551
|
-
|
|
10552
|
-
|
|
10888
|
+
if (inflight.size === 0)
|
|
10889
|
+
release();
|
|
10890
|
+
reject(rejected);
|
|
10553
10891
|
return;
|
|
10554
10892
|
}
|
|
10555
10893
|
const signal = runOptions?.signal;
|
|
@@ -10568,10 +10906,12 @@ function createTaskRuntime(options = {}) {
|
|
|
10568
10906
|
return new Promise((resolve, reject) => {
|
|
10569
10907
|
inflight.set(id, { out: output, keep: null, resolve, reject });
|
|
10570
10908
|
retain();
|
|
10571
|
-
|
|
10909
|
+
const rejected = taskSubmitError(ffi.taskSubmitOut(op, args, id, output));
|
|
10910
|
+
if (rejected) {
|
|
10572
10911
|
inflight.delete(id);
|
|
10573
|
-
|
|
10574
|
-
|
|
10912
|
+
if (inflight.size === 0)
|
|
10913
|
+
release();
|
|
10914
|
+
reject(rejected);
|
|
10575
10915
|
return;
|
|
10576
10916
|
}
|
|
10577
10917
|
const signal = runOptions?.signal;
|
|
@@ -10590,10 +10930,12 @@ function createTaskRuntime(options = {}) {
|
|
|
10590
10930
|
return new Promise((resolve, reject) => {
|
|
10591
10931
|
inflight.set(id, { out: null, keep: data, resolve, reject });
|
|
10592
10932
|
retain();
|
|
10593
|
-
|
|
10933
|
+
const rejected = taskSubmitError(ffi.taskSubmitSlice(op, hdr, data, id));
|
|
10934
|
+
if (rejected) {
|
|
10594
10935
|
inflight.delete(id);
|
|
10595
|
-
|
|
10596
|
-
|
|
10936
|
+
if (inflight.size === 0)
|
|
10937
|
+
release();
|
|
10938
|
+
reject(rejected);
|
|
10597
10939
|
return;
|
|
10598
10940
|
}
|
|
10599
10941
|
const signal = runOptions?.signal;
|
|
@@ -10695,12 +11037,30 @@ var opDecision = (op) => {
|
|
|
10695
11037
|
|
|
10696
11038
|
// index.ts
|
|
10697
11039
|
init_bytes();
|
|
11040
|
+
|
|
11041
|
+
// src/shared/memory.ts
|
|
11042
|
+
init_rust_ffi();
|
|
11043
|
+
init_context();
|
|
11044
|
+
init_runtime();
|
|
11045
|
+
function flushMemory(options = {}) {
|
|
11046
|
+
loader.clear();
|
|
11047
|
+
clearMimeCaches();
|
|
11048
|
+
try {
|
|
11049
|
+
rust.clearSchemaCache();
|
|
11050
|
+
} catch {}
|
|
11051
|
+
if (options.gc !== false) {
|
|
11052
|
+
forceGc2();
|
|
11053
|
+
}
|
|
11054
|
+
}
|
|
11055
|
+
|
|
11056
|
+
// index.ts
|
|
10698
11057
|
init_packed();
|
|
10699
11058
|
|
|
10700
11059
|
// src/shared/uuid.ts
|
|
10701
11060
|
init_uuid();
|
|
10702
11061
|
var uuidv7 = runtimeUuid.uuidv7;
|
|
10703
11062
|
export {
|
|
11063
|
+
ABORT_CODE,
|
|
10704
11064
|
AdaptiveEstimate,
|
|
10705
11065
|
BakedIngressResult,
|
|
10706
11066
|
DEFAULT_BODY_TIMEOUT_MS,
|
|
@@ -10762,6 +11122,7 @@ export {
|
|
|
10762
11122
|
TASK_STATUS_CANCELLED,
|
|
10763
11123
|
TASK_STATUS_ERROR,
|
|
10764
11124
|
TASK_STATUS_OK,
|
|
11125
|
+
abortResponse,
|
|
10765
11126
|
brotliCapacityGuess,
|
|
10766
11127
|
buildResponseContext,
|
|
10767
11128
|
buildTerminalResponse,
|
|
@@ -10796,6 +11157,7 @@ export {
|
|
|
10796
11157
|
errorCodeName,
|
|
10797
11158
|
errorMessage,
|
|
10798
11159
|
fallbackHandler,
|
|
11160
|
+
flushMemory,
|
|
10799
11161
|
generateRequestId,
|
|
10800
11162
|
getIngressBinding,
|
|
10801
11163
|
gracefulShutdown,
|
|
@@ -10803,6 +11165,7 @@ export {
|
|
|
10803
11165
|
headHandler,
|
|
10804
11166
|
headersForResult,
|
|
10805
11167
|
healthHandler,
|
|
11168
|
+
isAbortError,
|
|
10806
11169
|
isNativeOp,
|
|
10807
11170
|
isProven,
|
|
10808
11171
|
isValidResponseStatus,
|
|
@@ -10839,6 +11202,7 @@ export {
|
|
|
10839
11202
|
safeTerminalStatus,
|
|
10840
11203
|
serializeTraceParent,
|
|
10841
11204
|
sseResponse,
|
|
11205
|
+
staticLiveness,
|
|
10842
11206
|
statusForErrorCode,
|
|
10843
11207
|
unpackBitset,
|
|
10844
11208
|
unpackByteResults,
|