castrum 0.9.7 → 0.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/castrum.darwin-arm64.node +0 -0
  4. package/castrum.darwin-x64.node +0 -0
  5. package/castrum.linux-arm64-gnu.node +0 -0
  6. package/castrum.linux-arm64-musl.node +0 -0
  7. package/castrum.linux-x64-gnu.node +0 -0
  8. package/castrum.linux-x64-musl.node +0 -0
  9. package/castrum.linux-x64-v3-gnu.node +0 -0
  10. package/castrum.win32-x64-msvc.node +0 -0
  11. package/dist/index.d.ts +664 -8
  12. package/dist/index.js +2014 -1327
  13. package/docs/BENCHMARKS.md +13 -0
  14. package/docs/CASE_STUDY.md +1 -1
  15. package/docs/ENVIRONMENT.md +8 -1
  16. package/docs/FFI_BUN_GUIDE.md +1 -1
  17. package/docs/INGRESS.md +4 -4
  18. package/docs/REPO_MAP.md +7 -5
  19. package/docs/RND-CONCURRENCY.md +20 -4
  20. package/docs/superpowers/plans/2026-09-18-castrum-hardening-cleanup.md +617 -0
  21. package/docs/superpowers/plans/2026-09-18-elysia-learnings-adoption.md +634 -0
  22. package/index.ts +6 -0
  23. package/package.json +4 -2
  24. package/rust/crypto/aead.rs +44 -0
  25. package/rust/crypto/argon2.rs +32 -2
  26. package/rust/crypto/jwt/tests.rs +19 -0
  27. package/rust/crypto/jwt/token.rs +43 -28
  28. package/rust/crypto/session.rs +6 -0
  29. package/rust/ffi/crypto.rs +63 -32
  30. package/rust/ffi/hashing.rs +48 -33
  31. package/rust/ffi/http.rs +62 -36
  32. package/rust/ffi/metrics.rs +6 -2
  33. package/rust/ffi/payload.rs +47 -27
  34. package/rust/ffi/task.rs +55 -35
  35. package/rust/ffi/tests.rs +24 -0
  36. package/rust/ffi/util.rs +40 -0
  37. package/rust/http/headers.rs +13 -0
  38. package/rust/http/multipart.rs +139 -0
  39. package/rust/ingress/api.rs +21 -5
  40. package/rust/ingress/mod.rs +7 -0
  41. package/rust/ingress/native_route.rs +25 -5
  42. package/rust/ingress/schema_cache.rs +266 -0
  43. package/rust/task/completion.rs +7 -0
  44. package/rust/task/mod.rs +2 -1
  45. package/rust/task/runtime.rs +55 -7
  46. package/rust/task/tests.rs +128 -1
  47. package/src/ingress/abort.ts +48 -0
  48. package/src/ingress/body.ts +104 -8
  49. package/src/ingress/decode/baked-result.ts +13 -5
  50. package/src/ingress/decode/fast-result.ts +1 -1
  51. package/src/ingress/decode/result-base.ts +16 -2
  52. package/src/ingress/handlers.ts +104 -31
  53. package/src/ingress/headers/memoized-headers.ts +40 -0
  54. package/src/ingress/health.ts +22 -0
  55. package/src/ingress/index.ts +3 -1
  56. package/src/ingress/native-route.ts +25 -6
  57. package/src/ingress/options.ts +26 -2
  58. package/src/ingress/packing/gather-raw-headers.ts +24 -8
  59. package/src/ingress/response/baked-response.ts +118 -28
  60. package/src/ingress/router.ts +50 -3
  61. package/src/ingress/routes/common.ts +35 -11
  62. package/src/ingress/routes/echo.ts +60 -7
  63. package/src/ingress/routes/json-write.ts +32 -8
  64. package/src/ingress/routes/native.ts +24 -5
  65. package/src/ingress/routes/options.ts +6 -2
  66. package/src/ingress/routes/responder.ts +29 -9
  67. package/src/ingress/routes/safe-record.ts +57 -0
  68. package/src/ingress/server-error.ts +111 -0
  69. package/src/ingress/server-node.ts +135 -37
  70. package/src/ingress/server.ts +143 -36
  71. package/src/ingress/shared.ts +1 -1
  72. package/src/ingress/types.ts +15 -0
  73. package/src/integration/pipeline.ts +6 -0
  74. package/src/loader/ops.ts +1 -1
  75. package/src/native/ffi/build/codecs-selftest.ts +305 -0
  76. package/src/native/ffi/build/codecs.ts +0 -296
  77. package/src/native/ffi/build/instances-selftest.ts +264 -0
  78. package/src/native/ffi/build/instances.ts +10 -259
  79. package/src/native/ffi/build/parse-selftest.ts +238 -0
  80. package/src/native/ffi/build/parse.ts +11 -234
  81. package/src/native/ffi/build/task.ts +2 -1
  82. package/src/native/ffi/build.ts +5 -1
  83. package/src/native/ffi/selftest.ts +3 -3
  84. package/src/native/ffi/symbols.ts +460 -0
  85. package/src/native/ffi/types.ts +6 -1
  86. package/src/native/ffi.ts +2 -434
  87. package/src/native/ingress-binding.ts +39 -0
  88. package/src/native/types.ts +6 -0
  89. package/src/runtime/codec.ts +24 -0
  90. package/src/runtime/detect.ts +14 -0
  91. package/src/runtime/types.ts +11 -0
  92. package/src/rust-ffi/batch/build.ts +18 -8
  93. package/src/rust-ffi/batch/types.ts +15 -0
  94. package/src/rust-ffi/context.ts +46 -3
  95. package/src/rust-ffi/options.ts +19 -0
  96. package/src/rust-ffi/scalar/crypto.ts +8 -0
  97. package/src/rust-ffi/scalar/factories.ts +53 -20
  98. package/src/rust-ffi/scalar/http.ts +60 -10
  99. package/src/rust-ffi/scalar/interface.ts +18 -1
  100. package/src/rust-ffi/scalar/payload.ts +27 -1
  101. package/src/rust-ffi/text.ts +5 -4
  102. package/src/shared/buffer-pool.ts +54 -9
  103. package/src/shared/codec.ts +26 -0
  104. package/src/shared/memory.ts +68 -0
  105. package/src/shared/metrics.ts +86 -3
  106. package/src/shared/packed/parsers.ts +64 -7
  107. package/src/shared/runtime.ts +7 -0
  108. package/src/task/runtime.ts +81 -37
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();
@@ -87,7 +94,8 @@ function createBunCodec() {
87
94
  },
88
95
  decodeUtf8: decodeUtf8Bun,
89
96
  decodeUtf8Fatal: decodeUtf8Bun,
90
- decodeUtf8Range: decodeUtf8RangeBun
97
+ decodeUtf8Range: decodeUtf8RangeBun,
98
+ decodeUtf8RangeView: decodeUtf8RangeViewBun
91
99
  };
92
100
  }
93
101
  function bunEncodeUtf8Into(s, dest, offset = 0) {
@@ -122,6 +130,14 @@ function decodeUtf8RangeBun(bytes, start, end) {
122
130
  }
123
131
  return view.toString("latin1", base + start, base + end);
124
132
  }
133
+ function decodeUtf8RangeViewBun(view, start, end) {
134
+ for (let i = start;i < end; i++) {
135
+ if ((view[i] ?? 0) >= 128) {
136
+ return view.toString("utf8", start, end);
137
+ }
138
+ }
139
+ return view.toString("latin1", start, end);
140
+ }
125
141
  function createNodeCodec() {
126
142
  return {
127
143
  encodeUtf8(s) {
@@ -144,6 +160,14 @@ function createNodeCodec() {
144
160
  }
145
161
  }
146
162
  return view.toString("latin1", start, end);
163
+ },
164
+ decodeUtf8RangeView(view, start, end) {
165
+ for (let i = start;i < end; i++) {
166
+ if ((view[i] ?? 0) >= 128) {
167
+ return view.toString("utf8", start, end);
168
+ }
169
+ }
170
+ return view.toString("latin1", start, end);
147
171
  }
148
172
  };
149
173
  }
@@ -264,37 +288,382 @@ var init_builtins = __esm(() => {
264
288
  runtimeBuiltins = createBuiltins(detectedRuntime);
265
289
  });
266
290
 
291
+ // src/native/ffi/symbols.ts
292
+ function buildFFISymbolMap(useBufferLength) {
293
+ const inputAbi = useBufferLength ? ["buffer", "buffer_length"] : ["ptr", "usize"];
294
+ const abi = (shape) => {
295
+ if (!useBufferLength)
296
+ return shape;
297
+ const out = [];
298
+ for (let i = 0;i < shape.length; i++) {
299
+ const t = shape[i];
300
+ if (t === "ptr") {
301
+ out.push("buffer");
302
+ out.push("buffer_length");
303
+ i++;
304
+ } else {
305
+ out.push(t);
306
+ }
307
+ }
308
+ return out;
309
+ };
310
+ const U64_FAST = "u64_fast";
311
+ return {
312
+ castrum_crc32: { args: inputAbi, returns: "u32" },
313
+ castrum_fnv1a64: { args: inputAbi, returns: "u64" },
314
+ castrum_xxh3: { args: inputAbi, returns: "u64" },
315
+ castrum_json_valid: { args: inputAbi, returns: "u8" },
316
+ castrum_utf8_valid: { args: inputAbi, returns: "u8" },
317
+ castrum_hex_encode: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
318
+ castrum_hex_decode: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
319
+ castrum_url_encode: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
320
+ castrum_url_decode: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
321
+ castrum_validate_email: { args: ["cstring"], returns: "u8" },
322
+ castrum_validate_uuid: { args: ["cstring"], returns: "u8" },
323
+ castrum_validate_ipv4: { args: ["cstring"], returns: "u8" },
324
+ castrum_validate_ipv6: { args: ["cstring"], returns: "u8" },
325
+ castrum_validate_email_bytes: { args: inputAbi, returns: "u8" },
326
+ castrum_validate_uuid_bytes: { args: inputAbi, returns: "u8" },
327
+ castrum_validate_ipv4_bytes: { args: inputAbi, returns: "u8" },
328
+ castrum_validate_ipv6_bytes: { args: inputAbi, returns: "u8" },
329
+ castrum_json_sum_ids: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
330
+ castrum_hmac_sha256_verify: {
331
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
332
+ returns: "u8"
333
+ },
334
+ castrum_csrf_verify: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: "u8" },
335
+ castrum_password_verify: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: "u8" },
336
+ castrum_password_verify_bcrypt: {
337
+ args: abi(["ptr", "usize", "cstring"]),
338
+ returns: "u8"
339
+ },
340
+ castrum_ws_accept_key: { args: ["cstring"], returns: "cstring" },
341
+ castrum_ws_accept_key_into: {
342
+ args: abi(["ptr", "usize", "ptr", "usize"]),
343
+ returns: U64_FAST
344
+ },
345
+ castrum_etag: { args: abi(["ptr", "usize", "u8"]), returns: "cstring" },
346
+ castrum_etag_into: { args: abi(["ptr", "usize", "u8", "ptr", "usize"]), returns: U64_FAST },
347
+ castrum_conditional_is_not_modified: {
348
+ args: abi(["usize", "ptr", "usize", "ptr", "usize", "u8"]),
349
+ returns: "u8"
350
+ },
351
+ castrum_media_type_matcher_matches: {
352
+ args: abi(["usize", "ptr", "usize"]),
353
+ returns: "u8"
354
+ },
355
+ castrum_accept_negotiator_negotiate: {
356
+ args: abi(["usize", "ptr", "usize"]),
357
+ returns: "cstring"
358
+ },
359
+ castrum_accept_negotiator_negotiate_server: {
360
+ args: ["usize", "cstring"],
361
+ returns: "cstring"
362
+ },
363
+ castrum_jwt_signer_sign: {
364
+ args: abi(["usize", "ptr", "usize", "i64", "ptr", "usize"]),
365
+ returns: U64_FAST
366
+ },
367
+ castrum_jwt_signer_verify: {
368
+ args: abi(["usize", "ptr", "usize", "i64", "ptr", "usize"]),
369
+ returns: U64_FAST
370
+ },
371
+ castrum_template_render: {
372
+ args: abi(["usize", "ptr", "usize", "ptr", "usize"]),
373
+ returns: U64_FAST
374
+ },
375
+ castrum_schema_validator_validate: {
376
+ args: abi(["usize", "ptr", "usize"]),
377
+ returns: "u8"
378
+ },
379
+ castrum_rate_limiter_check: {
380
+ args: abi(["usize", "cstring", "i64", "ptr", "usize"]),
381
+ returns: U64_FAST
382
+ },
383
+ castrum_rate_limiter_check_key: {
384
+ args: abi(["usize", "i64", "i64", "ptr", "usize"]),
385
+ returns: U64_FAST
386
+ },
387
+ castrum_random_token: { args: ["u32"], returns: "cstring" },
388
+ castrum_random_token_into: { args: ["u32", ...abi(["ptr", "usize"])], returns: U64_FAST },
389
+ castrum_base64_encode: {
390
+ args: abi(["ptr", "usize", "ptr", "usize", "u8", "u8"]),
391
+ returns: U64_FAST
392
+ },
393
+ castrum_base64_decode: {
394
+ args: abi(["ptr", "usize", "ptr", "usize", "u8", "u8"]),
395
+ returns: U64_FAST
396
+ },
397
+ castrum_hmac_sha256: {
398
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
399
+ returns: U64_FAST
400
+ },
401
+ castrum_sign_cookie: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: "cstring" },
402
+ castrum_sign_cookie_into: {
403
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
404
+ returns: U64_FAST
405
+ },
406
+ castrum_verify_cookie: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: "cstring" },
407
+ castrum_verify_cookie_into: {
408
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
409
+ returns: U64_FAST
410
+ },
411
+ castrum_csrf_token: { args: abi(["ptr", "usize"]), returns: "cstring" },
412
+ castrum_csrf_token_into: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
413
+ castrum_password_hash: {
414
+ args: abi(["ptr", "usize", "ptr", "usize", "u32", "u32", "u32", "u32", "ptr", "usize"]),
415
+ returns: U64_FAST
416
+ },
417
+ castrum_password_hash_bcrypt: {
418
+ args: abi(["ptr", "usize", "u32", "ptr", "usize"]),
419
+ returns: U64_FAST
420
+ },
421
+ castrum_pbkdf2_sha256: {
422
+ args: abi(["ptr", "usize", "ptr", "usize", "u32", "u32", "ptr", "usize"]),
423
+ returns: U64_FAST
424
+ },
425
+ castrum_aead_encrypt: {
426
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize", "u8", "ptr", "usize"]),
427
+ returns: U64_FAST
428
+ },
429
+ castrum_aead_decrypt: {
430
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize", "u8", "ptr", "usize"]),
431
+ returns: U64_FAST
432
+ },
433
+ castrum_ws_frame_encode: {
434
+ args: abi(["u8", "ptr", "usize", "u8", "u8", "ptr", "usize"]),
435
+ returns: U64_FAST
436
+ },
437
+ castrum_json_patch: {
438
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
439
+ returns: U64_FAST
440
+ },
441
+ castrum_gzip_compress: {
442
+ args: abi(["ptr", "usize", "u32", "ptr", "usize"]),
443
+ returns: U64_FAST
444
+ },
445
+ castrum_gzip_decompress: {
446
+ args: abi(["ptr", "usize", "usize", "ptr", "usize"]),
447
+ returns: U64_FAST
448
+ },
449
+ castrum_brotli_compress: {
450
+ args: abi(["ptr", "usize", "u32", "ptr", "usize"]),
451
+ returns: U64_FAST
452
+ },
453
+ castrum_brotli_decompress: {
454
+ args: abi(["ptr", "usize", "usize", "ptr", "usize"]),
455
+ returns: U64_FAST
456
+ },
457
+ castrum_gzip_isize: { args: abi(["ptr", "usize"]), returns: "u32" },
458
+ castrum_http_parse_request_packed: {
459
+ args: abi(["ptr", "usize", "ptr", "usize"]),
460
+ returns: U64_FAST
461
+ },
462
+ castrum_query_parse_packed: {
463
+ args: abi(["ptr", "usize", "ptr", "usize"]),
464
+ returns: U64_FAST
465
+ },
466
+ castrum_cookie_parse_packed: {
467
+ args: abi(["ptr", "usize", "ptr", "usize"]),
468
+ returns: U64_FAST
469
+ },
470
+ castrum_http_date_into: { args: ["f64", ...abi(["ptr", "usize"])], returns: U64_FAST },
471
+ castrum_sse_encode_into: {
472
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize", "u8", "u32", "ptr", "usize"]),
473
+ returns: U64_FAST
474
+ },
475
+ castrum_jwt_sign_bytes: {
476
+ args: abi(["ptr", "usize", "ptr", "usize", "i64", "i64"]),
477
+ returns: "cstring"
478
+ },
479
+ castrum_jwt_sign_bytes_into: {
480
+ args: abi(["ptr", "usize", "ptr", "usize", "i64", "i64", "ptr", "usize"]),
481
+ returns: U64_FAST
482
+ },
483
+ castrum_ed25519_generate_keypair: {
484
+ args: abi(["ptr", "usize"]),
485
+ returns: U64_FAST
486
+ },
487
+ castrum_ed25519_sign: {
488
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
489
+ returns: U64_FAST
490
+ },
491
+ castrum_ed25519_verify: {
492
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
493
+ returns: "u32"
494
+ },
495
+ castrum_jwt_eddsa_sign: {
496
+ args: abi(["ptr", "usize", "ptr", "usize", "i64", "i64"]),
497
+ returns: "cstring"
498
+ },
499
+ castrum_jwt_eddsa_verify: {
500
+ args: abi(["ptr", "usize", "ptr", "usize", "i64"]),
501
+ returns: "cstring"
502
+ },
503
+ castrum_ws_frame_decode_packed: {
504
+ args: abi(["ptr", "usize", "ptr", "usize"]),
505
+ returns: U64_FAST
506
+ },
507
+ castrum_multipart_parse_packed: {
508
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
509
+ returns: U64_FAST
510
+ },
511
+ castrum_form_parse_packed: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
512
+ castrum_json_parse_packed: {
513
+ args: abi(["ptr", "usize", "ptr", "usize"]),
514
+ returns: U64_FAST
515
+ },
516
+ castrum_parse_media_type: {
517
+ args: abi(["ptr", "usize", "ptr", "usize"]),
518
+ returns: U64_FAST
519
+ },
520
+ castrum_parse_http_date: {
521
+ args: abi(["ptr", "usize", "ptr", "usize"]),
522
+ returns: U64_FAST
523
+ },
524
+ castrum_parse_accept_encoding: {
525
+ args: abi(["ptr", "usize", "ptr", "usize"]),
526
+ returns: U64_FAST
527
+ },
528
+ castrum_url_encode_query: { args: abi(["ptr", "usize"]), returns: "cstring" },
529
+ castrum_url_resolve: {
530
+ args: abi(["ptr", "usize", "ptr", "usize"]),
531
+ returns: "cstring"
532
+ },
533
+ castrum_url_builder_resolve: {
534
+ args: abi(["usize", "ptr", "usize", "ptr", "usize"]),
535
+ returns: U64_FAST
536
+ },
537
+ castrum_mime_from_extension: { args: ["cstring"], returns: "cstring" },
538
+ castrum_jwt_verify: {
539
+ args: abi(["ptr", "usize", "ptr", "usize", "i64"]),
540
+ returns: "cstring"
541
+ },
542
+ castrum_ingress_handle_packed: {
543
+ args: abi(["usize", "ptr", "usize", "ptr", "usize", "ptr", "usize"]),
544
+ returns: U64_FAST
545
+ },
546
+ castrum_ingress_handle_components: {
547
+ args: abi([
548
+ "usize",
549
+ "u8",
550
+ "cstring",
551
+ "cstring",
552
+ "ptr",
553
+ "usize",
554
+ "ptr",
555
+ "usize",
556
+ "ptr",
557
+ "usize",
558
+ "ptr",
559
+ "usize"
560
+ ]),
561
+ returns: U64_FAST
562
+ },
563
+ castrum_ingress_layout: { args: abi(["ptr", "usize"]), returns: U64_FAST },
564
+ castrum_route_compile: { args: abi(["ptr", "usize"]), returns: U64_FAST },
565
+ castrum_route_run: {
566
+ args: abi(["usize", "ptr", "usize", "ptr", "usize"]),
567
+ returns: U64_FAST
568
+ },
569
+ castrum_route_destroy: { args: ["usize"], returns: "void" },
570
+ castrum_hex_validate_batch: {
571
+ args: abi(["ptr", "usize", "u32", "ptr", "usize"]),
572
+ returns: U64_FAST
573
+ },
574
+ castrum_regex_escape: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
575
+ castrum_metrics_create: { args: [], returns: U64_FAST },
576
+ castrum_metrics_counter: { args: ["usize", "cstring", "cstring"], returns: "u32" },
577
+ castrum_metrics_gauge: { args: ["usize", "cstring", "cstring"], returns: "u32" },
578
+ castrum_metrics_histogram: {
579
+ args: ["usize", "cstring", "cstring", "cstring"],
580
+ returns: "u32"
581
+ },
582
+ castrum_metrics_record: {
583
+ args: abi(["usize", "u32", "ptr", "usize", "f64"]),
584
+ returns: "u8"
585
+ },
586
+ castrum_metrics_gauge_set: {
587
+ args: abi(["usize", "u32", "ptr", "usize", "f64"]),
588
+ returns: "u8"
589
+ },
590
+ castrum_metrics_record_str: {
591
+ args: ["usize", "u32", "cstring", "f64"],
592
+ returns: "u8"
593
+ },
594
+ castrum_metrics_gauge_set_str: {
595
+ args: ["usize", "u32", "cstring", "f64"],
596
+ returns: "u8"
597
+ },
598
+ castrum_regex_escape_str: { args: ["cstring"], returns: "cstring" },
599
+ castrum_hex_validate_batch_str: {
600
+ args: abi(["cstring", "u32", "ptr", "usize"]),
601
+ returns: U64_FAST
602
+ },
603
+ castrum_metrics_render: { args: abi(["usize", "ptr", "usize"]), returns: U64_FAST },
604
+ castrum_metrics_snapshot: { args: abi(["usize", "ptr", "usize"]), returns: U64_FAST },
605
+ castrum_metrics_record_batch: {
606
+ args: abi(["usize", "ptr", "usize"]),
607
+ returns: "u8"
608
+ },
609
+ castrum_query_validate: { args: ["usize", "cstring"], returns: "u8" },
610
+ castrum_cookie_validate: { args: ["usize", "cstring"], returns: "u8" },
611
+ castrum_session_seal: {
612
+ args: ["cstring", "cstring", "i64", "cstring"],
613
+ returns: "cstring"
614
+ },
615
+ castrum_session_open: {
616
+ args: abi(["cstring", "cstring", "ptr", "usize"]),
617
+ returns: U64_FAST
618
+ },
619
+ castrum_session_seal_bytes: {
620
+ args: abi(["ptr", "usize", "ptr", "usize", "i64", "ptr", "usize"]),
621
+ returns: "cstring"
622
+ },
623
+ castrum_session_open_bytes: {
624
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
625
+ returns: U64_FAST
626
+ },
627
+ castrum_task_init: { args: ["u32"], returns: "u32" },
628
+ castrum_task_submit: { args: abi(["u32", "ptr", "usize", "usize"]), returns: "u32" },
629
+ castrum_task_submit_slice: {
630
+ args: abi(["u32", "ptr", "usize", "ptr", "usize", "usize"]),
631
+ returns: "u32"
632
+ },
633
+ castrum_task_submit_out: {
634
+ args: abi(["u32", "ptr", "usize", "usize", "ptr", "usize"]),
635
+ returns: "u32"
636
+ },
637
+ castrum_task_drain: { args: abi(["ptr", "usize"]), returns: U64_FAST },
638
+ castrum_task_pending: { args: [], returns: "u32" },
639
+ castrum_task_cancel: { args: ["usize"], returns: "u32" },
640
+ castrum_task_set_doorbell: { args: ["ptr"], returns: "u32" },
641
+ castrum_task_shutdown: { args: [], returns: "u32" },
642
+ castrum_task_threads: { args: [], returns: "u32" },
643
+ castrum_metrics_destroy: { args: ["usize"], returns: "void" }
644
+ };
645
+ }
646
+
267
647
  // src/shared/runtime.ts
268
- var isBun;
648
+ var isBun, forceGc2;
269
649
  var init_runtime = __esm(() => {
270
650
  init_detect();
271
651
  isBun = isBunRuntime;
652
+ forceGc2 = forceGc;
272
653
  });
273
654
 
274
655
  // src/shared/codec.ts
275
- var encodeUtf8, encodeUtf8Into, decodeUtf8, decodeUtf8Range, decodeUtf8Fatal;
656
+ var encodeUtf8, encodeUtf8Into, decodeUtf8, decodeUtf8Range, decodeUtf8RangeView, decodeUtf8Fatal;
276
657
  var init_codec2 = __esm(() => {
277
658
  init_codec();
278
659
  encodeUtf8 = runtimeCodec.encodeUtf8;
279
660
  encodeUtf8Into = runtimeCodec.encodeUtf8Into;
280
661
  decodeUtf8 = runtimeCodec.decodeUtf8;
281
662
  decodeUtf8Range = runtimeCodec.decodeUtf8Range;
663
+ decodeUtf8RangeView = runtimeCodec.decodeUtf8RangeView ? runtimeCodec.decodeUtf8RangeView : (view, start, end) => decodeUtf8Range(view, start, end);
282
664
  decodeUtf8Fatal = runtimeCodec.decodeUtf8Fatal;
283
665
  });
284
666
 
285
- // src/native/ffi/constants.ts
286
- var MAX_DECOMPRESSED, MAX_JSON_PATCH_OUTPUT, COMPRESS_INITIAL_CAP, COMPRESS_MAX_CAP, COMPRESS_HEADROOM = 64, DECOMPRESS_GUESS_MULTIPLIER_GZIP = 16, DECOMPRESS_GUESS_MULTIPLIER_BROTLI = 32, DECOMPRESS_FALLBACK_CAP, DECOMPRESS_MIN_INITIAL = 1024, SELFTEST_HEX, SELFTEST_JSON, EMPTY_VIEW;
287
- var init_constants = __esm(() => {
288
- MAX_DECOMPRESSED = 64 * 1024 * 1024;
289
- MAX_JSON_PATCH_OUTPUT = 128 * 1024 * 1024;
290
- COMPRESS_INITIAL_CAP = 16 * 1024;
291
- COMPRESS_MAX_CAP = 1024 * 1024;
292
- DECOMPRESS_FALLBACK_CAP = 4 * 1024 * 1024;
293
- SELFTEST_HEX = new Uint8Array([104, 101, 108, 108, 111]);
294
- SELFTEST_JSON = new Uint8Array([123, 34, 97, 34, 58, 49, 125]);
295
- EMPTY_VIEW = new Uint8Array(0);
296
- });
297
-
298
667
  // src/shared/bytes.ts
299
668
  function toBytes(v) {
300
669
  return typeof v === "string" ? encodeUtf8(v) : v;
@@ -696,365 +1065,160 @@ function buildCodecs(sym, ctx) {
696
1065
  }
697
1066
  };
698
1067
  }
699
- function selfTestCodecs(b) {
1068
+ var init_codecs = __esm(() => {
1069
+ init_codec2();
1070
+ init_util();
1071
+ });
1072
+
1073
+ // src/native/ffi/constants.ts
1074
+ var MAX_DECOMPRESSED, MAX_JSON_PATCH_OUTPUT, COMPRESS_INITIAL_CAP, COMPRESS_MAX_CAP, COMPRESS_HEADROOM = 64, DECOMPRESS_GUESS_MULTIPLIER_GZIP = 16, DECOMPRESS_GUESS_MULTIPLIER_BROTLI = 32, DECOMPRESS_FALLBACK_CAP, DECOMPRESS_MIN_INITIAL = 1024, SELFTEST_HEX, SELFTEST_JSON, EMPTY_VIEW;
1075
+ var init_constants = __esm(() => {
1076
+ MAX_DECOMPRESSED = 64 * 1024 * 1024;
1077
+ MAX_JSON_PATCH_OUTPUT = 128 * 1024 * 1024;
1078
+ COMPRESS_INITIAL_CAP = 16 * 1024;
1079
+ COMPRESS_MAX_CAP = 1024 * 1024;
1080
+ DECOMPRESS_FALLBACK_CAP = 4 * 1024 * 1024;
1081
+ SELFTEST_HEX = new Uint8Array([104, 101, 108, 108, 111]);
1082
+ SELFTEST_JSON = new Uint8Array([123, 34, 97, 34, 58, 49, 125]);
1083
+ EMPTY_VIEW = new Uint8Array(0);
1084
+ });
1085
+
1086
+ // src/native/ffi/build/compress.ts
1087
+ function buildCompress(sym, ctx) {
1088
+ const { lenOrView, scratchFor } = ctx;
1089
+ const gzipCompress = sym.castrum_gzip_compress;
1090
+ const gzipDecompress = sym.castrum_gzip_decompress;
1091
+ const brotliCompress = sym.castrum_brotli_compress;
1092
+ const brotliDecompress = sym.castrum_brotli_decompress;
1093
+ const gzipIsize = sym.castrum_gzip_isize;
1094
+ const jsonPatch = sym.castrum_json_patch;
1095
+ return {
1096
+ jsonPatch(doc, patch) {
1097
+ let out = scratchFor(Math.min(Math.max(doc.length, patch.length) + 16, 64 * 1024));
1098
+ let w = Number(jsonPatch(doc, lenOrView(doc), patch, lenOrView(patch), out, lenOrView(out)));
1099
+ if (w > out.length && w <= MAX_JSON_PATCH_OUTPUT) {
1100
+ out = new Uint8Array(w);
1101
+ w = Number(jsonPatch(doc, lenOrView(doc), patch, lenOrView(patch), out, lenOrView(out)));
1102
+ }
1103
+ if (w === 0 || w > out.length) {
1104
+ throw new Error("json patch: output buffer too small or patch inapplicable");
1105
+ }
1106
+ return decodeUtf8(out.subarray(0, w));
1107
+ },
1108
+ gzipCompress(data, level = 6) {
1109
+ return growExact((out) => Number(gzipCompress(data, lenOrView(data), Math.min(level, 9), out, lenOrView(out))), Math.min(data.length + COMPRESS_HEADROOM, COMPRESS_INITIAL_CAP), Math.max(data.length * 2 + COMPRESS_HEADROOM, COMPRESS_MAX_CAP), "gzip compress: output buffer too small");
1110
+ },
1111
+ gzipCompressInto(data, output, level = 6) {
1112
+ const w = Number(gzipCompress(data, lenOrView(data), Math.min(level, 9), output, lenOrView(output)));
1113
+ if (w === 0) {
1114
+ throw new Error("gzip compress: invalid input");
1115
+ }
1116
+ if (w > output.length) {
1117
+ throw new Error("gzip compress: output buffer too small");
1118
+ }
1119
+ return w;
1120
+ },
1121
+ gzipDecompress(data, maxDecompressed) {
1122
+ const max = maxDecompressed ?? MAX_DECOMPRESSED;
1123
+ const isize = Number(gzipIsize(data, lenOrView(data)));
1124
+ const initial = isize !== 0 ? Math.min(isize, max) : Math.min(data.length * DECOMPRESS_GUESS_MULTIPLIER_GZIP, DECOMPRESS_FALLBACK_CAP);
1125
+ return growExact((out) => Number(gzipDecompress(data, lenOrView(data), max, out, lenOrView(out))), initial, max, "gzip decompress: invalid stream or exceeded max decompressed size");
1126
+ },
1127
+ gzipDecompressInto(data, output, maxDecompressed) {
1128
+ const max = maxDecompressed ?? MAX_DECOMPRESSED;
1129
+ const w = Number(gzipDecompress(data, lenOrView(data), max, output, lenOrView(output)));
1130
+ if (w === 0) {
1131
+ throw new Error("gzip decompress: invalid stream or exceeded max decompressed size");
1132
+ }
1133
+ if (w > output.length) {
1134
+ throw new Error("gzip decompress: output buffer too small");
1135
+ }
1136
+ return w;
1137
+ },
1138
+ gzipIsize(data) {
1139
+ return Number(gzipIsize(data, lenOrView(data)));
1140
+ },
1141
+ brotliCompress(data, quality = 5) {
1142
+ return growExact((out) => Number(brotliCompress(data, lenOrView(data), Math.min(quality, 11), out, lenOrView(out))), Math.min(data.length + COMPRESS_HEADROOM, COMPRESS_INITIAL_CAP), Math.max(data.length * 2 + COMPRESS_HEADROOM, COMPRESS_MAX_CAP), "brotli compress: output buffer too small");
1143
+ },
1144
+ brotliCompressInto(data, output, quality = 5) {
1145
+ const w = Number(brotliCompress(data, lenOrView(data), Math.min(quality, 11), output, lenOrView(output)));
1146
+ if (w === 0) {
1147
+ throw new Error("brotli compress: invalid input");
1148
+ }
1149
+ if (w > output.length) {
1150
+ throw new Error("brotli compress: output buffer too small");
1151
+ }
1152
+ return w;
1153
+ },
1154
+ brotliDecompress(data, maxDecompressed) {
1155
+ const max = maxDecompressed ?? MAX_DECOMPRESSED;
1156
+ return growExact((out) => Number(brotliDecompress(data, lenOrView(data), max, out, lenOrView(out))), Math.min(Math.max(data.length * DECOMPRESS_GUESS_MULTIPLIER_BROTLI, DECOMPRESS_MIN_INITIAL), DECOMPRESS_FALLBACK_CAP), max, "brotli decompress: invalid stream or exceeded max decompressed size");
1157
+ },
1158
+ brotliDecompressInto(data, output, maxDecompressed) {
1159
+ const max = maxDecompressed ?? MAX_DECOMPRESSED;
1160
+ const w = Number(brotliDecompress(data, lenOrView(data), max, output, lenOrView(output)));
1161
+ if (w === 0) {
1162
+ throw new Error("brotli decompress: invalid stream or exceeded max decompressed size");
1163
+ }
1164
+ if (w > output.length) {
1165
+ throw new Error("brotli decompress: output buffer too small");
1166
+ }
1167
+ return w;
1168
+ }
1169
+ };
1170
+ }
1171
+ function selfTestCompress(b) {
700
1172
  const enc = { encode: encodeUtf8 };
701
1173
  const dec = { decode: decodeUtf8 };
702
- if (b.crc32(enc.encode("123456789")) !== 3421780262)
703
- return false;
704
- if (b.fnv1a64(enc.encode("foobar")) !== 0x85944171f73967e8n)
705
- return false;
706
- if (b.xxh3(new Uint8Array(0)) !== 0x2d06800538d394c2n)
707
- return false;
708
- if (b.jsonValid(SELFTEST_JSON) !== true || b.jsonValid(enc.encode("{not json")) !== false) {
709
- return false;
710
- }
711
- if (!b.utf8Valid(enc.encode("héllo")) || b.utf8Valid(new Uint8Array([255, 254]))) {
712
- return false;
713
- }
714
- const hexOut = new Uint8Array(SELFTEST_HEX.length * 2);
715
- if (b.hexEncodeInto(SELFTEST_HEX, hexOut) !== 10 || dec.decode(hexOut) !== "68656c6c6f") {
716
- return false;
717
- }
718
- const urlInput = enc.encode("a b/c");
719
- const urlOut = new Uint8Array(9);
720
- if (b.urlEncodeInto(urlInput, urlOut) !== 9 || dec.decode(urlOut) !== "a%20b%2Fc") {
1174
+ const patched = b.jsonPatch(enc.encode(`{"a":"b"}`), enc.encode(`[{"op":"add","path":"/c","value":"d"}]`));
1175
+ if (!patched.includes(`"c":"d"`)) {
721
1176
  return false;
722
1177
  }
723
- if (!b.validateEmail("a@b.com") || !b.validateUuid("550e8400-e29b-41d4-a716-446655440000") || !b.validateIpv4("192.168.0.1") || !b.validateIpv6("2001:db8::1") || b.validateEmail("not-an-email") || b.validateUuid("not-a-uuid")) {
1178
+ const gz = b.gzipCompress(SELFTEST_HEX);
1179
+ if (dec.decode(b.gzipDecompress(gz)) !== "hello") {
724
1180
  return false;
725
1181
  }
726
- if (!b.validateEmailBytes(enc.encode("a@b.com")) || !b.validateUuidBytes(enc.encode("550e8400-e29b-41d4-a716-446655440000")) || !b.validateIpv4Bytes(enc.encode("192.168.0.1")) || !b.validateIpv6Bytes(enc.encode("2001:db8::1")) || b.validateEmailBytes(enc.encode("not-an-email")) || b.validateIpv6Bytes(enc.encode("999"))) {
1182
+ const br = b.brotliCompress(SELFTEST_HEX);
1183
+ if (dec.decode(b.brotliDecompress(br)) !== "hello") {
727
1184
  return false;
728
1185
  }
729
- if (b.jsonSumIds(enc.encode(`[{"id":1},{"id":2}]`)) !== 3n)
730
- return false;
731
- if (b.jsonSumIds(enc.encode(`[{"id":0},{"id":0}]`)) !== 0n)
732
- return false;
733
- let sumInvalidThrew = false;
1186
+ let decompressThrew = false;
734
1187
  try {
735
- b.jsonSumIds(enc.encode("nope"));
1188
+ b.gzipDecompress(enc.encode("not-a-gzip-stream"));
736
1189
  } catch {
737
- sumInvalidThrew = true;
1190
+ decompressThrew = true;
738
1191
  }
739
- if (!sumInvalidThrew)
1192
+ if (!decompressThrew) {
740
1193
  return false;
741
- {
742
- const ids = enc.encode(`507f1f77bcf86cd799439011
743
- zz
744
- 507F1F77BCF86CD799439012`);
745
- const tiny = new Uint8Array(2);
746
- const needed = b.hexValidateBatchInto(ids, 24, tiny);
747
- if (needed !== 3)
748
- return false;
749
- const out = new Uint8Array(needed);
750
- if (b.hexValidateBatchInto(ids, 24, out) !== 3)
751
- return false;
752
- if (out[0] !== 1 || out[1] !== 0 || out[2] !== 1)
753
- return false;
754
- let widthThrew = false;
755
- try {
756
- b.hexValidateBatchInto(ids, 0, new Uint8Array(3));
757
- } catch {
758
- widthThrew = true;
759
- }
760
- if (!widthThrew)
761
- return false;
762
- }
763
- {
764
- const out = new Uint8Array(64);
765
- const w = b.regexEscapeInto(enc.encode("a.c(x)"), out);
766
- if (w !== 9 || dec.decode(out.subarray(0, w)) !== "a\\.c\\(x\\)")
767
- return false;
768
- const plain = new Uint8Array(16);
769
- const wp = b.regexEscapeInto(enc.encode("hello"), plain);
770
- if (wp !== 5 || dec.decode(plain.subarray(0, wp)) !== "hello")
771
- return false;
772
- if (b.regexEscapeInto(enc.encode("a.b"), new Uint8Array(2)) !== 4)
773
- return false;
774
- if (b.regexEscapeStr("a.c(x)") !== "a\\.c\\(x\\)")
775
- return false;
776
- if (b.regexEscapeStr("plain text") !== "plain text")
777
- return false;
778
- }
779
- {
780
- const out = new Uint8Array(8);
781
- const w = b.hexValidateBatchStr(`507f1f77bcf86cd799439011
782
- zz`, 24, out);
783
- if (w !== 2 || out[0] !== 1 || out[1] !== 0)
784
- return false;
785
1194
  }
786
- const hmacKey = new Uint8Array(20).fill(11);
787
- const hmacData = enc.encode("Hi There");
788
- const hmacSig = enc.encode("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7");
789
- if (b.hmacSha256(hmacKey, hmacData) !== "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7") {
790
- return false;
1195
+ decompressThrew = false;
1196
+ try {
1197
+ b.brotliDecompress(enc.encode("not-brotli-stream"));
1198
+ } catch {
1199
+ decompressThrew = true;
791
1200
  }
792
- if (!b.hmacSha256Verify(hmacKey, hmacData, hmacSig)) {
1201
+ if (!decompressThrew) {
793
1202
  return false;
794
1203
  }
795
- const decoded = b.hexDecode(enc.encode("68656c6c6f"));
796
- if (dec.decode(decoded) !== "hello" || dec.decode(b.urlDecode(enc.encode("a%20b%2Fc"))) !== "a b/c") {
1204
+ const gzInto = new Uint8Array(64);
1205
+ const gzW = b.gzipCompressInto(SELFTEST_HEX, gzInto);
1206
+ if (gzW === 0 || dec.decode(b.gzipDecompress(gzInto.subarray(0, gzW))) !== "hello") {
797
1207
  return false;
798
1208
  }
799
- if (b.wsAcceptKey("dGhlIHNhbXBsZSBub25jZQ==") !== "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") {
1209
+ const brInto = new Uint8Array(64);
1210
+ const brW = b.brotliCompressInto(SELFTEST_HEX, brInto);
1211
+ if (brW === 0 || dec.decode(b.brotliDecompress(brInto.subarray(0, brW))) !== "hello") {
800
1212
  return false;
801
1213
  }
802
- if (b.etag(SELFTEST_HEX).length !== 10 || b.etag(SELFTEST_HEX, true).length !== 12) {
1214
+ const gzDst = new Uint8Array(16);
1215
+ const gzDW = b.gzipDecompressInto(gzInto.subarray(0, gzW), gzDst);
1216
+ if (gzDW === 0 || dec.decode(gzDst.subarray(0, gzDW)) !== "hello") {
803
1217
  return false;
804
1218
  }
805
- if (b.base64Encode(SELFTEST_HEX) !== "aGVsbG8=") {
806
- return false;
807
- }
808
- if (dec.decode(b.base64Decode(enc.encode("aGVsbG8="))) !== "hello") {
809
- return false;
810
- }
811
- const secret = enc.encode("s3cr3t-secret");
812
- const signed = b.signCookie(SELFTEST_HEX, secret);
813
- const verified = b.verifyCookie(enc.encode(signed), secret);
814
- if (verified === null || verified !== "hello") {
815
- return false;
816
- }
817
- if (b.verifyCookie(enc.encode("tampered.0000"), secret) !== null) {
818
- return false;
819
- }
820
- const csrfTokenStr = b.csrfToken(secret);
821
- if (csrfTokenStr.length !== 129 || !b.csrfVerify(enc.encode(csrfTokenStr), secret)) {
822
- return false;
823
- }
824
- const pw = enc.encode("correct horse battery staple");
825
- const salt = enc.encode("salty-salt-16b");
826
- const phc = b.passwordHash(pw, salt, 8, 1, 1, 16);
827
- if (phc.length === 0 || !b.passwordVerify(pw, enc.encode(phc))) {
828
- return false;
829
- }
830
- const bcryptPhc = b.passwordHashBcrypt(pw, 4);
831
- if (bcryptPhc.length === 0 || !b.passwordVerifyBcrypt(pw, bcryptPhc)) {
832
- return false;
833
- }
834
- const dk = b.pbkdf2Sha256(enc.encode("password"), enc.encode("salt"), 1, 32);
835
- if (b.hexEncode(dk) !== "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b") {
836
- return false;
837
- }
838
- const aeadKey = new Uint8Array(32).fill(66);
839
- const nonce = new Uint8Array(12).fill(7);
840
- const ct = b.aeadEncrypt(aeadKey, nonce, SELFTEST_HEX, 0);
841
- const pt = b.aeadDecrypt(aeadKey, nonce, ct, 0);
842
- if (pt === null || dec.decode(pt) !== "hello") {
843
- return false;
844
- }
845
- const rtOut = new Uint8Array(32);
846
- const rtW = b.randomTokenInto(16, rtOut);
847
- if (rtW !== 32 || !/^[0-9a-f]{32}$/.test(dec.decode(rtOut))) {
848
- return false;
849
- }
850
- try {
851
- b.randomTokenInto(16, new Uint8Array(8));
852
- return false;
853
- } catch {}
854
- const waOut = new Uint8Array(28);
855
- const waW = b.wsAcceptKeyInto(enc.encode("dGhlIHNhbXBsZSBub25jZQ=="), waOut);
856
- const waExpected = b.wsAcceptKey("dGhlIHNhbXBsZSBub25jZQ==");
857
- if (waW !== 28 || dec.decode(waOut) !== waExpected) {
858
- return false;
859
- }
860
- const etagData = enc.encode("hello");
861
- const etagOut = new Uint8Array(16);
862
- const etagW = b.etagInto(etagData, etagOut);
863
- if (etagW !== 10 || dec.decode(etagOut.subarray(0, etagW)) !== b.etag(etagData)) {
864
- return false;
865
- }
866
- const csrfOut = new Uint8Array(129);
867
- const csrfW = b.csrfTokenInto(enc.encode("csrf-secret"), csrfOut);
868
- if (csrfW !== 129 || csrfOut[64] !== 46) {
869
- return false;
870
- }
871
- const ckVal = enc.encode("session-value");
872
- const ckSec = enc.encode("s3cr3t-secret");
873
- const ckOut = new Uint8Array(256);
874
- const ckW = b.signCookieInto(ckVal, ckSec, ckOut);
875
- if (ckW !== ckVal.length + 65) {
876
- return false;
877
- }
878
- const ckVerifyOut = new Uint8Array(256);
879
- const ckV = b.verifyCookieInto(ckOut.subarray(0, ckW), ckSec, ckVerifyOut);
880
- if (ckV !== ckVal.length || dec.decode(ckVerifyOut.subarray(0, ckV)) !== "session-value") {
881
- return false;
882
- }
883
- if (b.urlEncode(enc.encode("a b/c")) !== "a%20b%2Fc") {
884
- return false;
885
- }
886
- const hdInto = new Uint8Array(8);
887
- if (b.hexDecodeInto(enc.encode("68656c6c6f"), hdInto) !== 5 || dec.decode(hdInto.subarray(0, 5)) !== "hello") {
888
- return false;
889
- }
890
- const udInto = new Uint8Array(16);
891
- if (b.urlDecodeInto(enc.encode("a%20b%2Fc"), udInto) !== 5 || dec.decode(udInto.subarray(0, 5)) !== "a b/c") {
892
- return false;
893
- }
894
- const b64Into = new Uint8Array(16);
895
- if (b.base64EncodeInto(SELFTEST_HEX, b64Into) !== 8 || dec.decode(b64Into.subarray(0, 8)) !== "aGVsbG8=") {
896
- return false;
897
- }
898
- const bdInto = new Uint8Array(8);
899
- if (b.base64DecodeInto(enc.encode("aGVsbG8="), bdInto) !== 5 || dec.decode(bdInto.subarray(0, 5)) !== "hello") {
900
- return false;
901
- }
902
- if (!/^[0-9a-f]{32}$/.test(b.randomToken(16))) {
903
- return false;
904
- }
905
- const hmacInto = new Uint8Array(64);
906
- if (b.hmacSha256Into(hmacKey, hmacData, hmacInto) !== 64 || dec.decode(hmacInto) !== "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7") {
907
- return false;
908
- }
909
- const ctInto = new Uint8Array(64);
910
- const ctW = b.aeadEncryptInto(aeadKey, nonce, SELFTEST_HEX, ctInto, 0);
911
- if (ctW === 0 || b.aeadDecrypt(aeadKey, nonce, ctInto.subarray(0, ctW), 0) === null) {
912
- return false;
913
- }
914
- return true;
915
- }
916
- var init_codecs = __esm(() => {
917
- init_codec2();
918
- init_constants();
919
- init_util();
920
- });
921
-
922
- // src/native/ffi/build/compress.ts
923
- function buildCompress(sym, ctx) {
924
- const { lenOrView, scratchFor } = ctx;
925
- const gzipCompress = sym.castrum_gzip_compress;
926
- const gzipDecompress = sym.castrum_gzip_decompress;
927
- const brotliCompress = sym.castrum_brotli_compress;
928
- const brotliDecompress = sym.castrum_brotli_decompress;
929
- const gzipIsize = sym.castrum_gzip_isize;
930
- const jsonPatch = sym.castrum_json_patch;
931
- return {
932
- jsonPatch(doc, patch) {
933
- let out = scratchFor(Math.min(Math.max(doc.length, patch.length) + 16, 64 * 1024));
934
- let w = Number(jsonPatch(doc, lenOrView(doc), patch, lenOrView(patch), out, lenOrView(out)));
935
- if (w > out.length && w <= MAX_JSON_PATCH_OUTPUT) {
936
- out = new Uint8Array(w);
937
- w = Number(jsonPatch(doc, lenOrView(doc), patch, lenOrView(patch), out, lenOrView(out)));
938
- }
939
- if (w === 0 || w > out.length) {
940
- throw new Error("json patch: output buffer too small or patch inapplicable");
941
- }
942
- return decodeUtf8(out.subarray(0, w));
943
- },
944
- gzipCompress(data, level = 6) {
945
- return growExact((out) => Number(gzipCompress(data, lenOrView(data), Math.min(level, 9), out, lenOrView(out))), Math.min(data.length + COMPRESS_HEADROOM, COMPRESS_INITIAL_CAP), Math.max(data.length * 2 + COMPRESS_HEADROOM, COMPRESS_MAX_CAP), "gzip compress: output buffer too small");
946
- },
947
- gzipCompressInto(data, output, level = 6) {
948
- const w = Number(gzipCompress(data, lenOrView(data), Math.min(level, 9), output, lenOrView(output)));
949
- if (w === 0) {
950
- throw new Error("gzip compress: invalid input");
951
- }
952
- if (w > output.length) {
953
- throw new Error("gzip compress: output buffer too small");
954
- }
955
- return w;
956
- },
957
- gzipDecompress(data, maxDecompressed) {
958
- const max = maxDecompressed ?? MAX_DECOMPRESSED;
959
- const isize = Number(gzipIsize(data, lenOrView(data)));
960
- const initial = isize !== 0 ? Math.min(isize, max) : Math.min(data.length * DECOMPRESS_GUESS_MULTIPLIER_GZIP, DECOMPRESS_FALLBACK_CAP);
961
- return growExact((out) => Number(gzipDecompress(data, lenOrView(data), max, out, lenOrView(out))), initial, max, "gzip decompress: invalid stream or exceeded max decompressed size");
962
- },
963
- gzipDecompressInto(data, output, maxDecompressed) {
964
- const max = maxDecompressed ?? MAX_DECOMPRESSED;
965
- const w = Number(gzipDecompress(data, lenOrView(data), max, output, lenOrView(output)));
966
- if (w === 0) {
967
- throw new Error("gzip decompress: invalid stream or exceeded max decompressed size");
968
- }
969
- if (w > output.length) {
970
- throw new Error("gzip decompress: output buffer too small");
971
- }
972
- return w;
973
- },
974
- gzipIsize(data) {
975
- return Number(gzipIsize(data, lenOrView(data)));
976
- },
977
- brotliCompress(data, quality = 5) {
978
- return growExact((out) => Number(brotliCompress(data, lenOrView(data), Math.min(quality, 11), out, lenOrView(out))), Math.min(data.length + COMPRESS_HEADROOM, COMPRESS_INITIAL_CAP), Math.max(data.length * 2 + COMPRESS_HEADROOM, COMPRESS_MAX_CAP), "brotli compress: output buffer too small");
979
- },
980
- brotliCompressInto(data, output, quality = 5) {
981
- const w = Number(brotliCompress(data, lenOrView(data), Math.min(quality, 11), output, lenOrView(output)));
982
- if (w === 0) {
983
- throw new Error("brotli compress: invalid input");
984
- }
985
- if (w > output.length) {
986
- throw new Error("brotli compress: output buffer too small");
987
- }
988
- return w;
989
- },
990
- brotliDecompress(data, maxDecompressed) {
991
- const max = maxDecompressed ?? MAX_DECOMPRESSED;
992
- return growExact((out) => Number(brotliDecompress(data, lenOrView(data), max, out, lenOrView(out))), Math.min(Math.max(data.length * DECOMPRESS_GUESS_MULTIPLIER_BROTLI, DECOMPRESS_MIN_INITIAL), DECOMPRESS_FALLBACK_CAP), max, "brotli decompress: invalid stream or exceeded max decompressed size");
993
- },
994
- brotliDecompressInto(data, output, maxDecompressed) {
995
- const max = maxDecompressed ?? MAX_DECOMPRESSED;
996
- const w = Number(brotliDecompress(data, lenOrView(data), max, output, lenOrView(output)));
997
- if (w === 0) {
998
- throw new Error("brotli decompress: invalid stream or exceeded max decompressed size");
999
- }
1000
- if (w > output.length) {
1001
- throw new Error("brotli decompress: output buffer too small");
1002
- }
1003
- return w;
1004
- }
1005
- };
1006
- }
1007
- function selfTestCompress(b) {
1008
- const enc = { encode: encodeUtf8 };
1009
- const dec = { decode: decodeUtf8 };
1010
- const patched = b.jsonPatch(enc.encode(`{"a":"b"}`), enc.encode(`[{"op":"add","path":"/c","value":"d"}]`));
1011
- if (!patched.includes(`"c":"d"`)) {
1012
- return false;
1013
- }
1014
- const gz = b.gzipCompress(SELFTEST_HEX);
1015
- if (dec.decode(b.gzipDecompress(gz)) !== "hello") {
1016
- return false;
1017
- }
1018
- const br = b.brotliCompress(SELFTEST_HEX);
1019
- if (dec.decode(b.brotliDecompress(br)) !== "hello") {
1020
- return false;
1021
- }
1022
- let decompressThrew = false;
1023
- try {
1024
- b.gzipDecompress(enc.encode("not-a-gzip-stream"));
1025
- } catch {
1026
- decompressThrew = true;
1027
- }
1028
- if (!decompressThrew) {
1029
- return false;
1030
- }
1031
- decompressThrew = false;
1032
- try {
1033
- b.brotliDecompress(enc.encode("not-brotli-stream"));
1034
- } catch {
1035
- decompressThrew = true;
1036
- }
1037
- if (!decompressThrew) {
1038
- return false;
1039
- }
1040
- const gzInto = new Uint8Array(64);
1041
- const gzW = b.gzipCompressInto(SELFTEST_HEX, gzInto);
1042
- if (gzW === 0 || dec.decode(b.gzipDecompress(gzInto.subarray(0, gzW))) !== "hello") {
1043
- return false;
1044
- }
1045
- const brInto = new Uint8Array(64);
1046
- const brW = b.brotliCompressInto(SELFTEST_HEX, brInto);
1047
- if (brW === 0 || dec.decode(b.brotliDecompress(brInto.subarray(0, brW))) !== "hello") {
1048
- return false;
1049
- }
1050
- const gzDst = new Uint8Array(16);
1051
- const gzDW = b.gzipDecompressInto(gzInto.subarray(0, gzW), gzDst);
1052
- if (gzDW === 0 || dec.decode(gzDst.subarray(0, gzDW)) !== "hello") {
1053
- return false;
1054
- }
1055
- const brDst = new Uint8Array(16);
1056
- const brDW = b.brotliDecompressInto(brInto.subarray(0, brW), brDst);
1057
- if (brDW === 0 || dec.decode(brDst.subarray(0, brDW)) !== "hello") {
1219
+ const brDst = new Uint8Array(16);
1220
+ const brDW = b.brotliDecompressInto(brInto.subarray(0, brW), brDst);
1221
+ if (brDW === 0 || dec.decode(brDst.subarray(0, brDW)) !== "hello") {
1058
1222
  return false;
1059
1223
  }
1060
1224
  return true;
@@ -1113,7 +1277,10 @@ function buildInstances(sym, ctx) {
1113
1277
  return acceptNegotiatorNegotiateRaw(inner, header, lenOrView(header));
1114
1278
  },
1115
1279
  acceptNegotiatorNegotiateServer(inner, header) {
1116
- return acceptNegotiatorNegotiateServerRaw(inner, decodeUtf8(header));
1280
+ const text = decodeUtf8(header);
1281
+ if (hasNul(text))
1282
+ return null;
1283
+ return acceptNegotiatorNegotiateServerRaw(inner, text);
1117
1284
  },
1118
1285
  jwtSignerSign(inner, claimsJson, nowSeconds) {
1119
1286
  return growExact((out) => Number(jwtSignerSignRaw(inner, claimsJson, lenOrView(claimsJson), BigInt(nowSeconds), out, lenOrView(out))), Math.min(claimsJson.length + 128, 64 * 1024), 1024 * 1024, "jwt signer: invalid claims JSON or output buffer too small");
@@ -1160,6 +1327,8 @@ function buildInstances(sym, ctx) {
1160
1327
  return Number(sessionOpenBytesSym(token, lenOrView(token), secret, lenOrView(secret), output, lenOrView(output)));
1161
1328
  },
1162
1329
  rateLimiterCheck(inner, key, nowMs) {
1330
+ if (hasNul(key))
1331
+ return { allowed: false, remaining: 0, resetMs: 0 };
1163
1332
  const out = rateScratch;
1164
1333
  const w = Number(rateLimiterCheckRaw(inner, key, BigInt(Math.trunc(nowMs)), out, lenOrView(out)));
1165
1334
  if (w === 0)
@@ -1252,190 +1421,6 @@ function buildInstances(sym, ctx) {
1252
1421
  }
1253
1422
  };
1254
1423
  }
1255
- function selfTestInstances(b) {
1256
- const enc = { encode: encodeUtf8 };
1257
- const dec = { decode: decodeUtf8 };
1258
- const layoutBuf = new Uint8Array(38 * 4);
1259
- b.ingressLayout(layoutBuf);
1260
- const layoutView = new DataView(layoutBuf.buffer, layoutBuf.byteOffset, layoutBuf.byteLength);
1261
- if (layoutView.getUint32(0, true) !== 0 || layoutView.getUint32(2 * 4, true) !== 2 || layoutView.getUint32(12 * 4, true) !== 48 || layoutView.getUint32(13 * 4, true) !== 1 || layoutView.getUint32(28 * 4, true) !== 32 || layoutView.getUint32(37 * 4, true) !== 8) {
1262
- return false;
1263
- }
1264
- try {
1265
- b.ingressHandlePacked(0, enc.encode("/"), null, new Uint8Array(64));
1266
- return false;
1267
- } catch {}
1268
- try {
1269
- b.ingressHandleComponents(0, 0, "/", "", enc.encode("rid"), new Uint8Array(2), null, new Uint8Array(64));
1270
- return false;
1271
- } catch {}
1272
- const routeDesc = new Uint8Array(33);
1273
- const rd = new DataView(routeDesc.buffer);
1274
- rd.setUint32(0, 1380930900, true);
1275
- rd.setUint32(4, 3, true);
1276
- rd.setUint32(8, 2 * 1024 * 1024, true);
1277
- rd.setUint32(12, 8192, true);
1278
- rd.setUint32(16, 8192, true);
1279
- rd.setUint32(20, 0, true);
1280
- rd.setUint32(24, 1, true);
1281
- routeDesc[28] = 0;
1282
- rd.setUint32(29, 0, true);
1283
- const routeHandle = b.routeCompile(routeDesc);
1284
- if (routeHandle === 0) {
1285
- return false;
1286
- }
1287
- const routeFrame = new Uint8Array(15);
1288
- const rf = new DataView(routeFrame.buffer);
1289
- rf.setUint32(0, 0, true);
1290
- rf.setUint32(4, 3, true);
1291
- routeFrame.set(enc.encode("a=1"), 8);
1292
- rf.setUint32(11, 0, true);
1293
- const routeOut = new Uint8Array(64);
1294
- const routeW = b.routeRun(routeHandle, routeFrame, routeOut);
1295
- if (routeW <= 8) {
1296
- return false;
1297
- }
1298
- const rv = new DataView(routeOut.buffer);
1299
- const rFlags = rv.getUint32(0, true);
1300
- if ((rFlags & 1) === 0 || (rFlags & 4) === 0) {
1301
- return false;
1302
- }
1303
- b.routeDestroy(routeHandle);
1304
- if (b.conditionalIsNotModified(0, null, null) !== false) {
1305
- return false;
1306
- }
1307
- if (b.mediaTypeMatcherMatches(0, enc.encode("x")) !== false)
1308
- return false;
1309
- if (b.acceptNegotiatorNegotiate(0, enc.encode("gzip")) !== null)
1310
- return false;
1311
- if (b.acceptNegotiatorNegotiateServer(0, enc.encode("gzip")) !== null)
1312
- return false;
1313
- if (b.schemaValidatorValidate(0, enc.encode("{}")) !== false)
1314
- return false;
1315
- if (b.jwtSignerVerify(0, enc.encode("a.b.c"), 0) !== null)
1316
- return false;
1317
- let phase6Threw = false;
1318
- try {
1319
- b.jwtSignerSign(0, enc.encode("{}"), 0);
1320
- } catch {
1321
- phase6Threw = true;
1322
- }
1323
- if (!phase6Threw)
1324
- return false;
1325
- phase6Threw = false;
1326
- try {
1327
- b.templateRender(0, enc.encode("{}"));
1328
- } catch {
1329
- phase6Threw = true;
1330
- }
1331
- if (!phase6Threw)
1332
- return false;
1333
- phase6Threw = false;
1334
- try {
1335
- b.rateLimiterCheck(0, "k", 0);
1336
- } catch {
1337
- phase6Threw = true;
1338
- }
1339
- if (!phase6Threw)
1340
- return false;
1341
- const jwtClaims = enc.encode('{"sub":"user-1"}');
1342
- const jwtSecret2 = enc.encode("my-secret");
1343
- const jwtOut = new Uint8Array(512);
1344
- const jwtW = b.jwtSignBytesInto(jwtClaims, jwtSecret2, 0, 0, jwtOut);
1345
- if (jwtW === 0 || dec.decode(jwtOut.subarray(0, jwtW)).split(".").length !== 3) {
1346
- return false;
1347
- }
1348
- const jwtSecret = enc.encode("my-secret");
1349
- const jwt = b.jwtSignBytes(enc.encode('{"sub":"user-1"}'), jwtSecret, 0, 0);
1350
- const jwtStr = jwt;
1351
- const segs = jwtStr.split(".");
1352
- if (segs.length !== 3 || segs[0] === "" || segs[1] === "" || segs[2] === "") {
1353
- return false;
1354
- }
1355
- const signingInput = enc.encode(`${segs[0]}.${segs[1]}`);
1356
- const sigHex = b.hmacSha256(jwtSecret, signingInput);
1357
- const sigBytes = b.base64Decode(enc.encode(segs[2] ?? ""), true, false);
1358
- if (sigBytes === null || b.hexEncode(sigBytes) !== sigHex) {
1359
- return false;
1360
- }
1361
- const vjwtSecret = enc.encode("verify-secret");
1362
- const vjwt = b.jwtSignBytes(enc.encode('{"sub":"u-1"}'), vjwtSecret, 0, 0);
1363
- const vjwtClaims = b.jwtVerify(enc.encode(vjwt), vjwtSecret, 0);
1364
- if (vjwtClaims === null || !vjwtClaims.includes('"sub":"u-1"')) {
1365
- return false;
1366
- }
1367
- if (b.jwtVerify(enc.encode("tampered.token.value"), vjwtSecret, 0) !== null) {
1368
- return false;
1369
- }
1370
- const edKp = b.ed25519GenerateKeypair();
1371
- const edMsg = enc.encode("ed25519 bind-time self-test");
1372
- const edSig = b.ed25519Sign(edMsg, edKp.privateKey);
1373
- if (edSig.byteLength !== 64 || !b.ed25519Verify(edMsg, edSig, edKp.publicKey)) {
1374
- return false;
1375
- }
1376
- const edTampered = edSig.slice();
1377
- edTampered[0] = (edTampered[0] ?? 0) ^ 255;
1378
- if (b.ed25519Verify(edMsg, edTampered, edKp.publicKey)) {
1379
- return false;
1380
- }
1381
- const edToken = b.jwtEdDSASign(enc.encode('{"sub":"ffi-self-test"}'), edKp.privateKey, 0, 0);
1382
- if (edToken === null || edToken.split(".").length !== 3) {
1383
- return false;
1384
- }
1385
- const edClaims = b.jwtEdDSAVerify(enc.encode(edToken), edKp.publicKey, 0);
1386
- if (edClaims === null || !edClaims.includes('"sub":"ffi-self-test"')) {
1387
- return false;
1388
- }
1389
- if (b.jwtEdDSAVerify(enc.encode("tampered.token.value"), edKp.publicKey, 0) !== null) {
1390
- return false;
1391
- }
1392
- let urlBuilderThrew = false;
1393
- try {
1394
- b.urlBuilderResolve(0, enc.encode("g"));
1395
- } catch {
1396
- urlBuilderThrew = true;
1397
- }
1398
- if (!urlBuilderThrew) {
1399
- return false;
1400
- }
1401
- if (b.queryValidate(0, "route=/a") !== false)
1402
- return false;
1403
- if (b.cookieValidate(0, "route=/a") !== false)
1404
- return false;
1405
- {
1406
- const tok = b.sessionSeal("sess-9", '{"n":1}', 1234567, "sekrit");
1407
- if (tok === null || !tok.includes(".") || !tok.startsWith('{"id":"sess-9"'))
1408
- return false;
1409
- const out = new Uint8Array(256);
1410
- const w = b.sessionOpen(tok, "sekrit", out);
1411
- if (w <= 13 || out[0] !== 1)
1412
- return false;
1413
- if (b.sessionOpen(tok, "wrong", out) !== 0)
1414
- return false;
1415
- const enc = new TextEncoder;
1416
- const tokB = b.sessionSealBytes(enc.encode("sess-9"), enc.encode('{"n":1}'), 1234567, enc.encode("sekrit"));
1417
- if (tokB === null || tokB !== tok)
1418
- return false;
1419
- const outB = new Uint8Array(256);
1420
- const wB = b.sessionOpenBytes(enc.encode(tokB), enc.encode("sekrit"), outB);
1421
- if (wB !== w || outB[0] !== 1)
1422
- return false;
1423
- if (b.sessionOpenBytes(enc.encode(tokB), enc.encode("wrong"), outB) !== 0)
1424
- return false;
1425
- if (b.sessionOpenBytes(enc.encode(tokB), enc.encode("sekrit"), outB.subarray(0, 4)) !== wB)
1426
- return false;
1427
- }
1428
- let rlKeyThrew = false;
1429
- try {
1430
- b.rateLimiterCheckKey(0, 12345, 0);
1431
- } catch {
1432
- rlKeyThrew = true;
1433
- }
1434
- if (!rlKeyThrew) {
1435
- return false;
1436
- }
1437
- return true;
1438
- }
1439
1424
  var init_instances = __esm(() => {
1440
1425
  init_codec2();
1441
1426
  init_constants();
@@ -1634,123 +1619,14 @@ var init_metrics = __esm(() => {
1634
1619
  EMPTY = new Uint8Array(0);
1635
1620
  });
1636
1621
 
1637
- // src/rust-ffi/scalar/json-packed.ts
1638
- function fastDecodeUtf8(bytes) {
1639
- if (bytes.byteLength === 0)
1640
- return "";
1641
- if (bunFfiCodec2 === undefined) {
1642
- bunFfiCodec2 = null;
1643
- if (isBun()) {
1644
- try {
1645
- bunFfiCodec2 = __require("bun:ffi");
1646
- } catch {
1647
- bunFfiCodec2 = null;
1648
- }
1649
- }
1650
- }
1651
- if (bunFfiCodec2) {
1652
- return new bunFfiCodec2.CString(bunFfiCodec2.ptr(bytes), 0, bytes.byteLength).toString();
1653
- }
1654
- return decodeUtf8(bytes);
1655
- }
1656
- function decodeJsonPacked(bytes) {
1657
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1658
- let p = 0;
1659
- const u32 = () => {
1660
- const v = view.getUint32(p, true);
1661
- p += 4;
1662
- return v;
1663
- };
1664
- const strCount = u32();
1665
- const strings = new Array(strCount);
1666
- for (let i = 0;i < strCount; i++) {
1667
- const n = u32();
1668
- strings[i] = fastDecodeUtf8(bytes.subarray(p, p + n));
1669
- p += n;
1670
- }
1671
- const treeLen = u32();
1672
- const end = p + treeLen;
1673
- let root;
1674
- const frames = [];
1675
- const popFrame = () => {
1676
- const f = frames.pop();
1677
- if (f === undefined)
1678
- throw new Error("json parse: malformed packed stream");
1679
- return f;
1680
- };
1681
- const attach = (v) => {
1682
- const f = frames[frames.length - 1];
1683
- if (f === undefined) {
1684
- root = v;
1685
- return;
1686
- }
1687
- if (f.isObj)
1688
- f.obj[f.key] = v;
1689
- else
1690
- f.arr.push(v);
1691
- };
1692
- while (p < end) {
1693
- const t = view.getUint8(p);
1694
- p += 1;
1695
- switch (t) {
1696
- case TAG_NULL:
1697
- attach(null);
1698
- break;
1699
- case TAG_FALSE:
1700
- attach(false);
1701
- break;
1702
- case TAG_TRUE:
1703
- attach(true);
1704
- break;
1705
- case TAG_NUMBER: {
1706
- const v = view.getFloat64(p, true);
1707
- p += 8;
1708
- attach(v);
1709
- break;
1710
- }
1711
- case TAG_STRING:
1712
- attach(strings[u32()]);
1713
- break;
1714
- case TAG_ARRAY_START:
1715
- frames.push({ isObj: false, arr: [], obj: {}, key: "" });
1716
- break;
1717
- case TAG_OBJECT_START:
1718
- frames.push({ isObj: true, arr: [], obj: {}, key: "" });
1719
- break;
1720
- case TAG_ARRAY_END:
1721
- attach(popFrame().arr);
1722
- break;
1723
- case TAG_OBJECT_END:
1724
- attach(popFrame().obj);
1725
- break;
1726
- case TAG_KEY: {
1727
- const f = frames[frames.length - 1];
1728
- f.key = strings[u32()];
1729
- break;
1730
- }
1731
- default:
1732
- throw new Error("json parse: malformed packed token");
1733
- }
1734
- }
1735
- if (p !== end || frames.length !== 0) {
1736
- throw new Error("json parse: malformed packed stream");
1737
- }
1738
- return root;
1739
- }
1740
- var TAG_NULL = 0, TAG_FALSE = 1, TAG_TRUE = 2, TAG_NUMBER = 3, TAG_STRING = 4, TAG_ARRAY_START = 5, TAG_OBJECT_START = 6, TAG_ARRAY_END = 7, TAG_OBJECT_END = 8, TAG_KEY = 9, bunFfiCodec2;
1741
- var init_json_packed = __esm(() => {
1742
- init_codec2();
1743
- init_runtime();
1744
- });
1745
-
1746
- // src/native/ffi/build/parse.ts
1747
- function sseMemoEncode(slot, s) {
1748
- if (s === slot.str)
1749
- return slot.bytes;
1750
- const bytes = encodeUtf8(s);
1751
- slot.str = s;
1752
- slot.bytes = bytes;
1753
- return bytes;
1622
+ // src/native/ffi/build/parse.ts
1623
+ function sseMemoEncode(slot, s) {
1624
+ if (s === slot.str)
1625
+ return slot.bytes;
1626
+ const bytes = encodeUtf8(s);
1627
+ slot.str = s;
1628
+ slot.bytes = bytes;
1629
+ return bytes;
1754
1630
  }
1755
1631
  function buildParse(sym, ctx) {
1756
1632
  const { lenOrView, scratchFor, dateScratch, dateScratchView } = ctx;
@@ -1911,13 +1787,635 @@ function buildParse(sym, ctx) {
1911
1787
  if (w === 0) {
1912
1788
  throw new Error("sse encode: invalid UTF-8 in event/id");
1913
1789
  }
1914
- if (w > output.length) {
1915
- throw new Error("sse encode: output buffer too small");
1790
+ if (w > output.length) {
1791
+ throw new Error("sse encode: output buffer too small");
1792
+ }
1793
+ return w;
1794
+ }
1795
+ };
1796
+ }
1797
+ var sseMemoEvent, sseMemoId;
1798
+ var init_parse = __esm(() => {
1799
+ init_codec2();
1800
+ init_constants();
1801
+ init_util();
1802
+ sseMemoEvent = { str: null, bytes: EMPTY_VIEW };
1803
+ sseMemoId = { str: null, bytes: EMPTY_VIEW };
1804
+ });
1805
+
1806
+ // src/native/ffi/build/task.ts
1807
+ function buildTask(sym, ctx) {
1808
+ const { lenOrView } = ctx;
1809
+ const init = sym.castrum_task_init;
1810
+ const submit = sym.castrum_task_submit;
1811
+ const submitOut = sym.castrum_task_submit_out;
1812
+ const submitSlice = sym.castrum_task_submit_slice;
1813
+ const drain = sym.castrum_task_drain;
1814
+ const pending = sym.castrum_task_pending;
1815
+ const cancel = sym.castrum_task_cancel;
1816
+ const setDoorbell = sym.castrum_task_set_doorbell;
1817
+ const shutdown = sym.castrum_task_shutdown;
1818
+ const threads = sym.castrum_task_threads;
1819
+ return {
1820
+ taskInit: (n) => Number(init(n)),
1821
+ taskSubmit: (op, args, taskId) => Number(submit(op, args, lenOrView(args), taskId)),
1822
+ taskSubmitOut: (op, args, taskId, output) => Number(submitOut(op, args, lenOrView(args), taskId, output, lenOrView(output))),
1823
+ taskSubmitSlice: (op, hdr, data, taskId) => Number(submitSlice(op, hdr, lenOrView(hdr), data, lenOrView(data), taskId)),
1824
+ taskDrain: (output) => Number(drain(output, lenOrView(output))),
1825
+ taskPending: () => Number(pending()),
1826
+ taskCancel: (taskId) => Number(cancel(taskId)),
1827
+ taskSetDoorbell: (ptr) => Number(setDoorbell(ptr)),
1828
+ taskShutdown: () => Number(shutdown()),
1829
+ taskThreads: () => Number(threads())
1830
+ };
1831
+ }
1832
+ function selfTestTask(b) {
1833
+ try {
1834
+ if (typeof b.taskPending() !== "number")
1835
+ return false;
1836
+ if (typeof b.taskThreads() !== "number" || b.taskThreads() < 0)
1837
+ return false;
1838
+ if (b.taskSetDoorbell(0) !== 1)
1839
+ return false;
1840
+ if (typeof b.taskDrain(new Uint8Array(4)) !== "number")
1841
+ return false;
1842
+ if (typeof b.taskSubmitOut !== "function")
1843
+ return false;
1844
+ if (typeof b.taskSubmitSlice !== "function")
1845
+ return false;
1846
+ return true;
1847
+ } catch {
1848
+ return false;
1849
+ }
1850
+ }
1851
+
1852
+ // src/native/ffi/build.ts
1853
+ function build(sym, useBufferLength) {
1854
+ const oneArg = (raw, v) => useBufferLength ? raw(v, v) : raw(v, v.length);
1855
+ const lenOrView = (v) => useBufferLength ? v : v.length;
1856
+ let encodeScratch = new Uint8Array(0);
1857
+ const scratchFor = (size) => {
1858
+ if (encodeScratch.byteLength < size)
1859
+ encodeScratch = new Uint8Array(size);
1860
+ return encodeScratch;
1861
+ };
1862
+ const jsonSumOut = new Uint8Array(9);
1863
+ const jsonSumView = new DataView(jsonSumOut.buffer);
1864
+ const dateScratch = new Uint8Array(9);
1865
+ const dateScratchView = new DataView(dateScratch.buffer);
1866
+ const rateScratch = new Uint8Array(13);
1867
+ const rateScratchView = new DataView(rateScratch.buffer);
1868
+ const ctx = {
1869
+ useBufferLength,
1870
+ lenOrView,
1871
+ oneArg,
1872
+ scratchFor,
1873
+ jsonSumOut,
1874
+ jsonSumView,
1875
+ dateScratch,
1876
+ dateScratchView,
1877
+ rateScratch,
1878
+ rateScratchView
1879
+ };
1880
+ return {
1881
+ ...buildCodecs(sym, ctx),
1882
+ ...buildCompress(sym, ctx),
1883
+ ...buildParse(sym, ctx),
1884
+ ...buildInstances(sym, ctx),
1885
+ ...buildMetrics(sym, ctx),
1886
+ ...buildTask(sym, ctx)
1887
+ };
1888
+ }
1889
+ var init_build = __esm(() => {
1890
+ init_codecs();
1891
+ init_compress();
1892
+ init_instances();
1893
+ init_metrics();
1894
+ init_parse();
1895
+ });
1896
+
1897
+ // src/native/ffi/build/codecs-selftest.ts
1898
+ function selfTestCodecs(b) {
1899
+ const enc = { encode: encodeUtf8 };
1900
+ const dec = { decode: decodeUtf8 };
1901
+ if (b.crc32(enc.encode("123456789")) !== 3421780262)
1902
+ return false;
1903
+ if (b.fnv1a64(enc.encode("foobar")) !== 0x85944171f73967e8n)
1904
+ return false;
1905
+ if (b.xxh3(new Uint8Array(0)) !== 0x2d06800538d394c2n)
1906
+ return false;
1907
+ if (b.jsonValid(SELFTEST_JSON) !== true || b.jsonValid(enc.encode("{not json")) !== false) {
1908
+ return false;
1909
+ }
1910
+ if (!b.utf8Valid(enc.encode("héllo")) || b.utf8Valid(new Uint8Array([255, 254]))) {
1911
+ return false;
1912
+ }
1913
+ const hexOut = new Uint8Array(SELFTEST_HEX.length * 2);
1914
+ if (b.hexEncodeInto(SELFTEST_HEX, hexOut) !== 10 || dec.decode(hexOut) !== "68656c6c6f") {
1915
+ return false;
1916
+ }
1917
+ const urlInput = enc.encode("a b/c");
1918
+ const urlOut = new Uint8Array(9);
1919
+ if (b.urlEncodeInto(urlInput, urlOut) !== 9 || dec.decode(urlOut) !== "a%20b%2Fc") {
1920
+ return false;
1921
+ }
1922
+ if (!b.validateEmail("a@b.com") || !b.validateUuid("550e8400-e29b-41d4-a716-446655440000") || !b.validateIpv4("192.168.0.1") || !b.validateIpv6("2001:db8::1") || b.validateEmail("not-an-email") || b.validateUuid("not-a-uuid")) {
1923
+ return false;
1924
+ }
1925
+ if (!b.validateEmailBytes(enc.encode("a@b.com")) || !b.validateUuidBytes(enc.encode("550e8400-e29b-41d4-a716-446655440000")) || !b.validateIpv4Bytes(enc.encode("192.168.0.1")) || !b.validateIpv6Bytes(enc.encode("2001:db8::1")) || b.validateEmailBytes(enc.encode("not-an-email")) || b.validateIpv6Bytes(enc.encode("999"))) {
1926
+ return false;
1927
+ }
1928
+ if (b.jsonSumIds(enc.encode(`[{"id":1},{"id":2}]`)) !== 3n)
1929
+ return false;
1930
+ if (b.jsonSumIds(enc.encode(`[{"id":0},{"id":0}]`)) !== 0n)
1931
+ return false;
1932
+ let sumInvalidThrew = false;
1933
+ try {
1934
+ b.jsonSumIds(enc.encode("nope"));
1935
+ } catch {
1936
+ sumInvalidThrew = true;
1937
+ }
1938
+ if (!sumInvalidThrew)
1939
+ return false;
1940
+ {
1941
+ const ids = enc.encode(`507f1f77bcf86cd799439011
1942
+ zz
1943
+ 507F1F77BCF86CD799439012`);
1944
+ const tiny = new Uint8Array(2);
1945
+ const needed = b.hexValidateBatchInto(ids, 24, tiny);
1946
+ if (needed !== 3)
1947
+ return false;
1948
+ const out = new Uint8Array(needed);
1949
+ if (b.hexValidateBatchInto(ids, 24, out) !== 3)
1950
+ return false;
1951
+ if (out[0] !== 1 || out[1] !== 0 || out[2] !== 1)
1952
+ return false;
1953
+ let widthThrew = false;
1954
+ try {
1955
+ b.hexValidateBatchInto(ids, 0, new Uint8Array(3));
1956
+ } catch {
1957
+ widthThrew = true;
1958
+ }
1959
+ if (!widthThrew)
1960
+ return false;
1961
+ }
1962
+ {
1963
+ const out = new Uint8Array(64);
1964
+ const w = b.regexEscapeInto(enc.encode("a.c(x)"), out);
1965
+ if (w !== 9 || dec.decode(out.subarray(0, w)) !== "a\\.c\\(x\\)")
1966
+ return false;
1967
+ const plain = new Uint8Array(16);
1968
+ const wp = b.regexEscapeInto(enc.encode("hello"), plain);
1969
+ if (wp !== 5 || dec.decode(plain.subarray(0, wp)) !== "hello")
1970
+ return false;
1971
+ if (b.regexEscapeInto(enc.encode("a.b"), new Uint8Array(2)) !== 4)
1972
+ return false;
1973
+ if (b.regexEscapeStr("a.c(x)") !== "a\\.c\\(x\\)")
1974
+ return false;
1975
+ if (b.regexEscapeStr("plain text") !== "plain text")
1976
+ return false;
1977
+ }
1978
+ {
1979
+ const out = new Uint8Array(8);
1980
+ const w = b.hexValidateBatchStr(`507f1f77bcf86cd799439011
1981
+ zz`, 24, out);
1982
+ if (w !== 2 || out[0] !== 1 || out[1] !== 0)
1983
+ return false;
1984
+ }
1985
+ const hmacKey = new Uint8Array(20).fill(11);
1986
+ const hmacData = enc.encode("Hi There");
1987
+ const hmacSig = enc.encode("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7");
1988
+ if (b.hmacSha256(hmacKey, hmacData) !== "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7") {
1989
+ return false;
1990
+ }
1991
+ if (!b.hmacSha256Verify(hmacKey, hmacData, hmacSig)) {
1992
+ return false;
1993
+ }
1994
+ const decoded = b.hexDecode(enc.encode("68656c6c6f"));
1995
+ if (dec.decode(decoded) !== "hello" || dec.decode(b.urlDecode(enc.encode("a%20b%2Fc"))) !== "a b/c") {
1996
+ return false;
1997
+ }
1998
+ if (b.wsAcceptKey("dGhlIHNhbXBsZSBub25jZQ==") !== "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") {
1999
+ return false;
2000
+ }
2001
+ if (b.etag(SELFTEST_HEX).length !== 10 || b.etag(SELFTEST_HEX, true).length !== 12) {
2002
+ return false;
2003
+ }
2004
+ if (b.base64Encode(SELFTEST_HEX) !== "aGVsbG8=") {
2005
+ return false;
2006
+ }
2007
+ if (dec.decode(b.base64Decode(enc.encode("aGVsbG8="))) !== "hello") {
2008
+ return false;
2009
+ }
2010
+ const secret = enc.encode("s3cr3t-secret");
2011
+ const signed = b.signCookie(SELFTEST_HEX, secret);
2012
+ const verified = b.verifyCookie(enc.encode(signed), secret);
2013
+ if (verified === null || verified !== "hello") {
2014
+ return false;
2015
+ }
2016
+ if (b.verifyCookie(enc.encode("tampered.0000"), secret) !== null) {
2017
+ return false;
2018
+ }
2019
+ const csrfTokenStr = b.csrfToken(secret);
2020
+ if (csrfTokenStr.length !== 129 || !b.csrfVerify(enc.encode(csrfTokenStr), secret)) {
2021
+ return false;
2022
+ }
2023
+ const pw = enc.encode("correct horse battery staple");
2024
+ const salt = enc.encode("salty-salt-16b");
2025
+ const phc = b.passwordHash(pw, salt, 8, 1, 1, 16);
2026
+ if (phc.length === 0 || !b.passwordVerify(pw, enc.encode(phc))) {
2027
+ return false;
2028
+ }
2029
+ const bcryptPhc = b.passwordHashBcrypt(pw, 4);
2030
+ if (bcryptPhc.length === 0 || !b.passwordVerifyBcrypt(pw, bcryptPhc)) {
2031
+ return false;
2032
+ }
2033
+ const dk = b.pbkdf2Sha256(enc.encode("password"), enc.encode("salt"), 1, 32);
2034
+ if (b.hexEncode(dk) !== "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b") {
2035
+ return false;
2036
+ }
2037
+ const aeadKey = new Uint8Array(32).fill(66);
2038
+ const nonce = new Uint8Array(12).fill(7);
2039
+ const ct = b.aeadEncrypt(aeadKey, nonce, SELFTEST_HEX, 0);
2040
+ const pt = b.aeadDecrypt(aeadKey, nonce, ct, 0);
2041
+ if (pt === null || dec.decode(pt) !== "hello") {
2042
+ return false;
2043
+ }
2044
+ const rtOut = new Uint8Array(32);
2045
+ const rtW = b.randomTokenInto(16, rtOut);
2046
+ if (rtW !== 32 || !/^[0-9a-f]{32}$/.test(dec.decode(rtOut))) {
2047
+ return false;
2048
+ }
2049
+ try {
2050
+ b.randomTokenInto(16, new Uint8Array(8));
2051
+ return false;
2052
+ } catch {}
2053
+ const waOut = new Uint8Array(28);
2054
+ const waW = b.wsAcceptKeyInto(enc.encode("dGhlIHNhbXBsZSBub25jZQ=="), waOut);
2055
+ const waExpected = b.wsAcceptKey("dGhlIHNhbXBsZSBub25jZQ==");
2056
+ if (waW !== 28 || dec.decode(waOut) !== waExpected) {
2057
+ return false;
2058
+ }
2059
+ const etagData = enc.encode("hello");
2060
+ const etagOut = new Uint8Array(16);
2061
+ const etagW = b.etagInto(etagData, etagOut);
2062
+ if (etagW !== 10 || dec.decode(etagOut.subarray(0, etagW)) !== b.etag(etagData)) {
2063
+ return false;
2064
+ }
2065
+ const csrfOut = new Uint8Array(129);
2066
+ const csrfW = b.csrfTokenInto(enc.encode("csrf-secret"), csrfOut);
2067
+ if (csrfW !== 129 || csrfOut[64] !== 46) {
2068
+ return false;
2069
+ }
2070
+ const ckVal = enc.encode("session-value");
2071
+ const ckSec = enc.encode("s3cr3t-secret");
2072
+ const ckOut = new Uint8Array(256);
2073
+ const ckW = b.signCookieInto(ckVal, ckSec, ckOut);
2074
+ if (ckW !== ckVal.length + 65) {
2075
+ return false;
2076
+ }
2077
+ const ckVerifyOut = new Uint8Array(256);
2078
+ const ckV = b.verifyCookieInto(ckOut.subarray(0, ckW), ckSec, ckVerifyOut);
2079
+ if (ckV !== ckVal.length || dec.decode(ckVerifyOut.subarray(0, ckV)) !== "session-value") {
2080
+ return false;
2081
+ }
2082
+ if (b.urlEncode(enc.encode("a b/c")) !== "a%20b%2Fc") {
2083
+ return false;
2084
+ }
2085
+ const hdInto = new Uint8Array(8);
2086
+ if (b.hexDecodeInto(enc.encode("68656c6c6f"), hdInto) !== 5 || dec.decode(hdInto.subarray(0, 5)) !== "hello") {
2087
+ return false;
2088
+ }
2089
+ const udInto = new Uint8Array(16);
2090
+ if (b.urlDecodeInto(enc.encode("a%20b%2Fc"), udInto) !== 5 || dec.decode(udInto.subarray(0, 5)) !== "a b/c") {
2091
+ return false;
2092
+ }
2093
+ const b64Into = new Uint8Array(16);
2094
+ if (b.base64EncodeInto(SELFTEST_HEX, b64Into) !== 8 || dec.decode(b64Into.subarray(0, 8)) !== "aGVsbG8=") {
2095
+ return false;
2096
+ }
2097
+ const bdInto = new Uint8Array(8);
2098
+ if (b.base64DecodeInto(enc.encode("aGVsbG8="), bdInto) !== 5 || dec.decode(bdInto.subarray(0, 5)) !== "hello") {
2099
+ return false;
2100
+ }
2101
+ if (!/^[0-9a-f]{32}$/.test(b.randomToken(16))) {
2102
+ return false;
2103
+ }
2104
+ const hmacInto = new Uint8Array(64);
2105
+ if (b.hmacSha256Into(hmacKey, hmacData, hmacInto) !== 64 || dec.decode(hmacInto) !== "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7") {
2106
+ return false;
2107
+ }
2108
+ const ctInto = new Uint8Array(64);
2109
+ const ctW = b.aeadEncryptInto(aeadKey, nonce, SELFTEST_HEX, ctInto, 0);
2110
+ if (ctW === 0 || b.aeadDecrypt(aeadKey, nonce, ctInto.subarray(0, ctW), 0) === null) {
2111
+ return false;
2112
+ }
2113
+ return true;
2114
+ }
2115
+ var init_codecs_selftest = __esm(() => {
2116
+ init_codec2();
2117
+ init_constants();
2118
+ });
2119
+
2120
+ // src/native/ffi/build/instances-selftest.ts
2121
+ function selfTestInstances(b) {
2122
+ const enc = { encode: encodeUtf8 };
2123
+ const dec = { decode: decodeUtf8 };
2124
+ const layoutBuf = new Uint8Array(38 * 4);
2125
+ b.ingressLayout(layoutBuf);
2126
+ const layoutView = new DataView(layoutBuf.buffer, layoutBuf.byteOffset, layoutBuf.byteLength);
2127
+ if (layoutView.getUint32(0, true) !== 0 || layoutView.getUint32(2 * 4, true) !== 2 || layoutView.getUint32(12 * 4, true) !== 48 || layoutView.getUint32(13 * 4, true) !== 1 || layoutView.getUint32(28 * 4, true) !== 32 || layoutView.getUint32(37 * 4, true) !== 8) {
2128
+ return false;
2129
+ }
2130
+ try {
2131
+ b.ingressHandlePacked(0, enc.encode("/"), null, new Uint8Array(64));
2132
+ return false;
2133
+ } catch {}
2134
+ try {
2135
+ b.ingressHandleComponents(0, 0, "/", "", enc.encode("rid"), new Uint8Array(2), null, new Uint8Array(64));
2136
+ return false;
2137
+ } catch {}
2138
+ const routeDesc = new Uint8Array(33);
2139
+ const rd = new DataView(routeDesc.buffer);
2140
+ rd.setUint32(0, 1380930900, true);
2141
+ rd.setUint32(4, 3, true);
2142
+ rd.setUint32(8, 2 * 1024 * 1024, true);
2143
+ rd.setUint32(12, 8192, true);
2144
+ rd.setUint32(16, 8192, true);
2145
+ rd.setUint32(20, 0, true);
2146
+ rd.setUint32(24, 1, true);
2147
+ routeDesc[28] = 0;
2148
+ rd.setUint32(29, 0, true);
2149
+ const routeHandle = b.routeCompile(routeDesc);
2150
+ if (routeHandle === 0) {
2151
+ return false;
2152
+ }
2153
+ const routeFrame = new Uint8Array(15);
2154
+ const rf = new DataView(routeFrame.buffer);
2155
+ rf.setUint32(0, 0, true);
2156
+ rf.setUint32(4, 3, true);
2157
+ routeFrame.set(enc.encode("a=1"), 8);
2158
+ rf.setUint32(11, 0, true);
2159
+ const routeOut = new Uint8Array(64);
2160
+ const routeW = b.routeRun(routeHandle, routeFrame, routeOut);
2161
+ if (routeW <= 8) {
2162
+ return false;
2163
+ }
2164
+ const rv = new DataView(routeOut.buffer);
2165
+ const rFlags = rv.getUint32(0, true);
2166
+ if ((rFlags & 1) === 0 || (rFlags & 4) === 0) {
2167
+ return false;
2168
+ }
2169
+ b.routeDestroy(routeHandle);
2170
+ if (b.conditionalIsNotModified(0, null, null) !== false) {
2171
+ return false;
2172
+ }
2173
+ if (b.mediaTypeMatcherMatches(0, enc.encode("x")) !== false)
2174
+ return false;
2175
+ if (b.acceptNegotiatorNegotiate(0, enc.encode("gzip")) !== null)
2176
+ return false;
2177
+ if (b.acceptNegotiatorNegotiateServer(0, enc.encode("gzip")) !== null)
2178
+ return false;
2179
+ if (b.schemaValidatorValidate(0, enc.encode("{}")) !== false)
2180
+ return false;
2181
+ if (b.jwtSignerVerify(0, enc.encode("a.b.c"), 0) !== null)
2182
+ return false;
2183
+ let phase6Threw = false;
2184
+ try {
2185
+ b.jwtSignerSign(0, enc.encode("{}"), 0);
2186
+ } catch {
2187
+ phase6Threw = true;
2188
+ }
2189
+ if (!phase6Threw)
2190
+ return false;
2191
+ phase6Threw = false;
2192
+ try {
2193
+ b.templateRender(0, enc.encode("{}"));
2194
+ } catch {
2195
+ phase6Threw = true;
2196
+ }
2197
+ if (!phase6Threw)
2198
+ return false;
2199
+ phase6Threw = false;
2200
+ try {
2201
+ b.rateLimiterCheck(0, "k", 0);
2202
+ } catch {
2203
+ phase6Threw = true;
2204
+ }
2205
+ if (!phase6Threw)
2206
+ return false;
2207
+ const jwtClaims = enc.encode('{"sub":"user-1"}');
2208
+ const jwtSecret2 = enc.encode("my-secret");
2209
+ const jwtOut = new Uint8Array(512);
2210
+ const jwtW = b.jwtSignBytesInto(jwtClaims, jwtSecret2, 0, 0, jwtOut);
2211
+ if (jwtW === 0 || dec.decode(jwtOut.subarray(0, jwtW)).split(".").length !== 3) {
2212
+ return false;
2213
+ }
2214
+ const jwtSecret = enc.encode("my-secret");
2215
+ const jwt = b.jwtSignBytes(enc.encode('{"sub":"user-1"}'), jwtSecret, 0, 0);
2216
+ const jwtStr = jwt;
2217
+ const segs = jwtStr.split(".");
2218
+ if (segs.length !== 3 || segs[0] === "" || segs[1] === "" || segs[2] === "") {
2219
+ return false;
2220
+ }
2221
+ const signingInput = enc.encode(`${segs[0]}.${segs[1]}`);
2222
+ const sigHex = b.hmacSha256(jwtSecret, signingInput);
2223
+ const sigBytes = b.base64Decode(enc.encode(segs[2] ?? ""), true, false);
2224
+ if (sigBytes === null || b.hexEncode(sigBytes) !== sigHex) {
2225
+ return false;
2226
+ }
2227
+ const vjwtSecret = enc.encode("verify-secret");
2228
+ const vjwt = b.jwtSignBytes(enc.encode('{"sub":"u-1"}'), vjwtSecret, 0, 0);
2229
+ const vjwtClaims = b.jwtVerify(enc.encode(vjwt), vjwtSecret, 0);
2230
+ if (vjwtClaims === null || !vjwtClaims.includes('"sub":"u-1"')) {
2231
+ return false;
2232
+ }
2233
+ if (b.jwtVerify(enc.encode("tampered.token.value"), vjwtSecret, 0) !== null) {
2234
+ return false;
2235
+ }
2236
+ const edKp = b.ed25519GenerateKeypair();
2237
+ const edMsg = enc.encode("ed25519 bind-time self-test");
2238
+ const edSig = b.ed25519Sign(edMsg, edKp.privateKey);
2239
+ if (edSig.byteLength !== 64 || !b.ed25519Verify(edMsg, edSig, edKp.publicKey)) {
2240
+ return false;
2241
+ }
2242
+ const edTampered = edSig.slice();
2243
+ edTampered[0] = (edTampered[0] ?? 0) ^ 255;
2244
+ if (b.ed25519Verify(edMsg, edTampered, edKp.publicKey)) {
2245
+ return false;
2246
+ }
2247
+ const edToken = b.jwtEdDSASign(enc.encode('{"sub":"ffi-self-test"}'), edKp.privateKey, 0, 0);
2248
+ if (edToken === null || edToken.split(".").length !== 3) {
2249
+ return false;
2250
+ }
2251
+ const edClaims = b.jwtEdDSAVerify(enc.encode(edToken), edKp.publicKey, 0);
2252
+ if (edClaims === null || !edClaims.includes('"sub":"ffi-self-test"')) {
2253
+ return false;
2254
+ }
2255
+ if (b.jwtEdDSAVerify(enc.encode("tampered.token.value"), edKp.publicKey, 0) !== null) {
2256
+ return false;
2257
+ }
2258
+ let urlBuilderThrew = false;
2259
+ try {
2260
+ b.urlBuilderResolve(0, enc.encode("g"));
2261
+ } catch {
2262
+ urlBuilderThrew = true;
2263
+ }
2264
+ if (!urlBuilderThrew) {
2265
+ return false;
2266
+ }
2267
+ if (b.queryValidate(0, "route=/a") !== false)
2268
+ return false;
2269
+ if (b.cookieValidate(0, "route=/a") !== false)
2270
+ return false;
2271
+ {
2272
+ const tok = b.sessionSeal("sess-9", '{"n":1}', 1234567, "sekrit");
2273
+ if (tok === null || !tok.includes(".") || !tok.startsWith('{"id":"sess-9"'))
2274
+ return false;
2275
+ const out = new Uint8Array(256);
2276
+ const w = b.sessionOpen(tok, "sekrit", out);
2277
+ if (w <= 13 || out[0] !== 1)
2278
+ return false;
2279
+ if (b.sessionOpen(tok, "wrong", out) !== 0)
2280
+ return false;
2281
+ const enc = new TextEncoder;
2282
+ const tokB = b.sessionSealBytes(enc.encode("sess-9"), enc.encode('{"n":1}'), 1234567, enc.encode("sekrit"));
2283
+ if (tokB === null || tokB !== tok)
2284
+ return false;
2285
+ const outB = new Uint8Array(256);
2286
+ const wB = b.sessionOpenBytes(enc.encode(tokB), enc.encode("sekrit"), outB);
2287
+ if (wB !== w || outB[0] !== 1)
2288
+ return false;
2289
+ if (b.sessionOpenBytes(enc.encode(tokB), enc.encode("wrong"), outB) !== 0)
2290
+ return false;
2291
+ if (b.sessionOpenBytes(enc.encode(tokB), enc.encode("sekrit"), outB.subarray(0, 4)) !== wB)
2292
+ return false;
2293
+ }
2294
+ let rlKeyThrew = false;
2295
+ try {
2296
+ b.rateLimiterCheckKey(0, 12345, 0);
2297
+ } catch {
2298
+ rlKeyThrew = true;
2299
+ }
2300
+ if (!rlKeyThrew) {
2301
+ return false;
2302
+ }
2303
+ return true;
2304
+ }
2305
+ var init_instances_selftest = __esm(() => {
2306
+ init_codec2();
2307
+ });
2308
+
2309
+ // src/rust-ffi/scalar/json-packed.ts
2310
+ function fastDecodeUtf8(bytes) {
2311
+ if (bytes.byteLength === 0)
2312
+ return "";
2313
+ if (bunFfiCodec2 === undefined) {
2314
+ bunFfiCodec2 = null;
2315
+ if (isBun()) {
2316
+ try {
2317
+ bunFfiCodec2 = __require("bun:ffi");
2318
+ } catch {
2319
+ bunFfiCodec2 = null;
2320
+ }
2321
+ }
2322
+ }
2323
+ if (bunFfiCodec2) {
2324
+ return new bunFfiCodec2.CString(bunFfiCodec2.ptr(bytes), 0, bytes.byteLength).toString();
2325
+ }
2326
+ return decodeUtf8(bytes);
2327
+ }
2328
+ function decodeJsonPacked(bytes) {
2329
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
2330
+ let p = 0;
2331
+ const u32 = () => {
2332
+ const v = view.getUint32(p, true);
2333
+ p += 4;
2334
+ return v;
2335
+ };
2336
+ const strCount = u32();
2337
+ const strings = new Array(strCount);
2338
+ for (let i = 0;i < strCount; i++) {
2339
+ const n = u32();
2340
+ strings[i] = fastDecodeUtf8(bytes.subarray(p, p + n));
2341
+ p += n;
2342
+ }
2343
+ const treeLen = u32();
2344
+ const end = p + treeLen;
2345
+ let root;
2346
+ const frames = [];
2347
+ const popFrame = () => {
2348
+ const f = frames.pop();
2349
+ if (f === undefined)
2350
+ throw new Error("json parse: malformed packed stream");
2351
+ return f;
2352
+ };
2353
+ const attach = (v) => {
2354
+ const f = frames[frames.length - 1];
2355
+ if (f === undefined) {
2356
+ root = v;
2357
+ return;
2358
+ }
2359
+ if (f.isObj)
2360
+ f.obj[f.key] = v;
2361
+ else
2362
+ f.arr.push(v);
2363
+ };
2364
+ while (p < end) {
2365
+ const t = view.getUint8(p);
2366
+ p += 1;
2367
+ switch (t) {
2368
+ case TAG_NULL:
2369
+ attach(null);
2370
+ break;
2371
+ case TAG_FALSE:
2372
+ attach(false);
2373
+ break;
2374
+ case TAG_TRUE:
2375
+ attach(true);
2376
+ break;
2377
+ case TAG_NUMBER: {
2378
+ const v = view.getFloat64(p, true);
2379
+ p += 8;
2380
+ attach(v);
2381
+ break;
2382
+ }
2383
+ case TAG_STRING:
2384
+ attach(strings[u32()]);
2385
+ break;
2386
+ case TAG_ARRAY_START:
2387
+ frames.push({ isObj: false, arr: [], obj: {}, key: "" });
2388
+ break;
2389
+ case TAG_OBJECT_START:
2390
+ frames.push({ isObj: true, arr: [], obj: {}, key: "" });
2391
+ break;
2392
+ case TAG_ARRAY_END:
2393
+ attach(popFrame().arr);
2394
+ break;
2395
+ case TAG_OBJECT_END:
2396
+ attach(popFrame().obj);
2397
+ break;
2398
+ case TAG_KEY: {
2399
+ const f = frames[frames.length - 1];
2400
+ f.key = strings[u32()];
2401
+ break;
1916
2402
  }
1917
- return w;
2403
+ default:
2404
+ throw new Error("json parse: malformed packed token");
1918
2405
  }
1919
- };
2406
+ }
2407
+ if (p !== end || frames.length !== 0) {
2408
+ throw new Error("json parse: malformed packed stream");
2409
+ }
2410
+ return root;
1920
2411
  }
2412
+ var TAG_NULL = 0, TAG_FALSE = 1, TAG_TRUE = 2, TAG_NUMBER = 3, TAG_STRING = 4, TAG_ARRAY_START = 5, TAG_OBJECT_START = 6, TAG_ARRAY_END = 7, TAG_OBJECT_END = 8, TAG_KEY = 9, bunFfiCodec2;
2413
+ var init_json_packed = __esm(() => {
2414
+ init_codec2();
2415
+ init_runtime();
2416
+ });
2417
+
2418
+ // src/native/ffi/build/parse-selftest.ts
1921
2419
  function selfTestParse(b) {
1922
2420
  const enc = { encode: encodeUtf8 };
1923
2421
  const dec = { decode: decodeUtf8 };
@@ -2096,105 +2594,10 @@ data: hello
2096
2594
  }
2097
2595
  return true;
2098
2596
  }
2099
- var sseMemoEvent, sseMemoId;
2100
- var init_parse = __esm(() => {
2597
+ var init_parse_selftest = __esm(() => {
2101
2598
  init_json_packed();
2102
2599
  init_codec2();
2103
2600
  init_constants();
2104
- init_util();
2105
- sseMemoEvent = { str: null, bytes: EMPTY_VIEW };
2106
- sseMemoId = { str: null, bytes: EMPTY_VIEW };
2107
- });
2108
-
2109
- // src/native/ffi/build/task.ts
2110
- function buildTask(sym, ctx) {
2111
- const { lenOrView } = ctx;
2112
- const init = sym.castrum_task_init;
2113
- const submit = sym.castrum_task_submit;
2114
- const submitOut = sym.castrum_task_submit_out;
2115
- const submitSlice = sym.castrum_task_submit_slice;
2116
- const drain = sym.castrum_task_drain;
2117
- const pending = sym.castrum_task_pending;
2118
- const cancel = sym.castrum_task_cancel;
2119
- const setDoorbell = sym.castrum_task_set_doorbell;
2120
- const shutdown = sym.castrum_task_shutdown;
2121
- const threads = sym.castrum_task_threads;
2122
- return {
2123
- taskInit: (n) => Number(init(n)),
2124
- taskSubmit: (op, args, taskId) => Number(submit(op, args, lenOrView(args), taskId)),
2125
- taskSubmitOut: (op, args, taskId, output) => Number(submitOut(op, args, lenOrView(args), taskId, output, lenOrView(output))),
2126
- taskSubmitSlice: (op, hdr, data, taskId) => Number(submitSlice(op, hdr, lenOrView(hdr), data, lenOrView(data), taskId)),
2127
- taskDrain: (output) => Number(drain(output, lenOrView(output))),
2128
- taskPending: () => Number(pending()),
2129
- taskCancel: (taskId) => Number(cancel(taskId)),
2130
- taskSetDoorbell: (ptr) => Number(setDoorbell(ptr)),
2131
- taskShutdown: () => Number(shutdown()),
2132
- taskThreads: () => Number(threads())
2133
- };
2134
- }
2135
- function selfTestTask(b) {
2136
- try {
2137
- if (typeof b.taskPending() !== "number")
2138
- return false;
2139
- if (typeof b.taskThreads() !== "number" || b.taskThreads() < 0)
2140
- return false;
2141
- if (b.taskSetDoorbell(0) !== 1)
2142
- return false;
2143
- if (typeof b.taskDrain(new Uint8Array(4)) !== "number")
2144
- return false;
2145
- if (typeof b.taskSubmitOut !== "function")
2146
- return false;
2147
- if (typeof b.taskSubmitSlice !== "function")
2148
- return false;
2149
- return true;
2150
- } catch {
2151
- return false;
2152
- }
2153
- }
2154
-
2155
- // src/native/ffi/build.ts
2156
- function build(sym, useBufferLength) {
2157
- const oneArg = (raw, v) => useBufferLength ? raw(v, v) : raw(v, v.length);
2158
- const lenOrView = (v) => useBufferLength ? v : v.length;
2159
- let encodeScratch = new Uint8Array(0);
2160
- const scratchFor = (size) => {
2161
- if (encodeScratch.byteLength < size)
2162
- encodeScratch = new Uint8Array(size);
2163
- return encodeScratch;
2164
- };
2165
- const jsonSumOut = new Uint8Array(9);
2166
- const jsonSumView = new DataView(jsonSumOut.buffer);
2167
- const dateScratch = new Uint8Array(9);
2168
- const dateScratchView = new DataView(dateScratch.buffer);
2169
- const rateScratch = new Uint8Array(13);
2170
- const rateScratchView = new DataView(rateScratch.buffer);
2171
- const ctx = {
2172
- useBufferLength,
2173
- lenOrView,
2174
- oneArg,
2175
- scratchFor,
2176
- jsonSumOut,
2177
- jsonSumView,
2178
- dateScratch,
2179
- dateScratchView,
2180
- rateScratch,
2181
- rateScratchView
2182
- };
2183
- return {
2184
- ...buildCodecs(sym, ctx),
2185
- ...buildCompress(sym, ctx),
2186
- ...buildParse(sym, ctx),
2187
- ...buildInstances(sym, ctx),
2188
- ...buildMetrics(sym, ctx),
2189
- ...buildTask(sym, ctx)
2190
- };
2191
- }
2192
- var init_build = __esm(() => {
2193
- init_codecs();
2194
- init_compress();
2195
- init_instances();
2196
- init_metrics();
2197
- init_parse();
2198
2601
  });
2199
2602
 
2200
2603
  // src/native/ffi/selftest.ts
@@ -2202,11 +2605,11 @@ function selfTest(b) {
2202
2605
  return selfTestCodecs(b) && selfTestCompress(b) && selfTestParse(b) && selfTestInstances(b) && selfTestMetrics(b) && selfTestTask(b);
2203
2606
  }
2204
2607
  var init_selftest = __esm(() => {
2205
- init_codecs();
2608
+ init_codecs_selftest();
2206
2609
  init_compress();
2207
- init_instances();
2610
+ init_instances_selftest();
2208
2611
  init_metrics();
2209
- init_parse();
2612
+ init_parse_selftest();
2210
2613
  });
2211
2614
 
2212
2615
  // src/native/loader.ts
@@ -2366,6 +2769,12 @@ function getBunFFI() {
2366
2769
  cached = bind();
2367
2770
  return cached;
2368
2771
  }
2772
+ function ffiAddonPath() {
2773
+ if (cached === undefined) {
2774
+ getBunFFI();
2775
+ }
2776
+ return boundAddonPath;
2777
+ }
2369
2778
  function probeBufferLength(dlopen, path) {
2370
2779
  try {
2371
2780
  const { symbols, close } = dlopen(path, {
@@ -2396,361 +2805,10 @@ function bind(forcedPath, tried = []) {
2396
2805
  }
2397
2806
  const path = forcedPath ?? getAddonPath();
2398
2807
  try {
2399
- const { dlopen } = __require("bun:ffi");
2400
- const useBufferLength = probeBufferLength(dlopen, path);
2401
- bufferAbiMode = useBufferLength ? "buffer-pair" : "ptr-len";
2402
- const inputAbi = useBufferLength ? ["buffer", "buffer_length"] : ["ptr", "usize"];
2403
- const abi = (shape) => {
2404
- if (!useBufferLength)
2405
- return shape;
2406
- const out = [];
2407
- for (let i = 0;i < shape.length; i++) {
2408
- const t = shape[i];
2409
- if (t === "ptr") {
2410
- out.push("buffer");
2411
- out.push("buffer_length");
2412
- i++;
2413
- } else {
2414
- out.push(t);
2415
- }
2416
- }
2417
- return out;
2418
- };
2419
- const U64_FAST = "u64_fast";
2420
- const { symbols } = dlopen(path, {
2421
- castrum_crc32: { args: inputAbi, returns: "u32" },
2422
- castrum_fnv1a64: { args: inputAbi, returns: "u64" },
2423
- castrum_xxh3: { args: inputAbi, returns: "u64" },
2424
- castrum_json_valid: { args: inputAbi, returns: "u8" },
2425
- castrum_utf8_valid: { args: inputAbi, returns: "u8" },
2426
- castrum_hex_encode: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
2427
- castrum_hex_decode: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
2428
- castrum_url_encode: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
2429
- castrum_url_decode: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
2430
- castrum_validate_email: { args: ["cstring"], returns: "u8" },
2431
- castrum_validate_uuid: { args: ["cstring"], returns: "u8" },
2432
- castrum_validate_ipv4: { args: ["cstring"], returns: "u8" },
2433
- castrum_validate_ipv6: { args: ["cstring"], returns: "u8" },
2434
- castrum_validate_email_bytes: { args: inputAbi, returns: "u8" },
2435
- castrum_validate_uuid_bytes: { args: inputAbi, returns: "u8" },
2436
- castrum_validate_ipv4_bytes: { args: inputAbi, returns: "u8" },
2437
- castrum_validate_ipv6_bytes: { args: inputAbi, returns: "u8" },
2438
- castrum_json_sum_ids: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
2439
- castrum_hmac_sha256_verify: {
2440
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2441
- returns: "u8"
2442
- },
2443
- castrum_csrf_verify: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: "u8" },
2444
- castrum_password_verify: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: "u8" },
2445
- castrum_password_verify_bcrypt: {
2446
- args: abi(["ptr", "usize", "cstring"]),
2447
- returns: "u8"
2448
- },
2449
- castrum_ws_accept_key: { args: ["cstring"], returns: "cstring" },
2450
- castrum_ws_accept_key_into: {
2451
- args: abi(["ptr", "usize", "ptr", "usize"]),
2452
- returns: U64_FAST
2453
- },
2454
- castrum_etag: { args: abi(["ptr", "usize", "u8"]), returns: "cstring" },
2455
- castrum_etag_into: { args: abi(["ptr", "usize", "u8", "ptr", "usize"]), returns: U64_FAST },
2456
- castrum_conditional_is_not_modified: {
2457
- args: abi(["usize", "ptr", "usize", "ptr", "usize", "u8"]),
2458
- returns: "u8"
2459
- },
2460
- castrum_media_type_matcher_matches: {
2461
- args: abi(["usize", "ptr", "usize"]),
2462
- returns: "u8"
2463
- },
2464
- castrum_accept_negotiator_negotiate: {
2465
- args: abi(["usize", "ptr", "usize"]),
2466
- returns: "cstring"
2467
- },
2468
- castrum_accept_negotiator_negotiate_server: {
2469
- args: ["usize", "cstring"],
2470
- returns: "cstring"
2471
- },
2472
- castrum_jwt_signer_sign: {
2473
- args: abi(["usize", "ptr", "usize", "i64", "ptr", "usize"]),
2474
- returns: U64_FAST
2475
- },
2476
- castrum_jwt_signer_verify: {
2477
- args: abi(["usize", "ptr", "usize", "i64", "ptr", "usize"]),
2478
- returns: U64_FAST
2479
- },
2480
- castrum_template_render: {
2481
- args: abi(["usize", "ptr", "usize", "ptr", "usize"]),
2482
- returns: U64_FAST
2483
- },
2484
- castrum_schema_validator_validate: {
2485
- args: abi(["usize", "ptr", "usize"]),
2486
- returns: "u8"
2487
- },
2488
- castrum_rate_limiter_check: {
2489
- args: abi(["usize", "cstring", "i64", "ptr", "usize"]),
2490
- returns: U64_FAST
2491
- },
2492
- castrum_rate_limiter_check_key: {
2493
- args: abi(["usize", "i64", "i64", "ptr", "usize"]),
2494
- returns: U64_FAST
2495
- },
2496
- castrum_random_token: { args: ["u32"], returns: "cstring" },
2497
- castrum_random_token_into: { args: ["u32", ...abi(["ptr", "usize"])], returns: U64_FAST },
2498
- castrum_base64_encode: {
2499
- args: abi(["ptr", "usize", "ptr", "usize", "u8", "u8"]),
2500
- returns: U64_FAST
2501
- },
2502
- castrum_base64_decode: {
2503
- args: abi(["ptr", "usize", "ptr", "usize", "u8", "u8"]),
2504
- returns: U64_FAST
2505
- },
2506
- castrum_hmac_sha256: {
2507
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2508
- returns: U64_FAST
2509
- },
2510
- castrum_sign_cookie: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: "cstring" },
2511
- castrum_sign_cookie_into: {
2512
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2513
- returns: U64_FAST
2514
- },
2515
- castrum_verify_cookie: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: "cstring" },
2516
- castrum_verify_cookie_into: {
2517
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2518
- returns: U64_FAST
2519
- },
2520
- castrum_csrf_token: { args: abi(["ptr", "usize"]), returns: "cstring" },
2521
- castrum_csrf_token_into: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
2522
- castrum_password_hash: {
2523
- args: abi(["ptr", "usize", "ptr", "usize", "u32", "u32", "u32", "u32", "ptr", "usize"]),
2524
- returns: U64_FAST
2525
- },
2526
- castrum_password_hash_bcrypt: {
2527
- args: abi(["ptr", "usize", "u32", "ptr", "usize"]),
2528
- returns: U64_FAST
2529
- },
2530
- castrum_pbkdf2_sha256: {
2531
- args: abi(["ptr", "usize", "ptr", "usize", "u32", "u32", "ptr", "usize"]),
2532
- returns: U64_FAST
2533
- },
2534
- castrum_aead_encrypt: {
2535
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize", "u8", "ptr", "usize"]),
2536
- returns: U64_FAST
2537
- },
2538
- castrum_aead_decrypt: {
2539
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize", "u8", "ptr", "usize"]),
2540
- returns: U64_FAST
2541
- },
2542
- castrum_ws_frame_encode: {
2543
- args: abi(["u8", "ptr", "usize", "u8", "u8", "ptr", "usize"]),
2544
- returns: U64_FAST
2545
- },
2546
- castrum_json_patch: {
2547
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2548
- returns: U64_FAST
2549
- },
2550
- castrum_gzip_compress: {
2551
- args: abi(["ptr", "usize", "u32", "ptr", "usize"]),
2552
- returns: U64_FAST
2553
- },
2554
- castrum_gzip_decompress: {
2555
- args: abi(["ptr", "usize", "usize", "ptr", "usize"]),
2556
- returns: U64_FAST
2557
- },
2558
- castrum_brotli_compress: {
2559
- args: abi(["ptr", "usize", "u32", "ptr", "usize"]),
2560
- returns: U64_FAST
2561
- },
2562
- castrum_brotli_decompress: {
2563
- args: abi(["ptr", "usize", "usize", "ptr", "usize"]),
2564
- returns: U64_FAST
2565
- },
2566
- castrum_gzip_isize: { args: abi(["ptr", "usize"]), returns: "u32" },
2567
- castrum_http_parse_request_packed: {
2568
- args: abi(["ptr", "usize", "ptr", "usize"]),
2569
- returns: U64_FAST
2570
- },
2571
- castrum_query_parse_packed: {
2572
- args: abi(["ptr", "usize", "ptr", "usize"]),
2573
- returns: U64_FAST
2574
- },
2575
- castrum_cookie_parse_packed: {
2576
- args: abi(["ptr", "usize", "ptr", "usize"]),
2577
- returns: U64_FAST
2578
- },
2579
- castrum_http_date_into: { args: ["f64", ...abi(["ptr", "usize"])], returns: U64_FAST },
2580
- castrum_sse_encode_into: {
2581
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize", "u8", "u32", "ptr", "usize"]),
2582
- returns: U64_FAST
2583
- },
2584
- castrum_jwt_sign_bytes: {
2585
- args: abi(["ptr", "usize", "ptr", "usize", "i64", "i64"]),
2586
- returns: "cstring"
2587
- },
2588
- castrum_jwt_sign_bytes_into: {
2589
- args: abi(["ptr", "usize", "ptr", "usize", "i64", "i64", "ptr", "usize"]),
2590
- returns: U64_FAST
2591
- },
2592
- castrum_ed25519_generate_keypair: {
2593
- args: abi(["ptr", "usize"]),
2594
- returns: U64_FAST
2595
- },
2596
- castrum_ed25519_sign: {
2597
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2598
- returns: U64_FAST
2599
- },
2600
- castrum_ed25519_verify: {
2601
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2602
- returns: "u32"
2603
- },
2604
- castrum_jwt_eddsa_sign: {
2605
- args: abi(["ptr", "usize", "ptr", "usize", "i64", "i64"]),
2606
- returns: "cstring"
2607
- },
2608
- castrum_jwt_eddsa_verify: {
2609
- args: abi(["ptr", "usize", "ptr", "usize", "i64"]),
2610
- returns: "cstring"
2611
- },
2612
- castrum_ws_frame_decode_packed: {
2613
- args: abi(["ptr", "usize", "ptr", "usize"]),
2614
- returns: U64_FAST
2615
- },
2616
- castrum_multipart_parse_packed: {
2617
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2618
- returns: U64_FAST
2619
- },
2620
- castrum_form_parse_packed: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
2621
- castrum_json_parse_packed: {
2622
- args: abi(["ptr", "usize", "ptr", "usize"]),
2623
- returns: U64_FAST
2624
- },
2625
- castrum_parse_media_type: {
2626
- args: abi(["ptr", "usize", "ptr", "usize"]),
2627
- returns: U64_FAST
2628
- },
2629
- castrum_parse_http_date: {
2630
- args: abi(["ptr", "usize", "ptr", "usize"]),
2631
- returns: U64_FAST
2632
- },
2633
- castrum_parse_accept_encoding: {
2634
- args: abi(["ptr", "usize", "ptr", "usize"]),
2635
- returns: U64_FAST
2636
- },
2637
- castrum_url_encode_query: { args: abi(["ptr", "usize"]), returns: "cstring" },
2638
- castrum_url_resolve: {
2639
- args: abi(["ptr", "usize", "ptr", "usize"]),
2640
- returns: "cstring"
2641
- },
2642
- castrum_url_builder_resolve: {
2643
- args: abi(["usize", "ptr", "usize", "ptr", "usize"]),
2644
- returns: U64_FAST
2645
- },
2646
- castrum_mime_from_extension: { args: ["cstring"], returns: "cstring" },
2647
- castrum_jwt_verify: {
2648
- args: abi(["ptr", "usize", "ptr", "usize", "i64"]),
2649
- returns: "cstring"
2650
- },
2651
- castrum_ingress_handle_packed: {
2652
- args: abi(["usize", "ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2653
- returns: U64_FAST
2654
- },
2655
- castrum_ingress_handle_components: {
2656
- args: abi([
2657
- "usize",
2658
- "u8",
2659
- "cstring",
2660
- "cstring",
2661
- "ptr",
2662
- "usize",
2663
- "ptr",
2664
- "usize",
2665
- "ptr",
2666
- "usize",
2667
- "ptr",
2668
- "usize"
2669
- ]),
2670
- returns: U64_FAST
2671
- },
2672
- castrum_ingress_layout: { args: abi(["ptr", "usize"]), returns: U64_FAST },
2673
- castrum_route_compile: { args: abi(["ptr", "usize"]), returns: U64_FAST },
2674
- castrum_route_run: {
2675
- args: abi(["usize", "ptr", "usize", "ptr", "usize"]),
2676
- returns: U64_FAST
2677
- },
2678
- castrum_route_destroy: { args: ["usize"], returns: "void" },
2679
- castrum_hex_validate_batch: {
2680
- args: abi(["ptr", "usize", "u32", "ptr", "usize"]),
2681
- returns: U64_FAST
2682
- },
2683
- castrum_regex_escape: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
2684
- castrum_metrics_create: { args: [], returns: U64_FAST },
2685
- castrum_metrics_counter: { args: ["usize", "cstring", "cstring"], returns: "u32" },
2686
- castrum_metrics_gauge: { args: ["usize", "cstring", "cstring"], returns: "u32" },
2687
- castrum_metrics_histogram: {
2688
- args: ["usize", "cstring", "cstring", "cstring"],
2689
- returns: "u32"
2690
- },
2691
- castrum_metrics_record: {
2692
- args: abi(["usize", "u32", "ptr", "usize", "f64"]),
2693
- returns: "u8"
2694
- },
2695
- castrum_metrics_gauge_set: {
2696
- args: abi(["usize", "u32", "ptr", "usize", "f64"]),
2697
- returns: "u8"
2698
- },
2699
- castrum_metrics_record_str: {
2700
- args: ["usize", "u32", "cstring", "f64"],
2701
- returns: "u8"
2702
- },
2703
- castrum_metrics_gauge_set_str: {
2704
- args: ["usize", "u32", "cstring", "f64"],
2705
- returns: "u8"
2706
- },
2707
- castrum_regex_escape_str: { args: ["cstring"], returns: "cstring" },
2708
- castrum_hex_validate_batch_str: {
2709
- args: abi(["cstring", "u32", "ptr", "usize"]),
2710
- returns: U64_FAST
2711
- },
2712
- castrum_metrics_render: { args: abi(["usize", "ptr", "usize"]), returns: U64_FAST },
2713
- castrum_metrics_snapshot: { args: abi(["usize", "ptr", "usize"]), returns: U64_FAST },
2714
- castrum_metrics_record_batch: {
2715
- args: abi(["usize", "ptr", "usize"]),
2716
- returns: "u8"
2717
- },
2718
- castrum_query_validate: { args: ["usize", "cstring"], returns: "u8" },
2719
- castrum_cookie_validate: { args: ["usize", "cstring"], returns: "u8" },
2720
- castrum_session_seal: {
2721
- args: ["cstring", "cstring", "i64", "cstring"],
2722
- returns: "cstring"
2723
- },
2724
- castrum_session_open: {
2725
- args: abi(["cstring", "cstring", "ptr", "usize"]),
2726
- returns: U64_FAST
2727
- },
2728
- castrum_session_seal_bytes: {
2729
- args: abi(["ptr", "usize", "ptr", "usize", "i64", "ptr", "usize"]),
2730
- returns: "cstring"
2731
- },
2732
- castrum_session_open_bytes: {
2733
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2734
- returns: U64_FAST
2735
- },
2736
- castrum_task_init: { args: ["u32"], returns: "u32" },
2737
- castrum_task_submit: { args: abi(["u32", "ptr", "usize", "usize"]), returns: "u32" },
2738
- castrum_task_submit_slice: {
2739
- args: abi(["u32", "ptr", "usize", "ptr", "usize", "usize"]),
2740
- returns: "u32"
2741
- },
2742
- castrum_task_submit_out: {
2743
- args: abi(["u32", "ptr", "usize", "usize", "ptr", "usize"]),
2744
- returns: "u32"
2745
- },
2746
- castrum_task_drain: { args: abi(["ptr", "usize"]), returns: U64_FAST },
2747
- castrum_task_pending: { args: [], returns: "u32" },
2748
- castrum_task_cancel: { args: ["usize"], returns: "u32" },
2749
- castrum_task_set_doorbell: { args: ["ptr"], returns: "u32" },
2750
- castrum_task_shutdown: { args: [], returns: "u32" },
2751
- castrum_task_threads: { args: [], returns: "u32" },
2752
- castrum_metrics_destroy: { args: ["usize"], returns: "void" }
2753
- });
2808
+ const { dlopen } = __require("bun:ffi");
2809
+ const useBufferLength = probeBufferLength(dlopen, path);
2810
+ bufferAbiMode = useBufferLength ? "buffer-pair" : "ptr-len";
2811
+ const { symbols } = dlopen(path, buildFFISymbolMap(useBufferLength));
2754
2812
  const bindings = build(symbols, useBufferLength);
2755
2813
  if (!selfTest(bindings)) {
2756
2814
  throw new Error(`bun:ffi bind-time self-test failed on ${path}`);
@@ -3425,24 +3483,53 @@ var init_wire = __esm(() => {
3425
3483
  });
3426
3484
 
3427
3485
  // src/shared/packed/parsers.ts
3428
- function parseQueryString(query) {
3429
- const packed = addon2.queryParsePacked(encoder.encode(query));
3486
+ function scratchFor(kind, input) {
3487
+ const need = input.length * 9 + 16;
3488
+ let s = ffiCaches[kind];
3489
+ if (s === null || s.length < need) {
3490
+ s = new Uint8Array(need);
3491
+ ffiCaches[kind] = s;
3492
+ }
3493
+ return s;
3494
+ }
3495
+ function parsePackedInto(kind, input) {
3496
+ const f = getBunFFI();
3497
+ if (!f)
3498
+ return null;
3499
+ const out = scratchFor(kind, input);
3500
+ const w = kind === "query" ? f.queryParsePackedInto(input, out) : kind === "cookie" ? f.cookieParsePackedInto(input, out) : f.formParsePackedInto(input, out);
3501
+ if (w === 0 || w > out.length)
3502
+ return null;
3503
+ return pairsToObject(readPairsPacked(out.subarray(0, w)));
3504
+ }
3505
+ function parseVia(kind, input) {
3506
+ const fast = parsePackedInto(kind, input);
3507
+ if (fast)
3508
+ return fast;
3509
+ const packed = kind === "query" ? addon2.queryParsePacked(input) : kind === "cookie" ? addon2.cookieParsePacked(input) : addon2.formParsePacked(input);
3430
3510
  return pairsToObject(readPairsPacked(packed));
3431
3511
  }
3512
+ function parseQueryString(query) {
3513
+ return parseVia("query", encoder.encode(query));
3514
+ }
3432
3515
  function parseCookieHeader(header) {
3433
- const packed = addon2.cookieParsePacked(encoder.encode(header));
3434
- return pairsToObject(readPairsPacked(packed));
3516
+ return parseVia("cookie", encoder.encode(header));
3435
3517
  }
3436
3518
  function parseFormBody(body) {
3437
- const packed = addon2.formParsePacked(body);
3438
- return pairsToObject(readPairsPacked(packed));
3519
+ return parseVia("form", body);
3439
3520
  }
3440
- var addon2;
3521
+ var addon2, ffiCaches;
3441
3522
  var init_parsers = __esm(() => {
3523
+ init_ffi();
3442
3524
  init_native();
3443
3525
  init_bytes();
3444
3526
  init_wire();
3445
3527
  addon2 = lazyAddon(getAddon);
3528
+ ffiCaches = {
3529
+ query: null,
3530
+ cookie: null,
3531
+ form: null
3532
+ };
3446
3533
  });
3447
3534
 
3448
3535
  // src/shared/packed/schema.ts
@@ -3507,6 +3594,11 @@ function asNumber(value) {
3507
3594
  }
3508
3595
  return 0;
3509
3596
  }
3597
+ function assertNonEmptySecret(secret, op) {
3598
+ if (secret.byteLength === 0) {
3599
+ throw new Error(`${op}: secret must not be empty`);
3600
+ }
3601
+ }
3510
3602
  function normalizeExt(ext) {
3511
3603
  let s;
3512
3604
  try {
@@ -3525,6 +3617,24 @@ var init_options = __esm(() => {
3525
3617
  });
3526
3618
 
3527
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
+ }
3528
3638
  function resolveNative(ctx, name) {
3529
3639
  let f = nativeFnCache.get(name);
3530
3640
  if (f === undefined) {
@@ -3553,7 +3663,6 @@ function memoizeFfi(transport) {
3553
3663
  };
3554
3664
  }
3555
3665
  function createContext(options) {
3556
- const mimeByText = new Map;
3557
3666
  const hmacSigners = new WeakMap;
3558
3667
  const state = {
3559
3668
  mimeCache: options.mimeCache !== false,
@@ -3583,6 +3692,7 @@ function createContext(options) {
3583
3692
  const key = normalizeExt(ext);
3584
3693
  let val = mimeByText.get(key);
3585
3694
  if (!val) {
3695
+ evictOldestHalf(mimeByText);
3586
3696
  val = addon.mimeFromExtension(ext);
3587
3697
  mimeByText.set(key, val);
3588
3698
  }
@@ -3603,7 +3713,6 @@ function createContext(options) {
3603
3713
  addon,
3604
3714
  runtime: runtimeNative,
3605
3715
  state,
3606
- mimeByText,
3607
3716
  hmacSigners,
3608
3717
  ensurePool,
3609
3718
  isPoolInitialized,
@@ -3613,12 +3722,14 @@ function createContext(options) {
3613
3722
  hmacSigner
3614
3723
  };
3615
3724
  }
3616
- var nativeFnCache;
3725
+ var nativeFnCache, MIME_CACHE_MAX = 1024, mimeByText, mimeStrCache;
3617
3726
  var init_context = __esm(() => {
3618
3727
  init_native2();
3619
3728
  init_addon();
3620
3729
  init_options();
3621
3730
  nativeFnCache = new Map;
3731
+ mimeByText = new Map;
3732
+ mimeStrCache = new Map;
3622
3733
  });
3623
3734
 
3624
3735
  // src/rust-ffi/batch/build.ts
@@ -3674,17 +3785,29 @@ function buildBatch(ctx) {
3674
3785
  gzipDecompress: (items, maxDecompressed) => via(ctx, "gzipDecompressBatchPacked", unpackByteResults, [maxDecompressed ?? null])(items),
3675
3786
  brotliCompress: (items, quality) => via(ctx, "brotliCompressBatchPacked", unpackByteResults, [quality ?? null])(items),
3676
3787
  brotliDecompress: (items, maxDecompressed) => via(ctx, "brotliDecompressBatchPacked", unpackByteResults, [maxDecompressed ?? null])(items),
3677
- jwtVerify: (tokens, secret, nowSeconds) => via(ctx, "jwtVerifyBatchPacked", unpackBitset, [secret, nowSeconds])(tokens),
3678
- hmacSha256: (items, key) => via(ctx, "hmacSha256BatchPacked", unpackByteResults, [key])(items),
3679
- hmacSha256Verify: (items, sigs, key) => via2(ctx, "hmacSha256VerifyBatchPacked", unpackBitset, [key])(items, sigs),
3788
+ jwtVerify: (tokens, secret, nowSeconds) => {
3789
+ assertNonEmptySecret(secret, "batch.jwtVerify");
3790
+ return via(ctx, "jwtVerifyBatchPacked", unpackBitset, [secret, nowSeconds])(tokens);
3791
+ },
3792
+ hmacSha256: (items, key) => {
3793
+ assertNonEmptySecret(key, "batch.hmacSha256");
3794
+ return via(ctx, "hmacSha256BatchPacked", unpackByteResults, [key])(items);
3795
+ },
3796
+ hmacSha256Verify: (items, sigs, key) => {
3797
+ assertNonEmptySecret(key, "batch.hmacSha256Verify");
3798
+ return via2(ctx, "hmacSha256VerifyBatchPacked", unpackBitset, [key])(items, sigs);
3799
+ },
3680
3800
  passwordVerify: (items, phcs) => via2(ctx, "passwordVerifyBatchPacked", unpackBitset)(items, phcs),
3681
3801
  urlResolve: (bases, references) => via2(ctx, "urlResolveBatchPacked", unpackByteResults)(bases, references),
3682
3802
  templateRender: (source, contexts) => via(ctx, "templateRenderBatchPacked", unpackByteResults, [source])(contexts),
3683
- jwtSign: (items, secret, ttlSeconds, nowSeconds) => via(ctx, "jwtSignBatchPacked", unpackByteResults, [
3684
- secret,
3685
- ttlSeconds ?? null,
3686
- nowSeconds ?? Math.floor(Date.now() / 1000)
3687
- ])(items),
3803
+ jwtSign: (items, secret, ttlSeconds, nowSeconds) => {
3804
+ assertNonEmptySecret(secret, "batch.jwtSign");
3805
+ return via(ctx, "jwtSignBatchPacked", unpackByteResults, [
3806
+ secret,
3807
+ ttlSeconds ?? null,
3808
+ nowSeconds ?? Math.floor(Date.now() / 1000)
3809
+ ])(items);
3810
+ },
3688
3811
  sseEncode: (items, event, id, retry) => via(ctx, "sseEncodeBatchPacked", unpackByteResults, [
3689
3812
  event ?? null,
3690
3813
  id ?? null,
@@ -3698,6 +3821,7 @@ function buildBatch(ctx) {
3698
3821
  var init_build2 = __esm(() => {
3699
3822
  init_packed();
3700
3823
  init_context();
3824
+ init_options();
3701
3825
  });
3702
3826
 
3703
3827
  // src/rust-ffi/batch/index.ts
@@ -3959,18 +4083,22 @@ function buildCrypto(ctx) {
3959
4083
  return writeInto("verify cookie", output, value);
3960
4084
  },
3961
4085
  csrfToken(secret) {
4086
+ assertNonEmptySecret(secret, "csrfToken");
3962
4087
  return resolveNative(ctx, "csrfToken")(secret);
3963
4088
  },
3964
4089
  csrfTokenInto(secret, output) {
4090
+ assertNonEmptySecret(secret, "csrfTokenInto");
3965
4091
  const f = ffi();
3966
4092
  if (f)
3967
4093
  return f.csrfTokenInto(secret, output);
3968
4094
  return writeInto("csrf token", output, addon.csrfToken(secret));
3969
4095
  },
3970
4096
  csrfVerify(token, secret) {
4097
+ assertNonEmptySecret(secret, "csrfVerify");
3971
4098
  return resolveNative(ctx, "csrfVerify")(token, secret);
3972
4099
  },
3973
4100
  jwtSign(claims, secret, ttlSeconds, nowSeconds) {
4101
+ assertNonEmptySecret(secret, "jwtSign");
3974
4102
  const f = ffi();
3975
4103
  if (f) {
3976
4104
  const token = f.jwtSignBytes(encoder.encode(JSON.stringify(claims)), secret, ttlSeconds ?? 0, nowSeconds ?? Math.floor(Date.now() / 1000));
@@ -3979,6 +4107,7 @@ function buildCrypto(ctx) {
3979
4107
  return addon.jwtSign(claims, secret, ttlSeconds ?? null, nowSeconds ?? Math.floor(Date.now() / 1000));
3980
4108
  },
3981
4109
  jwtSignBytes(claimsJson, secret, ttlSeconds, nowSeconds) {
4110
+ assertNonEmptySecret(secret, "jwtSignBytes");
3982
4111
  const f = ffi();
3983
4112
  if (f) {
3984
4113
  return f.jwtSignBytes(claimsJson, secret, ttlSeconds ?? 0, nowSeconds ?? Math.floor(Date.now() / 1000));
@@ -3986,6 +4115,7 @@ function buildCrypto(ctx) {
3986
4115
  return addon.jwtSignBytes(claimsJson, secret, ttlSeconds ?? null, nowSeconds ?? Math.floor(Date.now() / 1000));
3987
4116
  },
3988
4117
  jwtSignBytesInto(claimsJson, secret, output, ttlSeconds, nowSeconds) {
4118
+ assertNonEmptySecret(secret, "jwtSignBytesInto");
3989
4119
  const f = ffi();
3990
4120
  if (f) {
3991
4121
  return f.jwtSignBytesInto(claimsJson, secret, ttlSeconds ?? 0, nowSeconds ?? Math.floor(Date.now() / 1000), output);
@@ -3993,6 +4123,7 @@ function buildCrypto(ctx) {
3993
4123
  return writeInto("jwt sign", output, addon.jwtSignBytes(claimsJson, secret, ttlSeconds ?? null, nowSeconds ?? Math.floor(Date.now() / 1000)));
3994
4124
  },
3995
4125
  jwtVerify(token, secret, nowSeconds) {
4126
+ assertNonEmptySecret(secret, "jwtVerify");
3996
4127
  const f = ffi();
3997
4128
  if (f) {
3998
4129
  const claims = f.jwtVerify(token, secret, nowSeconds ?? Math.floor(Date.now() / 1000));
@@ -4053,9 +4184,17 @@ function buildCrypto(ctx) {
4053
4184
  var init_crypto = __esm(() => {
4054
4185
  init_bytes();
4055
4186
  init_context();
4187
+ init_options();
4056
4188
  });
4057
4189
 
4058
4190
  // src/rust-ffi/scalar/factories.ts
4191
+ function handlePtr(inner) {
4192
+ const v = inner ?? 0n;
4193
+ if (v < 0n || v > BigInt(Number.MAX_SAFE_INTEGER)) {
4194
+ throw new Error("castrum: native handle exceeds Number.MAX_SAFE_INTEGER");
4195
+ }
4196
+ return Number(v);
4197
+ }
4059
4198
  function ffiHmacSigner(key, ffi) {
4060
4199
  return {
4061
4200
  sign(data) {
@@ -4236,47 +4375,55 @@ function ffiFormParser(ffi) {
4236
4375
  };
4237
4376
  }
4238
4377
  function ffiMetricsRegistry(ffi) {
4239
- const handle = ffi.metricsCreate();
4378
+ let handle = ffi.metricsCreate();
4379
+ const live = () => {
4380
+ if (handle === 0)
4381
+ throw new Error("metrics registry: already destroyed");
4382
+ return handle;
4383
+ };
4240
4384
  return {
4241
4385
  counter(name, labelKeys) {
4242
- return ffi.metricsCounter(handle, name, (labelKeys ?? []).join("\x1F"));
4386
+ return ffi.metricsCounter(live(), name, (labelKeys ?? []).join("\x1F"));
4243
4387
  },
4244
4388
  gauge(name, labelKeys) {
4245
- return ffi.metricsGauge(handle, name, (labelKeys ?? []).join("\x1F"));
4389
+ return ffi.metricsGauge(live(), name, (labelKeys ?? []).join("\x1F"));
4246
4390
  },
4247
4391
  histogram(name, labelKeys, buckets) {
4248
- return ffi.metricsHistogram(handle, name, (labelKeys ?? []).join("\x1F"), (buckets ?? []).join(","));
4392
+ return ffi.metricsHistogram(live(), name, (labelKeys ?? []).join("\x1F"), (buckets ?? []).join(","));
4249
4393
  },
4250
4394
  record(series, values, amount) {
4251
4395
  const joined = (values ?? []).join("\x1F");
4252
- if (!ffi.metricsRecordStr(handle, series, joined, amount ?? 1)) {
4396
+ if (!ffi.metricsRecordStr(live(), series, joined, amount ?? 1)) {
4253
4397
  throw new Error("metrics record: unknown series / arity mismatch / invalid amount");
4254
4398
  }
4255
4399
  },
4256
4400
  gaugeSet(series, values, value) {
4257
4401
  const joined = (values ?? []).join("\x1F");
4258
- if (!ffi.metricsGaugeSetStr(handle, series, joined, value)) {
4402
+ if (!ffi.metricsGaugeSetStr(live(), series, joined, value)) {
4259
4403
  throw new Error("metrics gauge set: unknown series / arity mismatch");
4260
4404
  }
4261
4405
  },
4262
4406
  render() {
4263
4407
  let out = new Uint8Array(8192);
4264
- let w = ffi.metricsRender(handle, out);
4408
+ let w = ffi.metricsRender(live(), out);
4265
4409
  if (w > out.length) {
4266
4410
  out = new Uint8Array(w);
4267
- w = ffi.metricsRender(handle, out);
4411
+ w = ffi.metricsRender(live(), out);
4268
4412
  }
4269
4413
  return decodeUtf8(out.subarray(0, w));
4270
4414
  },
4271
4415
  destroy() {
4272
- ffi.metricsDestroy(handle);
4416
+ if (handle !== 0) {
4417
+ ffi.metricsDestroy(handle);
4418
+ handle = 0;
4419
+ }
4273
4420
  },
4274
4421
  snapshot() {
4275
4422
  let out = new Uint8Array(4096);
4276
- let w = ffi.metricsSnapshot(handle, out);
4423
+ let w = ffi.metricsSnapshot(live(), out);
4277
4424
  if (w > out.length) {
4278
4425
  out = new Uint8Array(w);
4279
- w = ffi.metricsSnapshot(handle, out);
4426
+ w = ffi.metricsSnapshot(live(), out);
4280
4427
  }
4281
4428
  return out.subarray(0, w);
4282
4429
  }
@@ -4290,11 +4437,12 @@ function buildFactories(ctx) {
4290
4437
  const ffi = transport.ffi;
4291
4438
  if (ffi) {
4292
4439
  const napi = new addon.SchemaValidator(schema);
4293
- return ffiSchemaValidator(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4440
+ return ffiSchemaValidator(napi, handlePtr(napi.innerPtr?.()), ffi);
4294
4441
  }
4295
4442
  return new addon.SchemaValidator(schema);
4296
4443
  },
4297
4444
  createHmacSigner(key) {
4445
+ assertNonEmptySecret(key, "createHmacSigner");
4298
4446
  const ffi = transport.ffi;
4299
4447
  if (ffi)
4300
4448
  return ffiHmacSigner(key, ffi);
@@ -4304,7 +4452,7 @@ function buildFactories(ctx) {
4304
4452
  const ffi = transport.ffi;
4305
4453
  if (ffi) {
4306
4454
  const napi = new addon.TemplateRenderer(source);
4307
- return ffiTemplateRenderer(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4455
+ return ffiTemplateRenderer(napi, handlePtr(napi.innerPtr?.()), ffi);
4308
4456
  }
4309
4457
  return new addon.TemplateRenderer(source);
4310
4458
  },
@@ -4321,7 +4469,7 @@ function buildFactories(ctx) {
4321
4469
  const ffi = transport.ffi;
4322
4470
  if (ffi) {
4323
4471
  const napi = new addon.ConditionalRequest(etagValue, lastModifiedSecs ?? undefined);
4324
- return ffiConditionalRequest(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4472
+ return ffiConditionalRequest(napi, handlePtr(napi.innerPtr?.()), ffi);
4325
4473
  }
4326
4474
  return new addon.ConditionalRequest(etagValue, lastModifiedSecs ?? undefined);
4327
4475
  },
@@ -4329,7 +4477,7 @@ function buildFactories(ctx) {
4329
4477
  const ffi = transport.ffi;
4330
4478
  if (ffi) {
4331
4479
  const napi = new addon.AcceptNegotiator(supported);
4332
- return ffiAcceptNegotiator(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4480
+ return ffiAcceptNegotiator(napi, handlePtr(napi.innerPtr?.()), ffi);
4333
4481
  }
4334
4482
  return new addon.AcceptNegotiator(supported);
4335
4483
  },
@@ -4340,12 +4488,14 @@ function buildFactories(ctx) {
4340
4488
  return new addon.Base64Codec(urlSafe ?? undefined, padding ?? undefined);
4341
4489
  },
4342
4490
  createCookieSigner(secret) {
4491
+ assertNonEmptySecret(secret, "createCookieSigner");
4343
4492
  const ffi = transport.ffi;
4344
4493
  if (ffi)
4345
4494
  return ffiCookieSigner(secret, ffi);
4346
4495
  return new addon.CookieSigner(secret);
4347
4496
  },
4348
4497
  createCsrfProtector(secret) {
4498
+ assertNonEmptySecret(secret, "createCsrfProtector");
4349
4499
  const ffi = transport.ffi;
4350
4500
  if (ffi)
4351
4501
  return ffiCsrfProtector(secret, ffi);
@@ -4355,15 +4505,16 @@ function buildFactories(ctx) {
4355
4505
  const ffi = transport.ffi;
4356
4506
  if (ffi) {
4357
4507
  const napi = new addon.UrlBuilder(base);
4358
- return ffiUrlBuilder(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4508
+ return ffiUrlBuilder(napi, handlePtr(napi.innerPtr?.()), ffi);
4359
4509
  }
4360
4510
  return new addon.UrlBuilder(base);
4361
4511
  },
4362
4512
  createJwtSigner(secret, ttlSeconds) {
4513
+ assertNonEmptySecret(secret, "createJwtSigner");
4363
4514
  const ffi = transport.ffi;
4364
4515
  if (ffi) {
4365
4516
  const napi = new addon.JwtSigner(secret, ttlSeconds ?? undefined);
4366
- return ffiJwtSigner(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4517
+ return ffiJwtSigner(napi, handlePtr(napi.innerPtr?.()), ffi);
4367
4518
  }
4368
4519
  return new addon.JwtSigner(secret, ttlSeconds ?? undefined);
4369
4520
  },
@@ -4383,7 +4534,7 @@ function buildFactories(ctx) {
4383
4534
  const ffi = transport.ffi;
4384
4535
  if (ffi) {
4385
4536
  const napi = new addon.MediaTypeMatcher(expected);
4386
- return ffiMediaTypeMatcher(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4537
+ return ffiMediaTypeMatcher(napi, handlePtr(napi.innerPtr?.()), ffi);
4387
4538
  }
4388
4539
  return new addon.MediaTypeMatcher(expected);
4389
4540
  },
@@ -4391,7 +4542,7 @@ function buildFactories(ctx) {
4391
4542
  const ffi = transport.ffi;
4392
4543
  if (ffi) {
4393
4544
  const napi = new addon.RateLimiter(limit, windowMs, maxEntries ?? undefined);
4394
- return ffiRateLimiter(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4545
+ return ffiRateLimiter(napi, handlePtr(napi.innerPtr?.()), ffi);
4395
4546
  }
4396
4547
  return new addon.RateLimiter(limit, windowMs, maxEntries ?? undefined);
4397
4548
  },
@@ -4409,6 +4560,10 @@ function buildFactories(ctx) {
4409
4560
  },
4410
4561
  rayonNumThreads() {
4411
4562
  return asNumber(addon.rayonNumThreads());
4563
+ },
4564
+ clearSchemaCache() {
4565
+ const native = addon;
4566
+ native.clearSchemaCache?.();
4412
4567
  }
4413
4568
  };
4414
4569
  }
@@ -4492,37 +4647,63 @@ function ensureValidUtf8(bytes, ffi) {
4492
4647
  function u32LE(b, off) {
4493
4648
  return ((b[off] ?? 0) | (b[off + 1] ?? 0) << 8 | (b[off + 2] ?? 0) << 16 | (b[off + 3] ?? 0) << 24) >>> 0;
4494
4649
  }
4650
+ function keyBytesEqual(b, off, lit) {
4651
+ for (let i = 0;i < lit.length; i++) {
4652
+ if ((b[off + i] ?? 0) !== (lit[i] ?? 0))
4653
+ return false;
4654
+ }
4655
+ return true;
4656
+ }
4495
4657
  function unpackMediaType(packed) {
4658
+ const view = Buffer.from(packed.buffer, packed.byteOffset, packed.byteLength);
4496
4659
  let off = 0;
4497
4660
  const mtLen = u32LE(packed, off);
4498
4661
  off += 4;
4499
- const mediaType = decodeUtf8Range(packed, off, off + mtLen);
4662
+ const mediaType = decodeUtf8RangeView(view, off, off + mtLen);
4500
4663
  off += mtLen;
4501
4664
  let charset = null;
4502
4665
  const csLen = u32LE(packed, off);
4503
4666
  off += 4;
4504
4667
  if (csLen !== 4294967295) {
4505
- charset = decodeUtf8Range(packed, off, off + csLen);
4668
+ charset = decodeUtf8RangeView(view, off, off + csLen);
4506
4669
  off += csLen;
4507
4670
  }
4508
4671
  let boundary = null;
4509
4672
  const bLen = u32LE(packed, off);
4510
4673
  off += 4;
4511
4674
  if (bLen !== 4294967295) {
4512
- boundary = decodeUtf8Range(packed, off, off + bLen);
4675
+ boundary = decodeUtf8RangeView(view, off, off + bLen);
4513
4676
  off += bLen;
4514
4677
  }
4515
4678
  const count = u32LE(packed, off);
4516
4679
  off += 4;
4517
4680
  const params = {};
4681
+ let charsetTaken = charset === null;
4682
+ let boundaryTaken = boundary === null;
4518
4683
  for (let i = 0;i < count; i++) {
4519
4684
  const kLen = u32LE(packed, off);
4520
4685
  off += 4;
4521
- const key = decodeUtf8Range(packed, off, off + kLen);
4686
+ let key;
4687
+ if (kLen === 7 && keyBytesEqual(packed, off, CHARSET_KEY_BYTES)) {
4688
+ key = "charset";
4689
+ } else if (kLen === 8 && keyBytesEqual(packed, off, BOUNDARY_KEY_BYTES)) {
4690
+ key = "boundary";
4691
+ } else {
4692
+ key = decodeUtf8RangeView(view, off, off + kLen);
4693
+ }
4522
4694
  off += kLen;
4523
4695
  const vLen = u32LE(packed, off);
4524
4696
  off += 4;
4525
- const val = decodeUtf8Range(packed, off, off + vLen);
4697
+ let val;
4698
+ if (!charsetTaken && key === "charset" && charset !== null) {
4699
+ val = charset;
4700
+ charsetTaken = true;
4701
+ } else if (!boundaryTaken && key === "boundary" && boundary !== null) {
4702
+ val = boundary;
4703
+ boundaryTaken = true;
4704
+ } else {
4705
+ val = decodeUtf8RangeView(view, off, off + vLen);
4706
+ }
4526
4707
  off += vLen;
4527
4708
  params[key] = val;
4528
4709
  }
@@ -4653,7 +4834,7 @@ function buildHttp(ctx) {
4653
4834
  queryParsePacked(input) {
4654
4835
  const f = ffi();
4655
4836
  if (f) {
4656
- const out = new Uint8Array(input.length * 9 + 16);
4837
+ const out = new Uint8Array(input.length * 5 + 8);
4657
4838
  const w = f.queryParsePackedInto(input, out);
4658
4839
  return out.subarray(0, w);
4659
4840
  }
@@ -4665,7 +4846,7 @@ function buildHttp(ctx) {
4665
4846
  cookieParsePacked(input) {
4666
4847
  const f = ffi();
4667
4848
  if (f) {
4668
- const out = new Uint8Array(input.length * 9 + 16);
4849
+ const out = new Uint8Array(input.length * 5 + 8);
4669
4850
  const w = f.cookieParsePackedInto(input, out);
4670
4851
  return out.subarray(0, w);
4671
4852
  }
@@ -4677,7 +4858,7 @@ function buildHttp(ctx) {
4677
4858
  formParsePacked(input) {
4678
4859
  const f = ffi();
4679
4860
  if (f) {
4680
- const out = new Uint8Array(input.length * 9 + 16);
4861
+ const out = new Uint8Array(input.length * 5 + 8);
4681
4862
  const w = f.formParsePackedInto(input, out);
4682
4863
  return out.subarray(0, w);
4683
4864
  }
@@ -4749,13 +4930,15 @@ function buildHttp(ctx) {
4749
4930
  }
4750
4931
  };
4751
4932
  }
4752
- var WS_ACCEPT_SCRATCH, f32Bits, f32BitsU32;
4933
+ var WS_ACCEPT_SCRATCH, CHARSET_KEY_BYTES, BOUNDARY_KEY_BYTES, f32Bits, f32BitsU32;
4753
4934
  var init_http = __esm(() => {
4754
4935
  init_bytes();
4755
4936
  init_codec2();
4756
4937
  init_packed();
4757
4938
  init_context();
4758
4939
  WS_ACCEPT_SCRATCH = new Uint8Array(64);
4940
+ CHARSET_KEY_BYTES = new Uint8Array([99, 104, 97, 114, 115, 101, 116]);
4941
+ BOUNDARY_KEY_BYTES = new Uint8Array([98, 111, 117, 110, 100, 97, 114, 121]);
4759
4942
  f32Bits = new Float32Array(1);
4760
4943
  f32BitsU32 = new Uint32Array(f32Bits.buffer);
4761
4944
  });
@@ -4835,6 +5018,7 @@ function buildPayload(ctx) {
4835
5018
  const { builtins, transport } = ctx.runtime;
4836
5019
  const HAS = { gzipCompress: builtins.has("gzipCompress") };
4837
5020
  const ffi = memoizeFfi(transport);
5021
+ let SCRATCH = null;
4838
5022
  return {
4839
5023
  gzipCompress(data, level) {
4840
5024
  if (HAS.gzipCompress)
@@ -4888,8 +5072,20 @@ function buildPayload(ctx) {
4888
5072
  },
4889
5073
  multipartParse(body, boundary) {
4890
5074
  const f = ffi();
4891
- if (f)
4892
- return unpackMultipart(f.multipartParsePacked(body, boundary));
5075
+ if (f) {
5076
+ const need = body.length + boundary.length + 64;
5077
+ if (need > 64 * 1024)
5078
+ return unpackMultipart(f.multipartParsePacked(body, boundary));
5079
+ if (!SCRATCH || SCRATCH.length < need)
5080
+ SCRATCH = new Uint8Array(need);
5081
+ let w;
5082
+ try {
5083
+ w = f.multipartParsePackedInto(body, boundary, SCRATCH);
5084
+ } catch {
5085
+ return unpackMultipart(f.multipartParsePacked(body, boundary));
5086
+ }
5087
+ return unpackMultipart(SCRATCH.subarray(0, w));
5088
+ }
4893
5089
  return addon.multipartParse(body, boundary).map((p) => ({
4894
5090
  name: p.name,
4895
5091
  filename: p.filename ?? null,
@@ -5053,13 +5249,13 @@ var init_scalar = __esm(() => {
5053
5249
  // src/rust-ffi/text.ts
5054
5250
  function buildText(ctx) {
5055
5251
  const { builtins, transport } = ctx.runtime;
5056
- const mimeStrCache = new Map;
5057
5252
  return {
5058
5253
  mimeFromExtension(ext) {
5059
5254
  let mime = mimeStrCache.get(ext);
5060
5255
  if (mime === undefined) {
5061
5256
  const f = transport.ffi;
5062
5257
  mime = f ? f.mimeFromExtension(ext) ?? "application/octet-stream" : decoder.decode(ctx.cachedMime(encoder.encode(ext)));
5258
+ evictOldestHalf(mimeStrCache);
5063
5259
  mimeStrCache.set(ext, mime);
5064
5260
  }
5065
5261
  return mime;
@@ -5318,10 +5514,13 @@ function createNativeRoute(plan = {}) {
5318
5514
  const parseQuery = plan.parseQuery === true;
5319
5515
  const parseCookies = plan.parseCookies === true;
5320
5516
  const bunFFI = getBunFFI();
5321
- const ffiHandle = bunFFI !== null ? compileFfi(bunFFI, descriptor) : 0;
5322
- const napiRoute = bunFFI === null ? compileNapi(descriptor) : null;
5517
+ let ffiHandle = bunFFI !== null ? compileFfi(bunFFI, descriptor) : 0;
5518
+ let napiRoute = bunFFI === null || ffiHandle === 0 ? compileNapi(descriptor) : null;
5323
5519
  let out = new Uint8Array(256);
5324
5520
  const runFrame = (frame) => {
5521
+ if (ffiHandle === 0 && napiRoute === null) {
5522
+ throw new Error("native route: already destroyed");
5523
+ }
5325
5524
  let written;
5326
5525
  if (ffiHandle !== 0 && bunFFI !== null) {
5327
5526
  written = bunFFI.routeRun(ffiHandle, frame, out);
@@ -5354,7 +5553,10 @@ function createNativeRoute(plan = {}) {
5354
5553
  destroy: () => {
5355
5554
  if (ffiHandle !== 0 && bunFFI !== null) {
5356
5555
  bunFFI.routeDestroy(ffiHandle);
5556
+ ffiHandle = 0;
5357
5557
  }
5558
+ napiRoute?.destroy?.();
5559
+ napiRoute = null;
5358
5560
  }
5359
5561
  };
5360
5562
  }
@@ -5378,6 +5580,26 @@ function compileNapi(descriptor) {
5378
5580
  }
5379
5581
  }
5380
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
+
5592
+ // src/ingress/headers/memoized-headers.ts
5593
+ var CACHE = new WeakMap;
5594
+ function memoizedHeaders(entries) {
5595
+ const existing = CACHE.get(entries);
5596
+ if (existing !== undefined)
5597
+ return existing;
5598
+ const built = new Headers(entries);
5599
+ CACHE.set(entries, built);
5600
+ return built;
5601
+ }
5602
+
5381
5603
  // src/ingress/shared.ts
5382
5604
  var DEFAULT_MAX_BODY_BYTES = 1048576;
5383
5605
  var DEFAULT_FAST_OUTPUT_BUFFER_SIZE = 262144;
@@ -5420,13 +5642,16 @@ function resolveIp(req, srv, opts) {
5420
5642
  return opts.getIp ? opts.getIp(req, srv) : undefined;
5421
5643
  }
5422
5644
  function buildSuccessInit(ingress, result, ctx, status = 200) {
5423
- return {
5424
- status,
5425
- headers: ingress.responseHeaders(result.headerVariant, ctx.requestIdHeader, ctx.origin, result.rateRemaining, result.rateResetMs > 0 ? secondsFromMs(result.rateResetMs) : undefined)
5426
- };
5645
+ const rateResetSecs = result.rateResetMs > 0 ? secondsFromMs(result.rateResetMs) : undefined;
5646
+ const headers = ingress.successHeaders ? ingress.successHeaders(result.headerVariant, ctx.requestIdHeader, ctx.origin, result.rateRemaining, rateResetSecs) : memoizedHeaders(ingress.responseHeaders(result.headerVariant, ctx.requestIdHeader, ctx.origin, result.rateRemaining, rateResetSecs));
5647
+ return { status, headers };
5427
5648
  }
5428
5649
  function runBaked(ingress, opts, respond) {
5429
- return (req, srv) => ingress.run(req, resolveIp(req, srv, opts), null, respond);
5650
+ return (req, srv) => {
5651
+ if (req.signal?.aborted)
5652
+ return abortResponse();
5653
+ return ingress.run(req, resolveIp(req, srv, opts), null, respond);
5654
+ };
5430
5655
  }
5431
5656
  // src/ingress/routes/read.ts
5432
5657
  function readHandler(ingress, opts = {}) {
@@ -5477,6 +5702,44 @@ function bodyTooLargeError() {
5477
5702
  err.code = "BODY_TOO_LARGE";
5478
5703
  return err;
5479
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
+ }
5480
5743
  var bodyWatchdog = (() => {
5481
5744
  const inflight = new Set;
5482
5745
  let timer = null;
@@ -5528,6 +5791,7 @@ function createDeadline(deadlineMs) {
5528
5791
  return { promise, cancel: () => bodyWatchdog.untrack(entry) };
5529
5792
  }
5530
5793
  async function readBodyWithLimit(req, maxBytes, guard, timeoutMs) {
5794
+ throwIfAborted(req.signal);
5531
5795
  const deadline = timeoutMs && timeoutMs > 0 ? timeoutMs : 0;
5532
5796
  const declared = req.headers.get("content-length");
5533
5797
  if (declared !== null) {
@@ -5537,45 +5801,67 @@ async function readBodyWithLimit(req, maxBytes, guard, timeoutMs) {
5537
5801
  throw bodyTooLargeError();
5538
5802
  }
5539
5803
  if (deadline <= 0) {
5540
- const bytes = await readBodyBytes(req);
5541
- if (guard && bytes.byteLength > maxBytes) {
5542
- throw bodyTooLargeError();
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();
5543
5822
  }
5544
- return bytes;
5545
5823
  }
5546
5824
  const buffered = peekBufferedBody(req);
5547
5825
  if (buffered === false) {
5548
5826
  const dl = createDeadline(deadline);
5827
+ const abort = createAbortGuard(req.signal);
5549
5828
  try {
5550
- const bytes = await Promise.race([readBodyBytes(req), dl.promise]);
5829
+ const bytes = await raceRead(readBodyBytes(req), abort !== null ? [dl, abort] : [dl]);
5830
+ throwIfAborted(req.signal);
5551
5831
  if (guard && bytes.byteLength > maxBytes) {
5552
5832
  throw bodyTooLargeError();
5553
5833
  }
5554
5834
  return bytes;
5555
5835
  } finally {
5556
5836
  dl.cancel();
5837
+ abort?.cancel();
5557
5838
  }
5558
5839
  }
5559
5840
  if ("bytes" in buffered) {
5841
+ throwIfAborted(req.signal);
5560
5842
  if (guard && buffered.bytes.byteLength > maxBytes) {
5561
5843
  throw bodyTooLargeError();
5562
5844
  }
5563
5845
  return buffered.bytes;
5564
5846
  }
5565
5847
  const dl = createDeadline(deadline);
5848
+ const abort = createAbortGuard(req.signal);
5566
5849
  try {
5567
- const bytes = await Promise.race([buffered, dl.promise]);
5850
+ const bytes = await raceRead(buffered, abort !== null ? [dl, abort] : [dl]);
5851
+ throwIfAborted(req.signal);
5568
5852
  if (guard && bytes.byteLength > maxBytes) {
5569
5853
  throw bodyTooLargeError();
5570
5854
  }
5571
5855
  return bytes;
5572
5856
  } finally {
5573
5857
  dl.cancel();
5858
+ abort?.cancel();
5574
5859
  }
5575
5860
  }
5576
5861
  }
5577
5862
  const body = req.body;
5578
5863
  if (!body) {
5864
+ throwIfAborted(req.signal);
5579
5865
  return EMPTY_BODY;
5580
5866
  }
5581
5867
  const reader = body.getReader();
@@ -5583,9 +5869,15 @@ async function readBodyWithLimit(req, maxBytes, guard, timeoutMs) {
5583
5869
  let total = 0;
5584
5870
  let completed = false;
5585
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);
5586
5878
  try {
5587
5879
  for (;; ) {
5588
- const { done, value } = dl !== null ? await Promise.race([reader.read(), dl.promise]) : await reader.read();
5880
+ const { done, value } = await raceRead(reader.read(), guards);
5589
5881
  if (done) {
5590
5882
  completed = true;
5591
5883
  break;
@@ -5600,6 +5892,7 @@ async function readBodyWithLimit(req, maxBytes, guard, timeoutMs) {
5600
5892
  }
5601
5893
  } finally {
5602
5894
  dl?.cancel();
5895
+ abort?.cancel();
5603
5896
  if (!completed) {
5604
5897
  await reader.cancel().catch(() => {});
5605
5898
  }
@@ -5802,11 +6095,25 @@ function rateLimitedBody(retryAfterMs) {
5802
6095
  }
5803
6096
 
5804
6097
  // src/ingress/routes/echo.ts
6098
+ var SAFE_ECHO_CONTENT_TYPES = new Set([
6099
+ "application/json",
6100
+ "application/octet-stream",
6101
+ "text/plain"
6102
+ ]);
6103
+ function echoMediaTypeOf(contentType) {
6104
+ const semi = contentType.indexOf(";");
6105
+ return (semi >= 0 ? contentType.slice(0, semi) : contentType).trim().toLowerCase();
6106
+ }
6107
+ function safeEchoContentType(contentType) {
6108
+ return SAFE_ECHO_CONTENT_TYPES.has(echoMediaTypeOf(contentType)) ? contentType : "application/octet-stream";
6109
+ }
5805
6110
  function echoHandler(ingress, opts = {}) {
5806
6111
  const maxBodyBytes = opts.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
5807
6112
  const bodyTimeoutMs = opts.bodyTimeoutMs ?? DEFAULT_BODY_TIMEOUT_MS;
5808
6113
  return async (req, srv) => {
5809
6114
  const ip = resolveIp(req, srv, opts);
6115
+ if (req.signal?.aborted)
6116
+ return abortResponse();
5810
6117
  const prep = ingress.run(req, ip, null, (result, ctx) => {
5811
6118
  const terminal = ingress.terminalResponse(req, result, ctx);
5812
6119
  if (terminal)
@@ -5821,41 +6128,54 @@ function echoHandler(ingress, opts = {}) {
5821
6128
  });
5822
6129
  if (prep.terminal)
5823
6130
  return prep.terminal;
6131
+ if (req.signal?.aborted)
6132
+ return abortResponse();
5824
6133
  const baseHeaders = prep.headers ?? [];
5825
- const requestedContentType = req.headers.get("content-type") ?? "application/octet-stream";
6134
+ const requestedContentType = safeEchoContentType(req.headers.get("content-type") ?? "application/octet-stream");
6135
+ const withEchoHeaders = (contentType) => {
6136
+ const headers = ingress.withContentType(baseHeaders, contentType);
6137
+ if (!headers.some(([name]) => name.toLowerCase() === "x-content-type-options")) {
6138
+ headers.push(["x-content-type-options", "nosniff"]);
6139
+ }
6140
+ return headers;
6141
+ };
5826
6142
  const contentLengthHeader = req.headers.get("content-length");
5827
6143
  const contentLength = contentLengthHeader === null ? NaN : Number(contentLengthHeader);
5828
6144
  if (Number.isFinite(contentLength)) {
5829
6145
  if (contentLength > maxBodyBytes) {
5830
6146
  return new Response(ERROR_BODIES.body_too_large, {
5831
6147
  status: 413,
5832
- headers: ingress.withContentType(baseHeaders, "application/json")
6148
+ headers: withEchoHeaders("application/json")
5833
6149
  });
5834
6150
  }
5835
6151
  if (contentLength <= 0 || req.body === null) {
5836
6152
  return new Response(null, {
5837
6153
  status: 200,
5838
- headers: ingress.withContentType(baseHeaders, requestedContentType)
6154
+ headers: withEchoHeaders(requestedContentType)
5839
6155
  });
5840
6156
  }
5841
6157
  return new Response(req.body, {
5842
6158
  status: 200,
5843
- headers: ingress.withContentType(baseHeaders, requestedContentType)
6159
+ headers: withEchoHeaders(requestedContentType)
5844
6160
  });
5845
6161
  }
5846
6162
  try {
5847
6163
  const bodyBytes = await readBodyWithLimit(req, maxBodyBytes, true, bodyTimeoutMs);
6164
+ if (req.signal?.aborted)
6165
+ return abortResponse();
5848
6166
  return new Response(bodyBytes.byteLength > 0 ? bodyBytes : null, {
5849
6167
  status: 200,
5850
- headers: ingress.withContentType(baseHeaders, requestedContentType)
6168
+ headers: withEchoHeaders(requestedContentType)
5851
6169
  });
5852
6170
  } catch (err) {
6171
+ if (isAbortError(err))
6172
+ return abortResponse();
5853
6173
  const code = err?.code;
5854
6174
  const isTooLarge = code === "BODY_TOO_LARGE";
5855
6175
  const isTimeout = code === "REQUEST_TIMEOUT";
5856
6176
  return new Response(isTooLarge ? ERROR_BODIES.body_too_large : isTimeout ? ERROR_BODIES.request_timeout : ERROR_BODIES.bad_request, {
5857
6177
  status: isTooLarge ? 413 : isTimeout ? 408 : 400,
5858
- headers: ingress.withContentType(baseHeaders, "application/json")
6178
+ headers: withEchoHeaders("application/json")
5859
6179
  });
5860
6180
  }
5861
6181
  };
@@ -5893,6 +6213,11 @@ function defaultJsonValid(bytes) {
5893
6213
  return false;
5894
6214
  }
5895
6215
  }
6216
+ function isJsonContentType(contentType) {
6217
+ const semi = contentType.indexOf(";");
6218
+ const mediaType = (semi >= 0 ? contentType.slice(0, semi) : contentType).trim().toLowerCase();
6219
+ return mediaType === "application/json" || mediaType.endsWith("+json");
6220
+ }
5896
6221
  function jsonWriteHandler(ingress, opts = {}) {
5897
6222
  const maxBodyBytes = opts.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
5898
6223
  const fallback = opts.fallback ?? ingress;
@@ -5901,19 +6226,18 @@ function jsonWriteHandler(ingress, opts = {}) {
5901
6226
  const jsonValid = opts.jsonValid ?? ingress.jsonValid ?? defaultJsonValid;
5902
6227
  return async (req, srv) => {
5903
6228
  const ip = resolveIp(req, srv, opts);
6229
+ if (req.signal?.aborted)
6230
+ return abortResponse();
5904
6231
  const contentType = req.headers.get("content-type") ?? "";
5905
- if (!contentType.includes("application/json")) {
5906
- return fallback.run(req, ip, null, (result, ctx) => {
5907
- const terminal = fallback.terminalResponse(req, result, ctx);
5908
- if (terminal)
5909
- return terminal;
5910
- return fallback.errorResponse(req, result, 415, "unsupported_media_type", "Content-Type must be application/json", ctx);
5911
- });
6232
+ if (!isJsonContentType(contentType)) {
6233
+ return fallback.run(req, ip, null, (result, ctx) => fallback.errorResponse(req, result, 415, "unsupported_media_type", "Content-Type must be application/json", ctx));
5912
6234
  }
5913
6235
  let bodyBytes;
5914
6236
  try {
5915
6237
  bodyBytes = await readBodyWithLimit(req, maxBodyBytes, true, bodyTimeoutMs);
5916
6238
  } catch (err) {
6239
+ if (isAbortError(err))
6240
+ return abortResponse();
5917
6241
  const code = err?.code;
5918
6242
  const isTooLarge = code === "BODY_TOO_LARGE";
5919
6243
  return fallback.run(req, ip, null, (result, ctx) => {
@@ -5923,6 +6247,8 @@ function jsonWriteHandler(ingress, opts = {}) {
5923
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);
5924
6248
  });
5925
6249
  }
6250
+ if (req.signal?.aborted)
6251
+ return abortResponse();
5926
6252
  return ingress.run(req, ip, bodyBytes, (result, ctx) => {
5927
6253
  const terminal = ingress.terminalResponse(req, result, ctx);
5928
6254
  if (terminal)
@@ -5944,12 +6270,16 @@ function jsonWriteHandler(ingress, opts = {}) {
5944
6270
  }
5945
6271
  // src/ingress/routes/options.ts
5946
6272
  function optionsHandler(ingress, opts = {}) {
5947
- return (req, srv) => ingress.run(req, resolveIp(req, srv, opts), null, (result, ctx) => {
5948
- const terminal = ingress.terminalResponse(req, result, ctx);
5949
- if (terminal)
5950
- return terminal;
5951
- return new Response(null, buildSuccessInit(ingress, result, ctx, 204));
5952
- });
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
+ };
5953
6283
  }
5954
6284
  // src/ingress/routes/responder.ts
5955
6285
  init_bytes();
@@ -6093,7 +6423,7 @@ function safeTerminalStatus(r) {
6093
6423
  }
6094
6424
 
6095
6425
  // src/ingress/routes/responder.ts
6096
- var IGNGEX_SECURITY_HEADERS = [
6426
+ var IGNEX_SECURITY_HEADERS = [
6097
6427
  ["x-frame-options", "DENY"],
6098
6428
  ["x-content-type-options", "nosniff"],
6099
6429
  ["referrer-policy", "no-referrer"]
@@ -6101,7 +6431,7 @@ var IGNGEX_SECURITY_HEADERS = [
6101
6431
  function buildIgngexTerminal(result, _ctx) {
6102
6432
  const preflightAllowed = result.isPreflight && result.corsAllowed;
6103
6433
  if (preflightAllowed) {
6104
- return new Response(null, { status: 204, headers: IGNGEX_SECURITY_HEADERS });
6434
+ return new Response(null, { status: 204, headers: IGNEX_SECURITY_HEADERS });
6105
6435
  }
6106
6436
  const status = safeTerminalStatus(result);
6107
6437
  const body = JSON.stringify({
@@ -6109,14 +6439,14 @@ function buildIgngexTerminal(result, _ctx) {
6109
6439
  status,
6110
6440
  code: errorCodeName(result.errorCode)
6111
6441
  });
6112
- const headers = new Headers(IGNGEX_SECURITY_HEADERS);
6442
+ const headers = new Headers(IGNEX_SECURITY_HEADERS);
6113
6443
  headers.set("content-type", "application/json; charset=utf-8");
6114
6444
  return new Response(body, { status, headers });
6115
6445
  }
6116
6446
  function parseSection(json) {
6117
6447
  try {
6118
- const v = JSON.parse(json);
6119
- return v !== null && typeof v === "object" && !Array.isArray(v) ? v : {};
6448
+ const parsed = JSON.parse(json);
6449
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
6120
6450
  } catch {
6121
6451
  return {};
6122
6452
  }
@@ -6128,19 +6458,25 @@ function nativeResponderRoute(ingress, responder, opts = {}) {
6128
6458
  const bodyTimeoutMs = opts.bodyTimeoutMs ?? DEFAULT_BODY_TIMEOUT_MS;
6129
6459
  return async (req, srv, _params) => {
6130
6460
  const ip = resolveIp(req, srv, opts);
6461
+ if (req.signal?.aborted)
6462
+ return abortResponse();
6131
6463
  const requestId = decoder.decode(generateRequestId());
6132
6464
  let body = null;
6133
6465
  if (readBody) {
6134
6466
  try {
6135
6467
  body = await readBodyWithLimit(req, maxBodyBytes, true, bodyTimeoutMs);
6136
6468
  } catch (err) {
6469
+ if (isAbortError(err))
6470
+ return abortResponse();
6137
6471
  const code = err.code;
6138
6472
  const status = code === "REQUEST_TIMEOUT" ? 408 : code === "BODY_TOO_LARGE" ? 413 : 400;
6139
6473
  const payload = JSON.stringify({ error: "Bad Request", status, code: "BAD_REQUEST" });
6140
- const headers = new Headers(IGNGEX_SECURITY_HEADERS);
6474
+ const headers = new Headers(IGNEX_SECURITY_HEADERS);
6141
6475
  headers.set("content-type", "application/json; charset=utf-8");
6142
6476
  return new Response(payload, { status, headers });
6143
6477
  }
6478
+ if (req.signal?.aborted)
6479
+ return abortResponse();
6144
6480
  }
6145
6481
  let terminal = null;
6146
6482
  let snapshot = null;
@@ -6175,10 +6511,30 @@ function nativeResponderRoute(ingress, responder, opts = {}) {
6175
6511
  }
6176
6512
  // src/ingress/routes/native.ts
6177
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
6178
6534
  function pairsToRecord(pairs) {
6179
- const out = {};
6535
+ const out = safeRecord();
6180
6536
  for (const [k, v] of pairs)
6181
- out[k] = v;
6537
+ assignOwn(out, k, v);
6182
6538
  return out;
6183
6539
  }
6184
6540
  function nativeRouteHandler(route, responder, opts = {}) {
@@ -6190,16 +6546,22 @@ function nativeRouteHandler(route, responder, opts = {}) {
6190
6546
  const terminal = (status, code, message) => Response.json({ ok: false, error: { code, message } }, { status, headers: { "content-type": "application/json" } });
6191
6547
  return async (req, srv, _params) => {
6192
6548
  const ip = resolveIp(req, srv, opts);
6549
+ if (req.signal?.aborted)
6550
+ return abortResponse();
6193
6551
  const requestId = decoder.decode(generateRequestId());
6194
6552
  let body = null;
6195
6553
  if (readBody) {
6196
6554
  try {
6197
6555
  body = await readBodyWithLimit(req, maxBodyBytes, true, bodyTimeoutMs);
6198
6556
  } catch (err) {
6557
+ if (isAbortError(err))
6558
+ return abortResponse();
6199
6559
  const code = err.code;
6200
6560
  const status = code === "REQUEST_TIMEOUT" ? 408 : code === "BODY_TOO_LARGE" ? 413 : 400;
6201
6561
  return terminal(status, "BAD_REQUEST", "Request body read failed");
6202
6562
  }
6563
+ if (req.signal?.aborted)
6564
+ return abortResponse();
6203
6565
  }
6204
6566
  const url = req.url;
6205
6567
  const qIndex = url.indexOf("?");
@@ -6235,6 +6597,49 @@ function nativeRouteHandler(route, responder, opts = {}) {
6235
6597
  };
6236
6598
  }
6237
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
+
6238
6643
  // src/ingress/path-matcher.ts
6239
6644
  function buildPathMatcher(routes) {
6240
6645
  const exact = new Map;
@@ -6342,6 +6747,10 @@ function buildRouteHandlers(options) {
6342
6747
  getIp: options.getIp,
6343
6748
  copyBody: options.copyBody
6344
6749
  };
6750
+ const onServerError = createServerErrorHandler({
6751
+ onError: options.onError,
6752
+ logger: options.logger
6753
+ });
6345
6754
  const serverRoutes = {};
6346
6755
  for (const [path, spec] of Object.entries(options.routes)) {
6347
6756
  const routeOpts = {
@@ -6350,9 +6759,12 @@ function buildRouteHandlers(options) {
6350
6759
  bodyTimeoutMs: spec.bodyTimeoutMs
6351
6760
  };
6352
6761
  const methods = {};
6353
- if (spec.read) {
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) {
6354
6766
  if (typeof spec.read === "function") {
6355
- methods.GET = spec.read;
6767
+ methods.GET = guardRouteHandler(spec.read, onServerError);
6356
6768
  } else {
6357
6769
  methods.GET = readHandler(spec.read, routeOpts);
6358
6770
  methods.HEAD = headHandler(spec.read, routeOpts);
@@ -6370,34 +6782,40 @@ function buildRouteHandlers(options) {
6370
6782
  if (spec.echo) {
6371
6783
  methods.POST = echoHandler(spec.echo, routeOpts);
6372
6784
  }
6373
- if (spec.cookies) {
6785
+ if (spec.cookies && !hasStatic) {
6374
6786
  methods.GET = readHandler(spec.cookies, routeOpts);
6375
6787
  }
6376
6788
  if (spec.delete) {
6377
6789
  methods.DELETE = deleteHandler(spec.delete, routeOpts);
6378
6790
  }
6379
6791
  if (spec.responder) {
6380
- const responderRoute = nativeResponderRoute(spec.responder.ingress, spec.responder.handler, {
6381
- ...routeOpts,
6382
- terminalStyle: spec.responder.terminalStyle,
6383
- readBody: spec.responder.readBody
6384
- });
6385
- const responderMethods = spec.responder.methods ?? ["GET"];
6386
- for (const m of responderMethods) {
6387
- methods[m] = responderRoute;
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
+ }
6388
6803
  }
6389
6804
  }
6390
6805
  if (spec.native) {
6391
- const nativeRoute = nativeRouteHandler(createNativeRoute(spec.native.plan), spec.native.handler, {
6392
- ...routeOpts,
6393
- readBody: spec.native.readBody
6394
- });
6395
- const nativeMethods = spec.native.methods ?? ["GET"];
6396
- for (const m of nativeMethods) {
6397
- methods[m] = nativeRoute;
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
+ }
6398
6816
  }
6399
6817
  }
6400
- 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;
6401
6819
  if (primary) {
6402
6820
  methods.OPTIONS = optionsHandler(primary, routeOpts);
6403
6821
  }
@@ -6410,12 +6828,17 @@ function createIngressServer(options) {
6410
6828
  throw new TypeError("createIngressServer is Bun-only (it builds a Bun.serve config). " + "On Node.js use createIngressServerNode with the same route handlers.");
6411
6829
  }
6412
6830
  const { routes: serverRoutes, baseOpts } = buildRouteHandlers(options);
6831
+ const onServerError = createServerErrorHandler({
6832
+ onError: options.onError,
6833
+ logger: options.logger
6834
+ });
6413
6835
  const serverOptions = {
6414
6836
  hostname: options.hostname ?? "0.0.0.0",
6415
6837
  port: options.port,
6416
6838
  idleTimeout: options.idleTimeout ?? 30,
6417
6839
  routes: serverRoutes,
6418
- maxRequestBodySize: options.maxRequestBodySize ?? DEFAULT_MAX_REQUEST_BODY_SIZE
6840
+ maxRequestBodySize: options.maxRequestBodySize ?? DEFAULT_MAX_REQUEST_BODY_SIZE,
6841
+ error: (error, req) => onServerError(error, req)
6419
6842
  };
6420
6843
  if (options.reusePort) {
6421
6844
  serverOptions.reusePort = true;
@@ -6468,8 +6891,15 @@ function nodeRequestToWebRequest(req) {
6468
6891
  }
6469
6892
  const method = req.method ?? "GET";
6470
6893
  const bodyForbidden = method === "GET" || method === "HEAD";
6471
- const hasBody = Number(req.headers["content-length"] ?? 0) > 0 || req.headers["transfer-encoding"] !== undefined;
6894
+ const hasBody = requestHasBody(req);
6472
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;
6473
6903
  if (hasBody && !bodyForbidden) {
6474
6904
  init.body = Readable.toWeb(req);
6475
6905
  init.duplex = "half";
@@ -6512,7 +6942,7 @@ async function writeResponse(res, response) {
6512
6942
  }
6513
6943
  function drainUnreadBody(res) {
6514
6944
  const req = res.req;
6515
- if (req !== undefined && req !== null && !req.complete && hasBody(req)) {
6945
+ if (req !== undefined && req !== null && !req.complete && requestHasBody(req)) {
6516
6946
  req.resume();
6517
6947
  res.shouldKeepAlive = false;
6518
6948
  if (!res.headersSent) {
@@ -6520,11 +6950,39 @@ function drainUnreadBody(res) {
6520
6950
  }
6521
6951
  }
6522
6952
  }
6523
- function hasBody(req) {
6953
+ function requestHasBody(req) {
6524
6954
  return Number(req.headers["content-length"] ?? 0) > 0 || req.headers["transfer-encoding"] !== undefined;
6525
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
+ }
6526
6980
  function makeRequestListener(options, routes, baseOpts) {
6527
6981
  const matchPath = buildPathMatcher(routes);
6982
+ const onServerError = createServerErrorHandler({
6983
+ onError: options.onError,
6984
+ logger: options.logger
6985
+ });
6528
6986
  return async (req, res) => {
6529
6987
  try {
6530
6988
  const contentLengthHeader = req.headers["content-length"];
@@ -6545,10 +7003,19 @@ function makeRequestListener(options, routes, baseOpts) {
6545
7003
  const pathname = new URL(webReq.url).pathname;
6546
7004
  const method = webReq.method ?? "GET";
6547
7005
  const matched = matchPath(pathname);
6548
- const handler = matched?.methods?.[method];
7006
+ const entry = matched?.methods?.[method];
6549
7007
  let response;
6550
- if (handler !== undefined && matched !== undefined) {
6551
- response = await handler(webReq, req, matched.params);
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
+ }
6552
7019
  } else if (options.fallback !== undefined) {
6553
7020
  const fallback = fallbackHandler(options.fallback, baseOpts);
6554
7021
  response = await fallback(webReq, req);
@@ -6558,24 +7025,31 @@ function makeRequestListener(options, routes, baseOpts) {
6558
7025
  await writeResponse(res, response);
6559
7026
  } catch (err) {
6560
7027
  if (!res.headersSent) {
6561
- res.statusCode = 500;
6562
- res.setHeader("content-type", "application/json");
6563
- res.end(JSON.stringify({
6564
- error: { code: "internal_error", message: "Internal Server Error" }
6565
- }));
7028
+ await writeResponse(res, onServerError(err));
6566
7029
  } else {
7030
+ onServerError(err);
6567
7031
  res.destroy();
6568
7032
  }
6569
- if (options.onError) {
6570
- try {
6571
- options.onError({
6572
- error: err instanceof Error ? err : new Error(String(err))
6573
- });
6574
- } catch {}
6575
- }
6576
7033
  }
6577
7034
  };
6578
7035
  }
7036
+ var WS_PROTOCOL_TOKEN = /^[A-Za-z0-9!#$%&'*+\-.^_`|~]+$/;
7037
+ function formatWebSocketHandshake(accept, protocol) {
7038
+ if (/[\r\n]/.test(accept)) {
7039
+ throw new Error("invalid Sec-WebSocket-Accept value");
7040
+ }
7041
+ let raw = `HTTP/1.1 101 Switching Protocols\r
7042
+ ` + `Upgrade: websocket\r
7043
+ ` + `Connection: Upgrade\r
7044
+ ` + `Sec-WebSocket-Accept: ${accept}\r
7045
+ `;
7046
+ if (protocol && WS_PROTOCOL_TOKEN.test(protocol)) {
7047
+ raw += `Sec-WebSocket-Protocol: ${protocol}\r
7048
+ `;
7049
+ }
7050
+ return `${raw}\r
7051
+ `;
7052
+ }
6579
7053
  function makeUpgradeListener(options) {
6580
7054
  return async (req, socket, head) => {
6581
7055
  try {
@@ -6585,18 +7059,7 @@ function makeUpgradeListener(options) {
6585
7059
  socket.destroy();
6586
7060
  return;
6587
7061
  }
6588
- let raw = `HTTP/1.1 101 Switching Protocols\r
6589
- ` + `Upgrade: websocket\r
6590
- ` + `Connection: Upgrade\r
6591
- ` + `Sec-WebSocket-Accept: ${handshake.accept}\r
6592
- `;
6593
- if (handshake.protocol) {
6594
- raw += `Sec-WebSocket-Protocol: ${handshake.protocol}\r
6595
- `;
6596
- }
6597
- raw += `\r
6598
- `;
6599
- socket.write(raw);
7062
+ socket.write(formatWebSocketHandshake(handshake.accept, handshake.protocol));
6600
7063
  options.onUpgrade?.(socket, req, head);
6601
7064
  } catch {
6602
7065
  socket.destroy();
@@ -6820,6 +7283,7 @@ class IngressResultBase {
6820
7283
  bodyTruncated = false;
6821
7284
  body = EMPTY_BODY2;
6822
7285
  _buf = EMPTY_BODY2;
7286
+ _len = 0;
6823
7287
  _bodyJsonStart = OUT_DATA_START;
6824
7288
  _bodyJsonLen = 0;
6825
7289
  _cookiesBuf = null;
@@ -6845,6 +7309,7 @@ class IngressResultBase {
6845
7309
  this.bodyTruncated = false;
6846
7310
  this.body = EMPTY_BODY2;
6847
7311
  this._buf = EMPTY_BODY2;
7312
+ this._len = 0;
6848
7313
  this._bodyJsonStart = OUT_DATA_START;
6849
7314
  this._bodyJsonLen = 0;
6850
7315
  this._cookiesBuf = null;
@@ -6869,8 +7334,9 @@ class IngressResultBase {
6869
7334
  this.retryAfterMs = 0;
6870
7335
  }
6871
7336
  }
6872
- setSections(buf, layout, bodyJsonLen) {
7337
+ setSections(buf, layout, bodyJsonLen, len = buf.byteLength) {
6873
7338
  this._buf = buf;
7339
+ this._len = len;
6874
7340
  this._bodyJsonStart = layout.bodyJsonStart;
6875
7341
  this._bodyJsonLen = bodyJsonLen;
6876
7342
  this._cookiesBuf = layout.safeCookiesLen > 0 ? buf.subarray(OUT_DATA_START, OUT_DATA_START + layout.safeCookiesLen) : null;
@@ -6900,7 +7366,7 @@ class IngressResultBase {
6900
7366
  if (this._bodyJsonLen === 0)
6901
7367
  return EMPTY_BODY2;
6902
7368
  const end = this._bodyJsonStart + this._bodyJsonLen;
6903
- if (end > this._buf.byteLength)
7369
+ if (end > this._len)
6904
7370
  return EMPTY_BODY2;
6905
7371
  return this._buf.subarray(this._bodyJsonStart, end);
6906
7372
  }
@@ -6964,7 +7430,7 @@ class FastIngressResult extends IngressResultBase {
6964
7430
  this.isPreflight = (flags & FLAG_IS_PREFLIGHT) !== 0;
6965
7431
  this.rateLimited = (flags & FLAG_RATE_LIMITED) !== 0;
6966
7432
  const layout = sectionLayout(buf.byteLength, cookiesJsonLen, queryJsonLen, bodyJsonLen);
6967
- this.setSections(buf, layout, bodyJsonLen);
7433
+ this.setSections(buf, layout, bodyJsonLen, buf.byteLength);
6968
7434
  this.bodyTruncated = (flags & FLAG_BODY_TRUNCATED) !== 0 || layout.truncated;
6969
7435
  }
6970
7436
  invalidate() {
@@ -7056,7 +7522,14 @@ function warnTrustProxyDeprecated() {
7056
7522
  if (trustProxyWarned)
7057
7523
  return;
7058
7524
  trustProxyWarned = true;
7059
- console.warn("[castrum] WARN: `trustProxy: true` is deprecated and trusts EVERY hop — " + "clients can spoof X-Forwarded-For / X-Real-IP to bypass IP-based rate " + "limiting. Use `trustedProxies: { enabled: true, networks: [...] }` and " + "only enable proxy trust behind a trusted edge.");
7525
+ console.warn("[castrum] WARN: `trustProxy: true` is deprecated and trusts EVERY hop — " + "clients can spoof X-Forwarded-For / X-Real-IP to bypass IP-based rate " + "limiting. Use `trustedProxies: { enabled: true, networks: [...] }` " + "(which takes precedence) and only enable proxy trust behind a trusted edge.");
7526
+ }
7527
+ var rateLimitNoIpWarned = false;
7528
+ function warnRateLimitWithoutGetIp() {
7529
+ if (rateLimitNoIpWarned)
7530
+ return;
7531
+ rateLimitNoIpWarned = true;
7532
+ console.warn("[castrum] WARN: rate limiting is enabled without `getIp` — every client " + "shares ONE bucket, so one client can exhaust it for everyone. Provide " + "`getIp` for per-client limits (or use a global limit deliberately).");
7060
7533
  }
7061
7534
 
7062
7535
  // src/ingress/packing/header-packing.ts
@@ -7622,6 +8095,11 @@ class BufferPool {
7622
8095
  this.estimate = options.adaptive === true ? new AdaptiveEstimate({ alpha: 0.25, min: this.initialSize }) : null;
7623
8096
  this.free.push(new Uint8Array(this.initialSize));
7624
8097
  this.created = 1;
8098
+ const prefill = Math.min(Math.max(0, Math.floor(options.prefill ?? 0)), this.maxBuffers - 1, options.prefillBytes !== undefined ? Math.max(0, Math.floor(options.prefillBytes / this.initialSize)) : Number.POSITIVE_INFINITY);
8099
+ for (let i = 0;i < prefill; i++) {
8100
+ this.free.push(new Uint8Array(this.initialSize));
8101
+ this.created++;
8102
+ }
7625
8103
  }
7626
8104
  get freeCount() {
7627
8105
  return this.free.length;
@@ -7629,6 +8107,9 @@ class BufferPool {
7629
8107
  get createdCount() {
7630
8108
  return this.created;
7631
8109
  }
8110
+ shrink() {
8111
+ this.free.length = 0;
8112
+ }
7632
8113
  acquire(minSize = 0) {
7633
8114
  if (this.maxInFlight > 0 && this.inFlight >= this.maxInFlight) {
7634
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.");
@@ -7655,7 +8136,9 @@ class BufferPool {
7655
8136
  for (let i = 0;i < this.free.length; i++) {
7656
8137
  const candidate = this.free[i];
7657
8138
  if (candidate.byteLength >= minSize) {
7658
- this.free.splice(i, 1);
8139
+ const last = this.free.pop();
8140
+ if (i < this.free.length)
8141
+ this.free[i] = last;
7659
8142
  return candidate;
7660
8143
  }
7661
8144
  }
@@ -7670,7 +8153,9 @@ class BufferPool {
7670
8153
  }
7671
8154
  }
7672
8155
  if (largestBuf !== null) {
7673
- this.free.splice(largest, 1);
8156
+ const last = this.free.pop();
8157
+ if (largest < this.free.length)
8158
+ this.free[largest] = last;
7674
8159
  this.created++;
7675
8160
  return new Uint8Array(target);
7676
8161
  }
@@ -7679,16 +8164,15 @@ class BufferPool {
7679
8164
  }
7680
8165
  releaseBuffer(buffer) {
7681
8166
  if (this.free.length >= this.maxBuffers) {
7682
- let smallestBuf = null;
7683
8167
  let smallest = 0;
7684
- for (let i = 0;i < this.free.length; i++) {
7685
- const candidate = this.free[i];
7686
- if (smallestBuf === null || candidate.byteLength < smallestBuf.byteLength) {
7687
- smallestBuf = candidate;
8168
+ for (let i = 1;i < this.free.length; i++) {
8169
+ if (this.free[i].byteLength < this.free[smallest].byteLength) {
7688
8170
  smallest = i;
7689
8171
  }
7690
8172
  }
7691
- this.free.splice(smallest, 1);
8173
+ const last = this.free.pop();
8174
+ if (smallest < this.free.length)
8175
+ this.free[smallest] = last;
7692
8176
  }
7693
8177
  this.free.push(buffer);
7694
8178
  }
@@ -7748,8 +8232,8 @@ function createStructuredLogger(stream = process.stderr, env = process.env) {
7748
8232
 
7749
8233
  // src/ingress/decode/baked-result.ts
7750
8234
  class BakedIngressResult extends IngressResultBase {
7751
- refresh(buf, body, view) {
7752
- if (buf.byteLength < OUT_DATA_START) {
8235
+ refresh(buf, body, view, len = buf.byteLength) {
8236
+ if (len < OUT_DATA_START) {
7753
8237
  this.setInternalError();
7754
8238
  return;
7755
8239
  }
@@ -7762,7 +8246,7 @@ class BakedIngressResult extends IngressResultBase {
7762
8246
  const queryLenRaw = view.getUint32(OUT_QUERY_JSON_LEN, true);
7763
8247
  const headerVariant = view.getUint8(OUT_HEADER_VARIANT);
7764
8248
  const bodyJsonLenRaw = view.getUint32(OUT_BODY_JSON_LEN, true);
7765
- const layout = sectionLayout(buf.byteLength, cookiesLenRaw, queryLenRaw, bodyJsonLenRaw);
8249
+ const layout = sectionLayout(len, cookiesLenRaw, queryLenRaw, bodyJsonLenRaw);
7766
8250
  if (h0 === 0 && h1 === 0) {
7767
8251
  this.verdict = 1;
7768
8252
  this.errorCode = ERR_CODE_INTERNAL;
@@ -7777,7 +8261,7 @@ class BakedIngressResult extends IngressResultBase {
7777
8261
  this.rateRemaining = h3;
7778
8262
  this.setRateWindow(h2, (flags & FLAG_RATE_LIMITED) !== 0, view);
7779
8263
  this.headerVariant = headerVariant;
7780
- this.setSections(buf, layout, layout.safeBodyJsonLen);
8264
+ this.setSections(buf, layout, layout.safeBodyJsonLen, len);
7781
8265
  this.updateOkTerminal();
7782
8266
  this.isPreflight = (flags & FLAG_IS_PREFLIGHT) !== 0;
7783
8267
  this.corsAllowed = (flags & FLAG_CORS_ALLOWED) !== 0;
@@ -7800,8 +8284,16 @@ class BakedIngressResult extends IngressResultBase {
7800
8284
  this.ok = false;
7801
8285
  }
7802
8286
  bodyJson(copy) {
8287
+ if (copy) {
8288
+ if (this._bodyJsonLen === 0)
8289
+ return this._buf.slice(0, 0);
8290
+ const end = this._bodyJsonStart + this._bodyJsonLen;
8291
+ if (end > this._len)
8292
+ return this._buf.slice(0, 0);
8293
+ return this._buf.slice(this._bodyJsonStart, end);
8294
+ }
7803
8295
  const slice = this.bodyJsonSlice();
7804
- return copy ? slice.slice() : slice;
8296
+ return slice;
7805
8297
  }
7806
8298
  }
7807
8299
 
@@ -7925,6 +8417,13 @@ function cachedOriginBlock(origin) {
7925
8417
  ORIGIN_BLOCK_CACHE.set(origin, block);
7926
8418
  return block;
7927
8419
  }
8420
+ function prewarmOriginBlocks(origins) {
8421
+ for (const origin of origins) {
8422
+ if (typeof origin === "string" && origin.length > 0 && origin.length <= MAX_SMALL_HEADER_BYTES && !origin.includes("*")) {
8423
+ cachedOriginBlock(origin);
8424
+ }
8425
+ }
8426
+ }
7928
8427
 
7929
8428
  // src/ingress/response/baked-response.ts
7930
8429
  init_bytes();
@@ -7977,6 +8476,12 @@ function pooledBodyResponse(handle, bytes, init = {}, timeoutMs = 0) {
7977
8476
 
7978
8477
  // src/ingress/response/baked-response.ts
7979
8478
  var EMPTY_BODY4 = new Uint8Array(0);
8479
+ var ORIGIN_HEADER_CACHE_MAX = 64;
8480
+ function prewarmOriginHeaders(responseHeaders, origins, variant) {
8481
+ for (const origin of origins) {
8482
+ memoizedHeaders(responseHeaders(variant, null, origin));
8483
+ }
8484
+ }
7980
8485
  function buildBakedResponseBuilders(deps) {
7981
8486
  const {
7982
8487
  headerTemplates,
@@ -7986,6 +8491,45 @@ function buildBakedResponseBuilders(deps) {
7986
8491
  originHeaderCache,
7987
8492
  state
7988
8493
  } = deps;
8494
+ function originBaseEntries(variant, origin) {
8495
+ const key = `${variant & 31}\x00${origin}`;
8496
+ const cached = originHeaderCache.get(key);
8497
+ if (cached !== undefined) {
8498
+ return cached;
8499
+ }
8500
+ const template = headerTemplates[variant & 31] ?? headerTemplates[0] ?? [];
8501
+ const entries = new Array(template.length + 1);
8502
+ let i = 0;
8503
+ for (const pair of template) {
8504
+ entries[i++] = pair;
8505
+ }
8506
+ entries[i] = ["access-control-allow-origin", origin];
8507
+ if (originHeaderCache.size >= ORIGIN_HEADER_CACHE_MAX) {
8508
+ const oldest = originHeaderCache.keys().next().value;
8509
+ if (oldest !== undefined)
8510
+ originHeaderCache.delete(oldest);
8511
+ }
8512
+ originHeaderCache.set(key, entries);
8513
+ return entries;
8514
+ }
8515
+ function successHeaders(variant, requestIdHeader, origin, rateRemaining, rateResetSecs) {
8516
+ const needsRequestId = emitRequestIdHeader && requestIdHeader !== null;
8517
+ const needsOrigin = ((variant & HV_CORS_SIMPLE) !== 0 || (variant & HV_CORS_PREFLIGHT) !== 0) && origin !== null;
8518
+ const needsRate = (variant & HV_RATE_ACTIVE) !== 0;
8519
+ const base = needsOrigin ? memoizedHeaders(originBaseEntries(variant, origin)) : memoizedHeaders(headerTemplates[variant & 31] ?? headerTemplates[0] ?? []);
8520
+ if (!needsRequestId && !needsRate) {
8521
+ return base;
8522
+ }
8523
+ const headers = new Headers(base);
8524
+ if (needsRequestId) {
8525
+ headers.set("x-request-id", requestIdHeader);
8526
+ }
8527
+ if (needsRate) {
8528
+ headers.set("ratelimit-remaining", String(rateRemaining ?? 0));
8529
+ headers.set("ratelimit-reset", String(rateResetSecs ?? 0));
8530
+ }
8531
+ return headers;
8532
+ }
7989
8533
  function responseHeaders(variant, requestIdHeader, origin, rateRemaining, rateResetSecs, retryAfterSecs) {
7990
8534
  const template = headerTemplates[variant & 31] ?? headerTemplates[0] ?? [];
7991
8535
  const needsRequestId = emitRequestIdHeader && requestIdHeader !== null;
@@ -7996,19 +8540,7 @@ function buildBakedResponseBuilders(deps) {
7996
8540
  return template;
7997
8541
  }
7998
8542
  if (!needsRequestId && !needsRate && !needsRetry && needsOrigin) {
7999
- const key = `${variant & 31}\x00${origin}`;
8000
- const cached = originHeaderCache.get(key);
8001
- if (cached !== undefined) {
8002
- return cached;
8003
- }
8004
- const entries = new Array(template.length + 1);
8005
- let i = 0;
8006
- for (const pair of template) {
8007
- entries[i++] = pair;
8008
- }
8009
- entries[i] = ["access-control-allow-origin", origin];
8010
- originHeaderCache.set(key, entries);
8011
- return entries;
8543
+ return originBaseEntries(variant, origin);
8012
8544
  }
8013
8545
  let extra = 0;
8014
8546
  if (needsRequestId)
@@ -8069,27 +8601,27 @@ function buildBakedResponseBuilders(deps) {
8069
8601
  if (preflightAllowed) {
8070
8602
  return new Response(null, {
8071
8603
  status: 204,
8072
- headers: responseHeaders(result.headerVariant, ctx.requestIdHeader, ctx.origin, result.rateRemaining, result.rateResetMs > 0 ? secondsFromMs(result.rateResetMs) : undefined, result.retryAfterMs > 0 ? secondsFromMs(result.retryAfterMs) : undefined)
8604
+ headers: memoizedHeaders(responseHeaders(result.headerVariant, ctx.requestIdHeader, ctx.origin, result.rateRemaining, result.rateResetMs > 0 ? secondsFromMs(result.rateResetMs) : undefined, result.retryAfterMs > 0 ? secondsFromMs(result.retryAfterMs) : undefined))
8073
8605
  });
8074
8606
  }
8075
8607
  const status = safeTerminalStatus(result);
8076
8608
  const body = result.errorCode === ERR_CODE_RATE_LIMITED ? rateLimitedBody(result.retryAfterMs) : ERROR_CODE_BODIES[result.errorCode] ?? ERROR_BODIES.internal ?? EMPTY_BODY4;
8077
8609
  return new Response(body, {
8078
8610
  status,
8079
- headers: terminalHeaders(result.headerVariant, ctx, result)
8611
+ headers: memoizedHeaders(terminalHeaders(result.headerVariant, ctx, result))
8080
8612
  });
8081
8613
  }
8082
8614
  function errorResponse(_req, result, status, code, message, ctx) {
8083
8615
  const body = ERROR_BODIES[code] ?? encoder.encode(JSON.stringify({ ok: false, error: { code, message } }));
8084
8616
  return new Response(body, {
8085
8617
  status,
8086
- headers: terminalHeaders(result?.headerVariant ?? HV_JSON, ctx, result)
8618
+ headers: memoizedHeaders(terminalHeaders(result?.headerVariant ?? HV_JSON, ctx, result))
8087
8619
  });
8088
8620
  }
8089
8621
  function internalErrorResponse(ctx, result) {
8090
8622
  return new Response(ERROR_BODIES.internal, {
8091
8623
  status: 500,
8092
- headers: terminalHeaders(result?.headerVariant ?? HV_JSON, ctx, result ?? null)
8624
+ headers: memoizedHeaders(terminalHeaders(result?.headerVariant ?? HV_JSON, ctx, result ?? null))
8093
8625
  });
8094
8626
  }
8095
8627
  function withContentType(headers, contentType) {
@@ -8110,6 +8642,7 @@ function buildBakedResponseBuilders(deps) {
8110
8642
  }
8111
8643
  return {
8112
8644
  responseHeaders,
8645
+ successHeaders,
8113
8646
  terminalHeaders,
8114
8647
  terminalResponse,
8115
8648
  errorResponse,
@@ -8124,6 +8657,8 @@ var EMPTY_BODY5 = new Uint8Array(0);
8124
8657
  var EMPTY_IP = "0.0.0.0";
8125
8658
  var RATE_LIMIT_U32_MAX = 4294967295;
8126
8659
  var MAX_FFI_FAILURES = 3;
8660
+ var PREWARM_POOL_BUFFERS = 16;
8661
+ var PREWARM_POOL_BYTES = 4 * 1024 * 1024;
8127
8662
  function pathForLog(req) {
8128
8663
  try {
8129
8664
  return new URL(req.url).pathname;
@@ -8160,14 +8695,24 @@ function createIngressHandler(options = {}, runtime = {}) {
8160
8695
  const outputBufferSize = clampIngressBufferSize(runtime.outputBufferSize ?? DEFAULT_BAKED_OUTPUT_BUFFER_SIZE, OUT_DATA_START);
8161
8696
  const logger = runtime.logger ?? (runtime.structuredLog === true ? createStructuredLogger() : null);
8162
8697
  const needRequestIdString = !!(emitRequestIdHeader || runtime.onRequest || runtime.onError || runtime.onResponse || logger);
8698
+ const hasOnRequest = runtime.onRequest !== undefined;
8699
+ const hasOnResponse = runtime.onResponse !== undefined;
8163
8700
  const outputPool = new BufferPool({
8164
8701
  initialSize: outputBufferSize,
8165
- maxInFlight: runtime.maxInFlight
8702
+ maxInFlight: runtime.maxInFlight,
8703
+ prefill: PREWARM_POOL_BUFFERS,
8704
+ prefillBytes: PREWARM_POOL_BYTES
8166
8705
  });
8167
8706
  const inputPacker = new IngressInputPacker;
8168
8707
  const responseState = { currentHandle: null, responseBorrowsBuffer: false };
8169
8708
  let ffiFailures = 0;
8170
8709
  let ffiDisabled = false;
8710
+ const noteFfiFailure = () => {
8711
+ ffiFailures++;
8712
+ if (ffiFailures >= MAX_FFI_FAILURES) {
8713
+ ffiDisabled = true;
8714
+ }
8715
+ };
8171
8716
  const originHeaderCache = new Map;
8172
8717
  const cors = options.cors;
8173
8718
  const securityEntries = runtime.enableSecurityHeaders === false ? Object.freeze([]) : Object.freeze(buildBakedSecurityEntries(options.security, options.https, runtime.securityHeaders));
@@ -8193,6 +8738,17 @@ function createIngressHandler(options = {}, runtime = {}) {
8193
8738
  originHeaderCache,
8194
8739
  state: responseState
8195
8740
  });
8741
+ if (headerPlan.cors && typeof cors?.allowOrigin === "object") {
8742
+ const exactOrigins = [];
8743
+ for (const origin of cors.allowOrigin) {
8744
+ if (typeof origin === "string" && origin.length > 0 && origin.length <= 512 && !origin.includes("*")) {
8745
+ exactOrigins.push(origin);
8746
+ }
8747
+ }
8748
+ prewarmOriginBlocks(exactOrigins);
8749
+ const successVariant = HV_JSON | HV_CORS_SIMPLE | (rateEnabled ? HV_RATE_ACTIVE : 0);
8750
+ prewarmOriginHeaders(responseBuilders.responseHeaders, exactOrigins, successVariant);
8751
+ }
8196
8752
  const result = new BakedIngressResult;
8197
8753
  const ctx = {
8198
8754
  requestIdHeader: null,
@@ -8205,7 +8761,8 @@ function createIngressHandler(options = {}, runtime = {}) {
8205
8761
  const requestIdStr = needRequestIdString ? decoder.decode(ridBytes) : "";
8206
8762
  ctx.requestIdHeader = emitRequestIdHeader ? requestIdStr : null;
8207
8763
  ctx.origin = headerPlan.cors ? req.headers.get("origin") : null;
8208
- runtime.onRequest?.(req, requestIdStr, ip);
8764
+ if (hasOnRequest)
8765
+ runtime.onRequest?.(req, requestIdStr, ip);
8209
8766
  const ipStr = ip ?? EMPTY_IP;
8210
8767
  let handle = null;
8211
8768
  responseState.currentHandle = null;
@@ -8223,10 +8780,7 @@ function createIngressHandler(options = {}, runtime = {}) {
8223
8780
  written = bunFFI.ingressHandleComponents(ingressPtr, methodKind, packUrl, packIp ?? "", ridBytes, packedHeaders, body, handle.buffer);
8224
8781
  ffiFailures = 0;
8225
8782
  } catch {
8226
- ffiFailures++;
8227
- if (ffiFailures >= MAX_FFI_FAILURES) {
8228
- ffiDisabled = true;
8229
- }
8783
+ noteFfiFailure();
8230
8784
  const input = inputPacker.packParts(methodKind, packUrl, packIp, ridBytes, packedHeaders);
8231
8785
  written = handler.handleRequestPacked(input, body, handle.buffer);
8232
8786
  }
@@ -8236,18 +8790,14 @@ function createIngressHandler(options = {}, runtime = {}) {
8236
8790
  written = bunFFI.ingressHandlePacked(ingressPtr, input, body, handle.buffer);
8237
8791
  ffiFailures = 0;
8238
8792
  } catch {
8239
- ffiFailures++;
8240
- if (ffiFailures >= MAX_FFI_FAILURES) {
8241
- ffiDisabled = true;
8242
- }
8793
+ noteFfiFailure();
8243
8794
  written = handler.handleRequestPacked(input, body, handle.buffer);
8244
8795
  }
8245
8796
  } else {
8246
8797
  const input = inputPacker.packParts(methodKind, packUrl, packIp, ridBytes, packedHeaders);
8247
8798
  written = handler.handleRequestPacked(input, body, handle.buffer);
8248
8799
  }
8249
- const used = handle.buffer.subarray(0, written);
8250
- result.refresh(used, body ?? EMPTY_BODY5, viewForArrayBuffer(used.buffer, used.byteOffset));
8800
+ result.refresh(handle.buffer, body ?? EMPTY_BODY5, viewForArrayBuffer(handle.buffer.buffer, handle.buffer.byteOffset), written);
8251
8801
  } catch (err) {
8252
8802
  result.setInternalError();
8253
8803
  const error = err instanceof Error ? err : new Error(String(err));
@@ -8263,17 +8813,20 @@ function createIngressHandler(options = {}, runtime = {}) {
8263
8813
  try {
8264
8814
  const out = fn(result, ctx);
8265
8815
  assertSyncCallback(out, "createIngressHandler().run()");
8266
- const status = out instanceof Response ? out.status : safeTerminalStatus(result);
8267
- runtime.onResponse?.(req, result, status, requestIdStr);
8268
- if (logger) {
8269
- logger.request({
8270
- requestId: requestIdStr,
8271
- method: req.method,
8272
- path: pathForLog(req),
8273
- status,
8274
- durationMs: Math.round((performance.now() - startedAt) * 1000) / 1000,
8275
- ip: ipStr
8276
- });
8816
+ if (hasOnResponse || logger !== null) {
8817
+ const status = out instanceof Response ? out.status : safeTerminalStatus(result);
8818
+ if (hasOnResponse)
8819
+ runtime.onResponse?.(req, result, status, requestIdStr);
8820
+ if (logger !== null) {
8821
+ logger.request({
8822
+ requestId: requestIdStr,
8823
+ method: req.method,
8824
+ path: pathForLog(req),
8825
+ status,
8826
+ durationMs: Math.round((performance.now() - startedAt) * 1000) / 1000,
8827
+ ip: ipStr
8828
+ });
8829
+ }
8277
8830
  }
8278
8831
  return out;
8279
8832
  } finally {
@@ -8321,6 +8874,9 @@ function jsonResponse(status, body) {
8321
8874
  function livenessHandler() {
8322
8875
  return (_req) => jsonResponse(200, { status: "ok" });
8323
8876
  }
8877
+ function staticLiveness() {
8878
+ return jsonResponse(200, { status: "ok" });
8879
+ }
8324
8880
  function readinessHandler(check) {
8325
8881
  return async (_req) => {
8326
8882
  if (!check)
@@ -8354,6 +8910,8 @@ var DEFAULT_BUCKETS = [
8354
8910
  5,
8355
8911
  10
8356
8912
  ];
8913
+ var MAX_SERIES_PER_METRIC = 1e4;
8914
+ var SERIES_DROPPED_METRIC = "castrum_metrics_series_dropped_total";
8357
8915
  function labelKey(names, values) {
8358
8916
  if (names.length === 0)
8359
8917
  return "";
@@ -8366,10 +8924,25 @@ function labelKey(names, values) {
8366
8924
  function escapeLabel(v) {
8367
8925
  return v.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
8368
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
+ }
8369
8941
  function createMetrics() {
8370
8942
  const counters = new Map;
8371
8943
  const gauges = new Map;
8372
8944
  const histograms = new Map;
8945
+ let seriesDropped = 0;
8373
8946
  const bucketUpper = (buckets) => [
8374
8947
  ...buckets.map((b) => b),
8375
8948
  Number.POSITIVE_INFINITY
@@ -8414,6 +8987,11 @@ function createMetrics() {
8414
8987
  out.push(...renderGauge(e));
8415
8988
  for (const e of histograms.values())
8416
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
+ }
8417
8995
  return `${out.join(`
8418
8996
  `)}
8419
8997
  `;
@@ -8422,6 +9000,7 @@ function createMetrics() {
8422
9000
  counters.clear();
8423
9001
  gauges.clear();
8424
9002
  histograms.clear();
9003
+ seriesDropped = 0;
8425
9004
  };
8426
9005
  return {
8427
9006
  counter(name, help, labelNames = []) {
@@ -8438,7 +9017,13 @@ function createMetrics() {
8438
9017
  return {
8439
9018
  inc(labels, by = 1) {
8440
9019
  const key = labelKey(names, names.map((n) => labels?.[n] ?? ""));
8441
- entry.values.set(key, (entry.values.get(key) ?? 0) + by);
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
+ }
8442
9027
  }
8443
9028
  };
8444
9029
  },
@@ -8454,14 +9039,27 @@ function createMetrics() {
8454
9039
  gauges.set(name, entry);
8455
9040
  const names = entry.labelNames;
8456
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
+ };
8457
9052
  return {
8458
9053
  inc(labels, by = 1) {
8459
- entry.values.set(keyOf(labels), (entry.values.get(keyOf(labels)) ?? 0) + by);
9054
+ add(labels, by);
8460
9055
  },
8461
9056
  dec(labels, by = 1) {
8462
- entry.values.set(keyOf(labels), (entry.values.get(keyOf(labels)) ?? 0) - by);
9057
+ add(labels, -by);
8463
9058
  },
8464
9059
  set(labels, value) {
9060
+ if (!entry.values.has(keyOf(labels))) {
9061
+ seriesDropped += evictOldestHalf2(entry.values).length;
9062
+ }
8465
9063
  entry.values.set(keyOf(labels), value);
8466
9064
  }
8467
9065
  };
@@ -8484,6 +9082,10 @@ function createMetrics() {
8484
9082
  const key = labelKey(names, names.map((n) => labels?.[n] ?? ""));
8485
9083
  let counts = entry.counts.get(key);
8486
9084
  if (!counts) {
9085
+ const dropped = evictOldestHalf2(entry.counts);
9086
+ for (const k of dropped)
9087
+ entry.sums.delete(k);
9088
+ seriesDropped += dropped.length;
8487
9089
  counts = new Array(entry.buckets.length + 1).fill(0);
8488
9090
  entry.counts.set(key, counts);
8489
9091
  }
@@ -8568,6 +9170,14 @@ function createIngressRouter(options) {
8568
9170
  if (options.copyBody !== undefined)
8569
9171
  baseOpts.copyBody = options.copyBody;
8570
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
+ }
8571
9181
  if (spec.raw) {
8572
9182
  bakedRoutes[path] = { read: spec.raw };
8573
9183
  continue;
@@ -8587,6 +9197,10 @@ function createIngressRouter(options) {
8587
9197
  }
8588
9198
  const handler = createIngressHandler(spec.options ?? {}, runtime);
8589
9199
  compiled[path] = handler;
9200
+ const routeLimit = spec.options?.rateLimit?.limit;
9201
+ if (typeof routeLimit === "number" && routeLimit > 0 && routeLimit !== RATE_LIMIT_U32_MAX && options.getIp === undefined) {
9202
+ warnRateLimitWithoutGetIp();
9203
+ }
8590
9204
  if (spec.responder) {
8591
9205
  bakedRoutes[path] = {
8592
9206
  responder: {
@@ -8638,6 +9252,9 @@ function createIngressRouter(options) {
8638
9252
  if (typeof methodHandler === "function") {
8639
9253
  return methodHandler(req, srv, matched.params);
8640
9254
  }
9255
+ if (methodHandler instanceof Response) {
9256
+ return methodHandler.clone();
9257
+ }
8641
9258
  if (req.method === "OPTIONS" && typeof matched.methods.OPTIONS === "function") {
8642
9259
  return matched.methods.OPTIONS(req, srv, matched.params);
8643
9260
  }
@@ -8811,6 +9428,22 @@ function createIngress(options = {}) {
8811
9428
  }
8812
9429
  };
8813
9430
  }
9431
+ // src/native/ingress-binding.ts
9432
+ init_ffi();
9433
+ var cached2;
9434
+ function getIngressBinding() {
9435
+ if (cached2 !== undefined)
9436
+ return cached2;
9437
+ const ffi = getBunFFI();
9438
+ const addonPath = ffiAddonPath();
9439
+ cached2 = ffi && addonPath ? {
9440
+ addonPath,
9441
+ ingressHandleComponents: ffi.ingressHandleComponents,
9442
+ ingressHandlePacked: ffi.ingressHandlePacked,
9443
+ ingressLayout: ffi.ingressLayout
9444
+ } : null;
9445
+ return cached2;
9446
+ }
8814
9447
  // src/loader/index.ts
8815
9448
  init_rust_ffi();
8816
9449
  init_bytes();
@@ -9921,6 +10554,9 @@ function createPipeline(opts = {}) {
9921
10554
  try {
9922
10555
  body = await readBodyWithLimit(req, maxBodyBytes, true, bodyTimeoutMs);
9923
10556
  } catch (err) {
10557
+ if (isAbortError(err)) {
10558
+ return { terminal: true, response: abortResponse(), result: null, ctx };
10559
+ }
9924
10560
  return {
9925
10561
  terminal: true,
9926
10562
  response: bodyErrorResponse(ingress, req, requestId, err),
@@ -10088,6 +10724,19 @@ function cancelledError() {
10088
10724
  err.name = "TaskCancelledError";
10089
10725
  return err;
10090
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
+ }
10091
10740
  var shared = null;
10092
10741
  function createTaskRuntime(options = {}) {
10093
10742
  if (shared)
@@ -10228,14 +10877,17 @@ function createTaskRuntime(options = {}) {
10228
10877
  queueMicrotask(drainNow);
10229
10878
  }
10230
10879
  function submit(op, args, runOptions) {
10880
+ ensureDoorbell();
10231
10881
  const id = nextId++;
10232
10882
  return new Promise((resolve, reject) => {
10233
10883
  inflight.set(id, { out: null, keep: null, resolve, reject });
10234
10884
  retain();
10235
- if (ffi.taskSubmit(op, args, id) === 0) {
10885
+ const rejected = taskSubmitError(ffi.taskSubmit(op, args, id));
10886
+ if (rejected) {
10236
10887
  inflight.delete(id);
10237
- release();
10238
- reject(new Error("task rejected by the native runtime"));
10888
+ if (inflight.size === 0)
10889
+ release();
10890
+ reject(rejected);
10239
10891
  return;
10240
10892
  }
10241
10893
  const signal = runOptions?.signal;
@@ -10249,14 +10901,17 @@ function createTaskRuntime(options = {}) {
10249
10901
  });
10250
10902
  }
10251
10903
  function submitInto(op, args, output, runOptions) {
10904
+ ensureDoorbell();
10252
10905
  const id = nextId++;
10253
10906
  return new Promise((resolve, reject) => {
10254
10907
  inflight.set(id, { out: output, keep: null, resolve, reject });
10255
10908
  retain();
10256
- if (ffi.taskSubmitOut(op, args, id, output) === 0) {
10909
+ const rejected = taskSubmitError(ffi.taskSubmitOut(op, args, id, output));
10910
+ if (rejected) {
10257
10911
  inflight.delete(id);
10258
- release();
10259
- reject(new Error("task rejected by the native runtime"));
10912
+ if (inflight.size === 0)
10913
+ release();
10914
+ reject(rejected);
10260
10915
  return;
10261
10916
  }
10262
10917
  const signal = runOptions?.signal;
@@ -10270,14 +10925,17 @@ function createTaskRuntime(options = {}) {
10270
10925
  });
10271
10926
  }
10272
10927
  function submitSlice(op, hdr, data, runOptions) {
10928
+ ensureDoorbell();
10273
10929
  const id = nextId++;
10274
10930
  return new Promise((resolve, reject) => {
10275
10931
  inflight.set(id, { out: null, keep: data, resolve, reject });
10276
10932
  retain();
10277
- if (ffi.taskSubmitSlice(op, hdr, data, id) === 0) {
10933
+ const rejected = taskSubmitError(ffi.taskSubmitSlice(op, hdr, data, id));
10934
+ if (rejected) {
10278
10935
  inflight.delete(id);
10279
- release();
10280
- reject(new Error("task rejected by the native runtime"));
10936
+ if (inflight.size === 0)
10937
+ release();
10938
+ reject(rejected);
10281
10939
  return;
10282
10940
  }
10283
10941
  const signal = runOptions?.signal;
@@ -10291,11 +10949,16 @@ function createTaskRuntime(options = {}) {
10291
10949
  });
10292
10950
  }
10293
10951
  const { JSCallback } = __require("bun:ffi");
10294
- const cb = new JSCallback(() => {
10295
- drainNow();
10296
- }, { args: [], returns: "void", threadsafe: true });
10297
- doorbell = cb;
10298
- ffi.taskSetDoorbell(Number(cb.ptr));
10952
+ function ensureDoorbell() {
10953
+ if (doorbell !== null)
10954
+ return;
10955
+ const cb = new JSCallback(() => {
10956
+ drainNow();
10957
+ }, { args: [], returns: "void", threadsafe: true });
10958
+ doorbell = cb;
10959
+ ffi.taskSetDoorbell(Number(cb.ptr));
10960
+ }
10961
+ ensureDoorbell();
10299
10962
  schedule();
10300
10963
  const runtime = {
10301
10964
  gzipDecompress(data, runOptions) {
@@ -10339,6 +11002,7 @@ function createTaskRuntime(options = {}) {
10339
11002
  deferred.reject(cancelledError());
10340
11003
  }
10341
11004
  inflight.clear();
11005
+ ffi.taskSetDoorbell(0);
10342
11006
  ffi.taskShutdown();
10343
11007
  release();
10344
11008
  doorbell?.close();
@@ -10373,12 +11037,30 @@ var opDecision = (op) => {
10373
11037
 
10374
11038
  // index.ts
10375
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
10376
11057
  init_packed();
10377
11058
 
10378
11059
  // src/shared/uuid.ts
10379
11060
  init_uuid();
10380
11061
  var uuidv7 = runtimeUuid.uuidv7;
10381
11062
  export {
11063
+ ABORT_CODE,
10382
11064
  AdaptiveEstimate,
10383
11065
  BakedIngressResult,
10384
11066
  DEFAULT_BODY_TIMEOUT_MS,
@@ -10440,6 +11122,7 @@ export {
10440
11122
  TASK_STATUS_CANCELLED,
10441
11123
  TASK_STATUS_ERROR,
10442
11124
  TASK_STATUS_OK,
11125
+ abortResponse,
10443
11126
  brotliCapacityGuess,
10444
11127
  buildResponseContext,
10445
11128
  buildTerminalResponse,
@@ -10474,12 +11157,15 @@ export {
10474
11157
  errorCodeName,
10475
11158
  errorMessage,
10476
11159
  fallbackHandler,
11160
+ flushMemory,
10477
11161
  generateRequestId,
11162
+ getIngressBinding,
10478
11163
  gracefulShutdown,
10479
11164
  gzipCompressUpperBound,
10480
11165
  headHandler,
10481
11166
  headersForResult,
10482
11167
  healthHandler,
11168
+ isAbortError,
10483
11169
  isNativeOp,
10484
11170
  isProven,
10485
11171
  isValidResponseStatus,
@@ -10516,6 +11202,7 @@ export {
10516
11202
  safeTerminalStatus,
10517
11203
  serializeTraceParent,
10518
11204
  sseResponse,
11205
+ staticLiveness,
10519
11206
  statusForErrorCode,
10520
11207
  unpackBitset,
10521
11208
  unpackByteResults,