castrum 0.9.7 → 0.9.8

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 (84) 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 +501 -5
  12. package/dist/index.js +1648 -1325
  13. package/docs/BENCHMARKS.md +13 -0
  14. package/docs/CASE_STUDY.md +1 -1
  15. package/docs/ENVIRONMENT.md +1 -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/superpowers/plans/2026-09-18-castrum-hardening-cleanup.md +617 -0
  20. package/index.ts +1 -0
  21. package/package.json +3 -2
  22. package/rust/crypto/aead.rs +44 -0
  23. package/rust/crypto/argon2.rs +32 -2
  24. package/rust/crypto/jwt/tests.rs +19 -0
  25. package/rust/crypto/jwt/token.rs +43 -28
  26. package/rust/crypto/session.rs +6 -0
  27. package/rust/ffi/crypto.rs +63 -32
  28. package/rust/ffi/hashing.rs +48 -33
  29. package/rust/ffi/http.rs +62 -36
  30. package/rust/ffi/metrics.rs +6 -2
  31. package/rust/ffi/payload.rs +47 -27
  32. package/rust/ffi/task.rs +51 -32
  33. package/rust/ffi/tests.rs +24 -0
  34. package/rust/ffi/util.rs +40 -0
  35. package/rust/http/headers.rs +13 -0
  36. package/rust/http/multipart.rs +139 -0
  37. package/rust/ingress/native_route.rs +21 -3
  38. package/rust/task/completion.rs +7 -0
  39. package/rust/task/mod.rs +1 -1
  40. package/rust/task/tests.rs +30 -0
  41. package/src/ingress/decode/baked-result.ts +13 -5
  42. package/src/ingress/decode/fast-result.ts +1 -1
  43. package/src/ingress/decode/result-base.ts +16 -2
  44. package/src/ingress/handlers.ts +103 -31
  45. package/src/ingress/headers/memoized-headers.ts +40 -0
  46. package/src/ingress/native-route.ts +25 -6
  47. package/src/ingress/options.ts +26 -2
  48. package/src/ingress/packing/gather-raw-headers.ts +24 -8
  49. package/src/ingress/response/baked-response.ts +118 -28
  50. package/src/ingress/router.ts +14 -2
  51. package/src/ingress/routes/common.ts +26 -10
  52. package/src/ingress/routes/echo.ts +50 -7
  53. package/src/ingress/routes/json-write.ts +21 -8
  54. package/src/ingress/routes/responder.ts +4 -4
  55. package/src/ingress/server-node.ts +33 -14
  56. package/src/ingress/server.ts +3 -4
  57. package/src/ingress/shared.ts +1 -1
  58. package/src/ingress/types.ts +15 -0
  59. package/src/loader/ops.ts +1 -1
  60. package/src/native/ffi/build/codecs-selftest.ts +305 -0
  61. package/src/native/ffi/build/codecs.ts +0 -296
  62. package/src/native/ffi/build/instances-selftest.ts +264 -0
  63. package/src/native/ffi/build/instances.ts +10 -259
  64. package/src/native/ffi/build/parse-selftest.ts +238 -0
  65. package/src/native/ffi/build/parse.ts +11 -234
  66. package/src/native/ffi/build.ts +5 -1
  67. package/src/native/ffi/selftest.ts +3 -3
  68. package/src/native/ffi/symbols.ts +460 -0
  69. package/src/native/ffi.ts +2 -434
  70. package/src/native/ingress-binding.ts +39 -0
  71. package/src/runtime/codec.ts +24 -0
  72. package/src/runtime/types.ts +11 -0
  73. package/src/rust-ffi/batch/build.ts +18 -8
  74. package/src/rust-ffi/batch/types.ts +15 -0
  75. package/src/rust-ffi/options.ts +19 -0
  76. package/src/rust-ffi/scalar/crypto.ts +8 -0
  77. package/src/rust-ffi/scalar/factories.ts +47 -20
  78. package/src/rust-ffi/scalar/http.ts +60 -10
  79. package/src/rust-ffi/scalar/interface.ts +4 -1
  80. package/src/rust-ffi/scalar/payload.ts +27 -1
  81. package/src/shared/buffer-pool.ts +44 -9
  82. package/src/shared/codec.ts +26 -0
  83. package/src/shared/packed/parsers.ts +64 -7
  84. package/src/task/runtime.ts +29 -28
package/dist/index.js CHANGED
@@ -87,7 +87,8 @@ function createBunCodec() {
87
87
  },
88
88
  decodeUtf8: decodeUtf8Bun,
89
89
  decodeUtf8Fatal: decodeUtf8Bun,
90
- decodeUtf8Range: decodeUtf8RangeBun
90
+ decodeUtf8Range: decodeUtf8RangeBun,
91
+ decodeUtf8RangeView: decodeUtf8RangeViewBun
91
92
  };
92
93
  }
93
94
  function bunEncodeUtf8Into(s, dest, offset = 0) {
@@ -122,6 +123,14 @@ function decodeUtf8RangeBun(bytes, start, end) {
122
123
  }
123
124
  return view.toString("latin1", base + start, base + end);
124
125
  }
126
+ function decodeUtf8RangeViewBun(view, start, end) {
127
+ for (let i = start;i < end; i++) {
128
+ if ((view[i] ?? 0) >= 128) {
129
+ return view.toString("utf8", start, end);
130
+ }
131
+ }
132
+ return view.toString("latin1", start, end);
133
+ }
125
134
  function createNodeCodec() {
126
135
  return {
127
136
  encodeUtf8(s) {
@@ -144,6 +153,14 @@ function createNodeCodec() {
144
153
  }
145
154
  }
146
155
  return view.toString("latin1", start, end);
156
+ },
157
+ decodeUtf8RangeView(view, start, end) {
158
+ for (let i = start;i < end; i++) {
159
+ if ((view[i] ?? 0) >= 128) {
160
+ return view.toString("utf8", start, end);
161
+ }
162
+ }
163
+ return view.toString("latin1", start, end);
147
164
  }
148
165
  };
149
166
  }
@@ -264,6 +281,362 @@ var init_builtins = __esm(() => {
264
281
  runtimeBuiltins = createBuiltins(detectedRuntime);
265
282
  });
266
283
 
284
+ // src/native/ffi/symbols.ts
285
+ function buildFFISymbolMap(useBufferLength) {
286
+ const inputAbi = useBufferLength ? ["buffer", "buffer_length"] : ["ptr", "usize"];
287
+ const abi = (shape) => {
288
+ if (!useBufferLength)
289
+ return shape;
290
+ const out = [];
291
+ for (let i = 0;i < shape.length; i++) {
292
+ const t = shape[i];
293
+ if (t === "ptr") {
294
+ out.push("buffer");
295
+ out.push("buffer_length");
296
+ i++;
297
+ } else {
298
+ out.push(t);
299
+ }
300
+ }
301
+ return out;
302
+ };
303
+ const U64_FAST = "u64_fast";
304
+ return {
305
+ castrum_crc32: { args: inputAbi, returns: "u32" },
306
+ castrum_fnv1a64: { args: inputAbi, returns: "u64" },
307
+ castrum_xxh3: { args: inputAbi, returns: "u64" },
308
+ castrum_json_valid: { args: inputAbi, returns: "u8" },
309
+ castrum_utf8_valid: { args: inputAbi, returns: "u8" },
310
+ castrum_hex_encode: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
311
+ castrum_hex_decode: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
312
+ castrum_url_encode: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
313
+ castrum_url_decode: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
314
+ castrum_validate_email: { args: ["cstring"], returns: "u8" },
315
+ castrum_validate_uuid: { args: ["cstring"], returns: "u8" },
316
+ castrum_validate_ipv4: { args: ["cstring"], returns: "u8" },
317
+ castrum_validate_ipv6: { args: ["cstring"], returns: "u8" },
318
+ castrum_validate_email_bytes: { args: inputAbi, returns: "u8" },
319
+ castrum_validate_uuid_bytes: { args: inputAbi, returns: "u8" },
320
+ castrum_validate_ipv4_bytes: { args: inputAbi, returns: "u8" },
321
+ castrum_validate_ipv6_bytes: { args: inputAbi, returns: "u8" },
322
+ castrum_json_sum_ids: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
323
+ castrum_hmac_sha256_verify: {
324
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
325
+ returns: "u8"
326
+ },
327
+ castrum_csrf_verify: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: "u8" },
328
+ castrum_password_verify: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: "u8" },
329
+ castrum_password_verify_bcrypt: {
330
+ args: abi(["ptr", "usize", "cstring"]),
331
+ returns: "u8"
332
+ },
333
+ castrum_ws_accept_key: { args: ["cstring"], returns: "cstring" },
334
+ castrum_ws_accept_key_into: {
335
+ args: abi(["ptr", "usize", "ptr", "usize"]),
336
+ returns: U64_FAST
337
+ },
338
+ castrum_etag: { args: abi(["ptr", "usize", "u8"]), returns: "cstring" },
339
+ castrum_etag_into: { args: abi(["ptr", "usize", "u8", "ptr", "usize"]), returns: U64_FAST },
340
+ castrum_conditional_is_not_modified: {
341
+ args: abi(["usize", "ptr", "usize", "ptr", "usize", "u8"]),
342
+ returns: "u8"
343
+ },
344
+ castrum_media_type_matcher_matches: {
345
+ args: abi(["usize", "ptr", "usize"]),
346
+ returns: "u8"
347
+ },
348
+ castrum_accept_negotiator_negotiate: {
349
+ args: abi(["usize", "ptr", "usize"]),
350
+ returns: "cstring"
351
+ },
352
+ castrum_accept_negotiator_negotiate_server: {
353
+ args: ["usize", "cstring"],
354
+ returns: "cstring"
355
+ },
356
+ castrum_jwt_signer_sign: {
357
+ args: abi(["usize", "ptr", "usize", "i64", "ptr", "usize"]),
358
+ returns: U64_FAST
359
+ },
360
+ castrum_jwt_signer_verify: {
361
+ args: abi(["usize", "ptr", "usize", "i64", "ptr", "usize"]),
362
+ returns: U64_FAST
363
+ },
364
+ castrum_template_render: {
365
+ args: abi(["usize", "ptr", "usize", "ptr", "usize"]),
366
+ returns: U64_FAST
367
+ },
368
+ castrum_schema_validator_validate: {
369
+ args: abi(["usize", "ptr", "usize"]),
370
+ returns: "u8"
371
+ },
372
+ castrum_rate_limiter_check: {
373
+ args: abi(["usize", "cstring", "i64", "ptr", "usize"]),
374
+ returns: U64_FAST
375
+ },
376
+ castrum_rate_limiter_check_key: {
377
+ args: abi(["usize", "i64", "i64", "ptr", "usize"]),
378
+ returns: U64_FAST
379
+ },
380
+ castrum_random_token: { args: ["u32"], returns: "cstring" },
381
+ castrum_random_token_into: { args: ["u32", ...abi(["ptr", "usize"])], returns: U64_FAST },
382
+ castrum_base64_encode: {
383
+ args: abi(["ptr", "usize", "ptr", "usize", "u8", "u8"]),
384
+ returns: U64_FAST
385
+ },
386
+ castrum_base64_decode: {
387
+ args: abi(["ptr", "usize", "ptr", "usize", "u8", "u8"]),
388
+ returns: U64_FAST
389
+ },
390
+ castrum_hmac_sha256: {
391
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
392
+ returns: U64_FAST
393
+ },
394
+ castrum_sign_cookie: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: "cstring" },
395
+ castrum_sign_cookie_into: {
396
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
397
+ returns: U64_FAST
398
+ },
399
+ castrum_verify_cookie: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: "cstring" },
400
+ castrum_verify_cookie_into: {
401
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
402
+ returns: U64_FAST
403
+ },
404
+ castrum_csrf_token: { args: abi(["ptr", "usize"]), returns: "cstring" },
405
+ castrum_csrf_token_into: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
406
+ castrum_password_hash: {
407
+ args: abi(["ptr", "usize", "ptr", "usize", "u32", "u32", "u32", "u32", "ptr", "usize"]),
408
+ returns: U64_FAST
409
+ },
410
+ castrum_password_hash_bcrypt: {
411
+ args: abi(["ptr", "usize", "u32", "ptr", "usize"]),
412
+ returns: U64_FAST
413
+ },
414
+ castrum_pbkdf2_sha256: {
415
+ args: abi(["ptr", "usize", "ptr", "usize", "u32", "u32", "ptr", "usize"]),
416
+ returns: U64_FAST
417
+ },
418
+ castrum_aead_encrypt: {
419
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize", "u8", "ptr", "usize"]),
420
+ returns: U64_FAST
421
+ },
422
+ castrum_aead_decrypt: {
423
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize", "u8", "ptr", "usize"]),
424
+ returns: U64_FAST
425
+ },
426
+ castrum_ws_frame_encode: {
427
+ args: abi(["u8", "ptr", "usize", "u8", "u8", "ptr", "usize"]),
428
+ returns: U64_FAST
429
+ },
430
+ castrum_json_patch: {
431
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
432
+ returns: U64_FAST
433
+ },
434
+ castrum_gzip_compress: {
435
+ args: abi(["ptr", "usize", "u32", "ptr", "usize"]),
436
+ returns: U64_FAST
437
+ },
438
+ castrum_gzip_decompress: {
439
+ args: abi(["ptr", "usize", "usize", "ptr", "usize"]),
440
+ returns: U64_FAST
441
+ },
442
+ castrum_brotli_compress: {
443
+ args: abi(["ptr", "usize", "u32", "ptr", "usize"]),
444
+ returns: U64_FAST
445
+ },
446
+ castrum_brotli_decompress: {
447
+ args: abi(["ptr", "usize", "usize", "ptr", "usize"]),
448
+ returns: U64_FAST
449
+ },
450
+ castrum_gzip_isize: { args: abi(["ptr", "usize"]), returns: "u32" },
451
+ castrum_http_parse_request_packed: {
452
+ args: abi(["ptr", "usize", "ptr", "usize"]),
453
+ returns: U64_FAST
454
+ },
455
+ castrum_query_parse_packed: {
456
+ args: abi(["ptr", "usize", "ptr", "usize"]),
457
+ returns: U64_FAST
458
+ },
459
+ castrum_cookie_parse_packed: {
460
+ args: abi(["ptr", "usize", "ptr", "usize"]),
461
+ returns: U64_FAST
462
+ },
463
+ castrum_http_date_into: { args: ["f64", ...abi(["ptr", "usize"])], returns: U64_FAST },
464
+ castrum_sse_encode_into: {
465
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize", "u8", "u32", "ptr", "usize"]),
466
+ returns: U64_FAST
467
+ },
468
+ castrum_jwt_sign_bytes: {
469
+ args: abi(["ptr", "usize", "ptr", "usize", "i64", "i64"]),
470
+ returns: "cstring"
471
+ },
472
+ castrum_jwt_sign_bytes_into: {
473
+ args: abi(["ptr", "usize", "ptr", "usize", "i64", "i64", "ptr", "usize"]),
474
+ returns: U64_FAST
475
+ },
476
+ castrum_ed25519_generate_keypair: {
477
+ args: abi(["ptr", "usize"]),
478
+ returns: U64_FAST
479
+ },
480
+ castrum_ed25519_sign: {
481
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
482
+ returns: U64_FAST
483
+ },
484
+ castrum_ed25519_verify: {
485
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
486
+ returns: "u32"
487
+ },
488
+ castrum_jwt_eddsa_sign: {
489
+ args: abi(["ptr", "usize", "ptr", "usize", "i64", "i64"]),
490
+ returns: "cstring"
491
+ },
492
+ castrum_jwt_eddsa_verify: {
493
+ args: abi(["ptr", "usize", "ptr", "usize", "i64"]),
494
+ returns: "cstring"
495
+ },
496
+ castrum_ws_frame_decode_packed: {
497
+ args: abi(["ptr", "usize", "ptr", "usize"]),
498
+ returns: U64_FAST
499
+ },
500
+ castrum_multipart_parse_packed: {
501
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
502
+ returns: U64_FAST
503
+ },
504
+ castrum_form_parse_packed: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
505
+ castrum_json_parse_packed: {
506
+ args: abi(["ptr", "usize", "ptr", "usize"]),
507
+ returns: U64_FAST
508
+ },
509
+ castrum_parse_media_type: {
510
+ args: abi(["ptr", "usize", "ptr", "usize"]),
511
+ returns: U64_FAST
512
+ },
513
+ castrum_parse_http_date: {
514
+ args: abi(["ptr", "usize", "ptr", "usize"]),
515
+ returns: U64_FAST
516
+ },
517
+ castrum_parse_accept_encoding: {
518
+ args: abi(["ptr", "usize", "ptr", "usize"]),
519
+ returns: U64_FAST
520
+ },
521
+ castrum_url_encode_query: { args: abi(["ptr", "usize"]), returns: "cstring" },
522
+ castrum_url_resolve: {
523
+ args: abi(["ptr", "usize", "ptr", "usize"]),
524
+ returns: "cstring"
525
+ },
526
+ castrum_url_builder_resolve: {
527
+ args: abi(["usize", "ptr", "usize", "ptr", "usize"]),
528
+ returns: U64_FAST
529
+ },
530
+ castrum_mime_from_extension: { args: ["cstring"], returns: "cstring" },
531
+ castrum_jwt_verify: {
532
+ args: abi(["ptr", "usize", "ptr", "usize", "i64"]),
533
+ returns: "cstring"
534
+ },
535
+ castrum_ingress_handle_packed: {
536
+ args: abi(["usize", "ptr", "usize", "ptr", "usize", "ptr", "usize"]),
537
+ returns: U64_FAST
538
+ },
539
+ castrum_ingress_handle_components: {
540
+ args: abi([
541
+ "usize",
542
+ "u8",
543
+ "cstring",
544
+ "cstring",
545
+ "ptr",
546
+ "usize",
547
+ "ptr",
548
+ "usize",
549
+ "ptr",
550
+ "usize",
551
+ "ptr",
552
+ "usize"
553
+ ]),
554
+ returns: U64_FAST
555
+ },
556
+ castrum_ingress_layout: { args: abi(["ptr", "usize"]), returns: U64_FAST },
557
+ castrum_route_compile: { args: abi(["ptr", "usize"]), returns: U64_FAST },
558
+ castrum_route_run: {
559
+ args: abi(["usize", "ptr", "usize", "ptr", "usize"]),
560
+ returns: U64_FAST
561
+ },
562
+ castrum_route_destroy: { args: ["usize"], returns: "void" },
563
+ castrum_hex_validate_batch: {
564
+ args: abi(["ptr", "usize", "u32", "ptr", "usize"]),
565
+ returns: U64_FAST
566
+ },
567
+ castrum_regex_escape: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
568
+ castrum_metrics_create: { args: [], returns: U64_FAST },
569
+ castrum_metrics_counter: { args: ["usize", "cstring", "cstring"], returns: "u32" },
570
+ castrum_metrics_gauge: { args: ["usize", "cstring", "cstring"], returns: "u32" },
571
+ castrum_metrics_histogram: {
572
+ args: ["usize", "cstring", "cstring", "cstring"],
573
+ returns: "u32"
574
+ },
575
+ castrum_metrics_record: {
576
+ args: abi(["usize", "u32", "ptr", "usize", "f64"]),
577
+ returns: "u8"
578
+ },
579
+ castrum_metrics_gauge_set: {
580
+ args: abi(["usize", "u32", "ptr", "usize", "f64"]),
581
+ returns: "u8"
582
+ },
583
+ castrum_metrics_record_str: {
584
+ args: ["usize", "u32", "cstring", "f64"],
585
+ returns: "u8"
586
+ },
587
+ castrum_metrics_gauge_set_str: {
588
+ args: ["usize", "u32", "cstring", "f64"],
589
+ returns: "u8"
590
+ },
591
+ castrum_regex_escape_str: { args: ["cstring"], returns: "cstring" },
592
+ castrum_hex_validate_batch_str: {
593
+ args: abi(["cstring", "u32", "ptr", "usize"]),
594
+ returns: U64_FAST
595
+ },
596
+ castrum_metrics_render: { args: abi(["usize", "ptr", "usize"]), returns: U64_FAST },
597
+ castrum_metrics_snapshot: { args: abi(["usize", "ptr", "usize"]), returns: U64_FAST },
598
+ castrum_metrics_record_batch: {
599
+ args: abi(["usize", "ptr", "usize"]),
600
+ returns: "u8"
601
+ },
602
+ castrum_query_validate: { args: ["usize", "cstring"], returns: "u8" },
603
+ castrum_cookie_validate: { args: ["usize", "cstring"], returns: "u8" },
604
+ castrum_session_seal: {
605
+ args: ["cstring", "cstring", "i64", "cstring"],
606
+ returns: "cstring"
607
+ },
608
+ castrum_session_open: {
609
+ args: abi(["cstring", "cstring", "ptr", "usize"]),
610
+ returns: U64_FAST
611
+ },
612
+ castrum_session_seal_bytes: {
613
+ args: abi(["ptr", "usize", "ptr", "usize", "i64", "ptr", "usize"]),
614
+ returns: "cstring"
615
+ },
616
+ castrum_session_open_bytes: {
617
+ args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
618
+ returns: U64_FAST
619
+ },
620
+ castrum_task_init: { args: ["u32"], returns: "u32" },
621
+ castrum_task_submit: { args: abi(["u32", "ptr", "usize", "usize"]), returns: "u32" },
622
+ castrum_task_submit_slice: {
623
+ args: abi(["u32", "ptr", "usize", "ptr", "usize", "usize"]),
624
+ returns: "u32"
625
+ },
626
+ castrum_task_submit_out: {
627
+ args: abi(["u32", "ptr", "usize", "usize", "ptr", "usize"]),
628
+ returns: "u32"
629
+ },
630
+ castrum_task_drain: { args: abi(["ptr", "usize"]), returns: U64_FAST },
631
+ castrum_task_pending: { args: [], returns: "u32" },
632
+ castrum_task_cancel: { args: ["usize"], returns: "u32" },
633
+ castrum_task_set_doorbell: { args: ["ptr"], returns: "u32" },
634
+ castrum_task_shutdown: { args: [], returns: "u32" },
635
+ castrum_task_threads: { args: [], returns: "u32" },
636
+ castrum_metrics_destroy: { args: ["usize"], returns: "void" }
637
+ };
638
+ }
639
+
267
640
  // src/shared/runtime.ts
268
641
  var isBun;
269
642
  var init_runtime = __esm(() => {
@@ -272,29 +645,17 @@ var init_runtime = __esm(() => {
272
645
  });
273
646
 
274
647
  // src/shared/codec.ts
275
- var encodeUtf8, encodeUtf8Into, decodeUtf8, decodeUtf8Range, decodeUtf8Fatal;
648
+ var encodeUtf8, encodeUtf8Into, decodeUtf8, decodeUtf8Range, decodeUtf8RangeView, decodeUtf8Fatal;
276
649
  var init_codec2 = __esm(() => {
277
650
  init_codec();
278
651
  encodeUtf8 = runtimeCodec.encodeUtf8;
279
652
  encodeUtf8Into = runtimeCodec.encodeUtf8Into;
280
653
  decodeUtf8 = runtimeCodec.decodeUtf8;
281
654
  decodeUtf8Range = runtimeCodec.decodeUtf8Range;
655
+ decodeUtf8RangeView = runtimeCodec.decodeUtf8RangeView ? runtimeCodec.decodeUtf8RangeView : (view, start, end) => decodeUtf8Range(view, start, end);
282
656
  decodeUtf8Fatal = runtimeCodec.decodeUtf8Fatal;
283
657
  });
284
658
 
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
659
  // src/shared/bytes.ts
299
660
  function toBytes(v) {
300
661
  return typeof v === "string" ? encodeUtf8(v) : v;
@@ -696,365 +1057,160 @@ function buildCodecs(sym, ctx) {
696
1057
  }
697
1058
  };
698
1059
  }
699
- function selfTestCodecs(b) {
1060
+ var init_codecs = __esm(() => {
1061
+ init_codec2();
1062
+ init_util();
1063
+ });
1064
+
1065
+ // src/native/ffi/constants.ts
1066
+ 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;
1067
+ var init_constants = __esm(() => {
1068
+ MAX_DECOMPRESSED = 64 * 1024 * 1024;
1069
+ MAX_JSON_PATCH_OUTPUT = 128 * 1024 * 1024;
1070
+ COMPRESS_INITIAL_CAP = 16 * 1024;
1071
+ COMPRESS_MAX_CAP = 1024 * 1024;
1072
+ DECOMPRESS_FALLBACK_CAP = 4 * 1024 * 1024;
1073
+ SELFTEST_HEX = new Uint8Array([104, 101, 108, 108, 111]);
1074
+ SELFTEST_JSON = new Uint8Array([123, 34, 97, 34, 58, 49, 125]);
1075
+ EMPTY_VIEW = new Uint8Array(0);
1076
+ });
1077
+
1078
+ // src/native/ffi/build/compress.ts
1079
+ function buildCompress(sym, ctx) {
1080
+ const { lenOrView, scratchFor } = ctx;
1081
+ const gzipCompress = sym.castrum_gzip_compress;
1082
+ const gzipDecompress = sym.castrum_gzip_decompress;
1083
+ const brotliCompress = sym.castrum_brotli_compress;
1084
+ const brotliDecompress = sym.castrum_brotli_decompress;
1085
+ const gzipIsize = sym.castrum_gzip_isize;
1086
+ const jsonPatch = sym.castrum_json_patch;
1087
+ return {
1088
+ jsonPatch(doc, patch) {
1089
+ let out = scratchFor(Math.min(Math.max(doc.length, patch.length) + 16, 64 * 1024));
1090
+ let w = Number(jsonPatch(doc, lenOrView(doc), patch, lenOrView(patch), out, lenOrView(out)));
1091
+ if (w > out.length && w <= MAX_JSON_PATCH_OUTPUT) {
1092
+ out = new Uint8Array(w);
1093
+ w = Number(jsonPatch(doc, lenOrView(doc), patch, lenOrView(patch), out, lenOrView(out)));
1094
+ }
1095
+ if (w === 0 || w > out.length) {
1096
+ throw new Error("json patch: output buffer too small or patch inapplicable");
1097
+ }
1098
+ return decodeUtf8(out.subarray(0, w));
1099
+ },
1100
+ gzipCompress(data, level = 6) {
1101
+ 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");
1102
+ },
1103
+ gzipCompressInto(data, output, level = 6) {
1104
+ const w = Number(gzipCompress(data, lenOrView(data), Math.min(level, 9), output, lenOrView(output)));
1105
+ if (w === 0) {
1106
+ throw new Error("gzip compress: invalid input");
1107
+ }
1108
+ if (w > output.length) {
1109
+ throw new Error("gzip compress: output buffer too small");
1110
+ }
1111
+ return w;
1112
+ },
1113
+ gzipDecompress(data, maxDecompressed) {
1114
+ const max = maxDecompressed ?? MAX_DECOMPRESSED;
1115
+ const isize = Number(gzipIsize(data, lenOrView(data)));
1116
+ const initial = isize !== 0 ? Math.min(isize, max) : Math.min(data.length * DECOMPRESS_GUESS_MULTIPLIER_GZIP, DECOMPRESS_FALLBACK_CAP);
1117
+ return growExact((out) => Number(gzipDecompress(data, lenOrView(data), max, out, lenOrView(out))), initial, max, "gzip decompress: invalid stream or exceeded max decompressed size");
1118
+ },
1119
+ gzipDecompressInto(data, output, maxDecompressed) {
1120
+ const max = maxDecompressed ?? MAX_DECOMPRESSED;
1121
+ const w = Number(gzipDecompress(data, lenOrView(data), max, output, lenOrView(output)));
1122
+ if (w === 0) {
1123
+ throw new Error("gzip decompress: invalid stream or exceeded max decompressed size");
1124
+ }
1125
+ if (w > output.length) {
1126
+ throw new Error("gzip decompress: output buffer too small");
1127
+ }
1128
+ return w;
1129
+ },
1130
+ gzipIsize(data) {
1131
+ return Number(gzipIsize(data, lenOrView(data)));
1132
+ },
1133
+ brotliCompress(data, quality = 5) {
1134
+ 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");
1135
+ },
1136
+ brotliCompressInto(data, output, quality = 5) {
1137
+ const w = Number(brotliCompress(data, lenOrView(data), Math.min(quality, 11), output, lenOrView(output)));
1138
+ if (w === 0) {
1139
+ throw new Error("brotli compress: invalid input");
1140
+ }
1141
+ if (w > output.length) {
1142
+ throw new Error("brotli compress: output buffer too small");
1143
+ }
1144
+ return w;
1145
+ },
1146
+ brotliDecompress(data, maxDecompressed) {
1147
+ const max = maxDecompressed ?? MAX_DECOMPRESSED;
1148
+ 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");
1149
+ },
1150
+ brotliDecompressInto(data, output, maxDecompressed) {
1151
+ const max = maxDecompressed ?? MAX_DECOMPRESSED;
1152
+ const w = Number(brotliDecompress(data, lenOrView(data), max, output, lenOrView(output)));
1153
+ if (w === 0) {
1154
+ throw new Error("brotli decompress: invalid stream or exceeded max decompressed size");
1155
+ }
1156
+ if (w > output.length) {
1157
+ throw new Error("brotli decompress: output buffer too small");
1158
+ }
1159
+ return w;
1160
+ }
1161
+ };
1162
+ }
1163
+ function selfTestCompress(b) {
700
1164
  const enc = { encode: encodeUtf8 };
701
1165
  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") {
1166
+ const patched = b.jsonPatch(enc.encode(`{"a":"b"}`), enc.encode(`[{"op":"add","path":"/c","value":"d"}]`));
1167
+ if (!patched.includes(`"c":"d"`)) {
721
1168
  return false;
722
1169
  }
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")) {
1170
+ const gz = b.gzipCompress(SELFTEST_HEX);
1171
+ if (dec.decode(b.gzipDecompress(gz)) !== "hello") {
724
1172
  return false;
725
1173
  }
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"))) {
1174
+ const br = b.brotliCompress(SELFTEST_HEX);
1175
+ if (dec.decode(b.brotliDecompress(br)) !== "hello") {
727
1176
  return false;
728
1177
  }
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;
1178
+ let decompressThrew = false;
734
1179
  try {
735
- b.jsonSumIds(enc.encode("nope"));
1180
+ b.gzipDecompress(enc.encode("not-a-gzip-stream"));
736
1181
  } catch {
737
- sumInvalidThrew = true;
738
- }
739
- if (!sumInvalidThrew)
740
- 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;
1182
+ decompressThrew = true;
785
1183
  }
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") {
1184
+ if (!decompressThrew) {
790
1185
  return false;
791
1186
  }
792
- if (!b.hmacSha256Verify(hmacKey, hmacData, hmacSig)) {
793
- return false;
1187
+ decompressThrew = false;
1188
+ try {
1189
+ b.brotliDecompress(enc.encode("not-brotli-stream"));
1190
+ } catch {
1191
+ decompressThrew = true;
794
1192
  }
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") {
1193
+ if (!decompressThrew) {
797
1194
  return false;
798
1195
  }
799
- if (b.wsAcceptKey("dGhlIHNhbXBsZSBub25jZQ==") !== "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") {
1196
+ const gzInto = new Uint8Array(64);
1197
+ const gzW = b.gzipCompressInto(SELFTEST_HEX, gzInto);
1198
+ if (gzW === 0 || dec.decode(b.gzipDecompress(gzInto.subarray(0, gzW))) !== "hello") {
800
1199
  return false;
801
1200
  }
802
- if (b.etag(SELFTEST_HEX).length !== 10 || b.etag(SELFTEST_HEX, true).length !== 12) {
1201
+ const brInto = new Uint8Array(64);
1202
+ const brW = b.brotliCompressInto(SELFTEST_HEX, brInto);
1203
+ if (brW === 0 || dec.decode(b.brotliDecompress(brInto.subarray(0, brW))) !== "hello") {
803
1204
  return false;
804
1205
  }
805
- if (b.base64Encode(SELFTEST_HEX) !== "aGVsbG8=") {
1206
+ const gzDst = new Uint8Array(16);
1207
+ const gzDW = b.gzipDecompressInto(gzInto.subarray(0, gzW), gzDst);
1208
+ if (gzDW === 0 || dec.decode(gzDst.subarray(0, gzDW)) !== "hello") {
806
1209
  return false;
807
1210
  }
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") {
1211
+ const brDst = new Uint8Array(16);
1212
+ const brDW = b.brotliDecompressInto(brInto.subarray(0, brW), brDst);
1213
+ if (brDW === 0 || dec.decode(brDst.subarray(0, brDW)) !== "hello") {
1058
1214
  return false;
1059
1215
  }
1060
1216
  return true;
@@ -1113,7 +1269,10 @@ function buildInstances(sym, ctx) {
1113
1269
  return acceptNegotiatorNegotiateRaw(inner, header, lenOrView(header));
1114
1270
  },
1115
1271
  acceptNegotiatorNegotiateServer(inner, header) {
1116
- return acceptNegotiatorNegotiateServerRaw(inner, decodeUtf8(header));
1272
+ const text = decodeUtf8(header);
1273
+ if (hasNul(text))
1274
+ return null;
1275
+ return acceptNegotiatorNegotiateServerRaw(inner, text);
1117
1276
  },
1118
1277
  jwtSignerSign(inner, claimsJson, nowSeconds) {
1119
1278
  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 +1319,8 @@ function buildInstances(sym, ctx) {
1160
1319
  return Number(sessionOpenBytesSym(token, lenOrView(token), secret, lenOrView(secret), output, lenOrView(output)));
1161
1320
  },
1162
1321
  rateLimiterCheck(inner, key, nowMs) {
1322
+ if (hasNul(key))
1323
+ return { allowed: false, remaining: 0, resetMs: 0 };
1163
1324
  const out = rateScratch;
1164
1325
  const w = Number(rateLimiterCheckRaw(inner, key, BigInt(Math.trunc(nowMs)), out, lenOrView(out)));
1165
1326
  if (w === 0)
@@ -1252,190 +1413,6 @@ function buildInstances(sym, ctx) {
1252
1413
  }
1253
1414
  };
1254
1415
  }
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
1416
  var init_instances = __esm(() => {
1440
1417
  init_codec2();
1441
1418
  init_constants();
@@ -1623,15 +1600,702 @@ function selfTestMetrics(b) {
1623
1600
  } catch (err) {
1624
1601
  console.error("METRICS SELFTEST THROW:", err);
1625
1602
  return false;
1626
- } finally {
1627
- b.metricsDestroy(handle);
1603
+ } finally {
1604
+ b.metricsDestroy(handle);
1605
+ }
1606
+ }
1607
+ var METRICS_DECLARE_ERR = 4294967295, EMPTY;
1608
+ var init_metrics = __esm(() => {
1609
+ init_codec2();
1610
+ init_util();
1611
+ EMPTY = new Uint8Array(0);
1612
+ });
1613
+
1614
+ // src/native/ffi/build/parse.ts
1615
+ function sseMemoEncode(slot, s) {
1616
+ if (s === slot.str)
1617
+ return slot.bytes;
1618
+ const bytes = encodeUtf8(s);
1619
+ slot.str = s;
1620
+ slot.bytes = bytes;
1621
+ return bytes;
1622
+ }
1623
+ function buildParse(sym, ctx) {
1624
+ const { lenOrView, scratchFor, dateScratch, dateScratchView } = ctx;
1625
+ const httpParsePacked = sym.castrum_http_parse_request_packed;
1626
+ const queryParsePacked = sym.castrum_query_parse_packed;
1627
+ const cookieParsePacked = sym.castrum_cookie_parse_packed;
1628
+ const httpDateIntoRaw = sym.castrum_http_date_into;
1629
+ const sseEncodeIntoRaw = sym.castrum_sse_encode_into;
1630
+ const jsonParsePackedSym = sym.castrum_json_parse_packed;
1631
+ const wsFrameDecodePacked = sym.castrum_ws_frame_decode_packed;
1632
+ const multipartParsePacked = sym.castrum_multipart_parse_packed;
1633
+ const formParsePacked = sym.castrum_form_parse_packed;
1634
+ const wsFrameEncode = sym.castrum_ws_frame_encode;
1635
+ const parseMediaTypeSym = sym.castrum_parse_media_type;
1636
+ const parseHttpDateSym = sym.castrum_parse_http_date;
1637
+ const parseAcceptEncodingSym = sym.castrum_parse_accept_encoding;
1638
+ const urlEncodeQuerySym = sym.castrum_url_encode_query;
1639
+ const urlResolveSym = sym.castrum_url_resolve;
1640
+ const mimeFromExtensionSym = sym.castrum_mime_from_extension;
1641
+ return {
1642
+ httpParseRequestPackedInto(input, output) {
1643
+ const w = Number(httpParsePacked(input, lenOrView(input), output, lenOrView(output)));
1644
+ if (w === 0 && input.length !== 0) {
1645
+ throw new Error("http parse: output buffer too small or malformed request");
1646
+ }
1647
+ return w;
1648
+ },
1649
+ queryParsePackedInto(input, output) {
1650
+ const w = Number(queryParsePacked(input, lenOrView(input), output, lenOrView(output)));
1651
+ if (w === 0 || w > output.length) {
1652
+ throw new Error("query parse: output buffer too small");
1653
+ }
1654
+ return w;
1655
+ },
1656
+ cookieParsePackedInto(input, output) {
1657
+ const w = Number(cookieParsePacked(input, lenOrView(input), output, lenOrView(output)));
1658
+ if (w === 0 || w > output.length) {
1659
+ throw new Error("cookie parse: output buffer too small");
1660
+ }
1661
+ return w;
1662
+ },
1663
+ wsFrameEncode(opcode, payload, mask, fin) {
1664
+ const out = allocOut(payload.length + 14);
1665
+ const w = Number(wsFrameEncode(opcode, payload, lenOrView(payload), flag(mask), flag(fin), out, lenOrView(out)));
1666
+ if (w === 0) {
1667
+ throw new Error("ws frame encode: output buffer too small");
1668
+ }
1669
+ return out.subarray(0, w);
1670
+ },
1671
+ wsFrameEncodeInto(opcode, payload, mask, fin, output) {
1672
+ const need = payload.length + 14;
1673
+ if (output.length < need) {
1674
+ throw new Error("ws frame encode: output buffer too small");
1675
+ }
1676
+ const w = Number(wsFrameEncode(opcode, payload, lenOrView(payload), flag(mask), flag(fin), output, lenOrView(output)));
1677
+ if (w === 0) {
1678
+ throw new Error("ws frame encode: output buffer too small");
1679
+ }
1680
+ return w;
1681
+ },
1682
+ wsFrameDecodePacked(data) {
1683
+ const out = new Uint8Array(data.length + 6);
1684
+ const w = Number(wsFrameDecodePacked(data, lenOrView(data), out, lenOrView(out)));
1685
+ return w === 0 ? null : out.subarray(0, w);
1686
+ },
1687
+ wsFrameDecodePackedInto(data, output) {
1688
+ const w = Number(wsFrameDecodePacked(data, lenOrView(data), output, lenOrView(output)));
1689
+ return w === 0 ? null : w;
1690
+ },
1691
+ multipartParsePacked(body, boundary) {
1692
+ return growExact((out) => Number(multipartParsePacked(body, lenOrView(body), boundary, lenOrView(boundary), out, lenOrView(out))), Math.min(body.length + boundary.length + 64, 64 * 1024), 128 * 1024 * 1024, "multipart parse: output buffer too small");
1693
+ },
1694
+ multipartParsePackedInto(body, boundary, output) {
1695
+ const w = Number(multipartParsePacked(body, lenOrView(body), boundary, lenOrView(boundary), output, lenOrView(output)));
1696
+ if (w === 0 || w > output.length) {
1697
+ throw new Error("multipart parse: output buffer too small or malformed body");
1698
+ }
1699
+ return w;
1700
+ },
1701
+ formParsePackedInto(input, output) {
1702
+ const w = Number(formParsePacked(input, lenOrView(input), output, lenOrView(output)));
1703
+ if (w === 0 || w > output.length) {
1704
+ throw new Error("form parse: output buffer too small");
1705
+ }
1706
+ return w;
1707
+ },
1708
+ jsonParsePacked(input) {
1709
+ return growExact((out) => Number(jsonParsePackedSym(input, lenOrView(input), out, lenOrView(out))), Math.min(input.length + (input.length >> 1), 16 * 1024 * 1024), Math.max(1024 * 1024, input.length * 16), "json parse packed: invalid JSON or output buffer too small");
1710
+ },
1711
+ parseMediaType(input) {
1712
+ let out = scratchFor(input.length * 2 + 128);
1713
+ let w = Number(parseMediaTypeSym(input, lenOrView(input), out, lenOrView(out)));
1714
+ if (w > out.length) {
1715
+ out = new Uint8Array(w);
1716
+ w = Number(parseMediaTypeSym(input, lenOrView(input), out, lenOrView(out)));
1717
+ }
1718
+ if (w === 0 || w > out.length) {
1719
+ throw new Error("media type parse: invalid media type or output buffer too small");
1720
+ }
1721
+ return out.subarray(0, w);
1722
+ },
1723
+ parseHttpDate(input) {
1724
+ const out = dateScratch;
1725
+ const w = Number(parseHttpDateSym(input, lenOrView(input), out, lenOrView(out)));
1726
+ if (w === 0) {
1727
+ throw new Error("http date parse: output buffer too small");
1728
+ }
1729
+ if (out[0] === 0)
1730
+ return null;
1731
+ return dateScratchView.getBigInt64(1, true);
1732
+ },
1733
+ parseAcceptEncoding(input) {
1734
+ let out = scratchFor(input.length * 2 + 128);
1735
+ let w = Number(parseAcceptEncodingSym(input, lenOrView(input), out, lenOrView(out)));
1736
+ if (w > out.length) {
1737
+ out = new Uint8Array(w);
1738
+ w = Number(parseAcceptEncodingSym(input, lenOrView(input), out, lenOrView(out)));
1739
+ }
1740
+ if (w === 0 || w > out.length) {
1741
+ throw new Error("accept encoding parse: output buffer too small");
1742
+ }
1743
+ return out.subarray(0, w);
1744
+ },
1745
+ urlEncodeQuery(input) {
1746
+ return urlEncodeQuerySym(input, lenOrView(input));
1747
+ },
1748
+ urlResolve(base, reference) {
1749
+ return urlResolveSym(base, lenOrView(base), reference, lenOrView(reference));
1750
+ },
1751
+ mimeFromExtension(ext) {
1752
+ return mimeFromExtensionSym(ext);
1753
+ },
1754
+ httpDateInto(secs, output) {
1755
+ const w = Number(httpDateIntoRaw(secs, output, lenOrView(output)));
1756
+ if (w === 0) {
1757
+ throw new Error("http date: output buffer too small or year out of range");
1758
+ }
1759
+ return w;
1760
+ },
1761
+ httpDate(secs) {
1762
+ const out = scratchFor(32);
1763
+ const w = Number(httpDateIntoRaw(secs ?? 0, out, lenOrView(out)));
1764
+ if (w !== 0)
1765
+ return decodeUtf8(out.subarray(0, w));
1766
+ return new Date((secs ?? 0) * 1000).toUTCString();
1767
+ },
1768
+ sseEncodeEvent(event, data, id, retry) {
1769
+ const ev = event === null ? EMPTY_VIEW : sseMemoEncode(sseMemoEvent, event);
1770
+ const idv = id === null ? EMPTY_VIEW : sseMemoEncode(sseMemoId, id);
1771
+ const flags = (event === null ? 0 : 1) | (id === null ? 0 : 2) | (retry === null ? 0 : 4);
1772
+ return growExact((out) => Number(sseEncodeIntoRaw(ev, lenOrView(ev), data, lenOrView(data), idv, lenOrView(idv), flags, retry ?? 0, out, lenOrView(out))), data.length + 64, 1 << 20, "sse encode: output buffer too small");
1773
+ },
1774
+ sseEncodeEventInto(event, data, id, retry, output) {
1775
+ const ev = event === null ? EMPTY_VIEW : sseMemoEncode(sseMemoEvent, event);
1776
+ const idv = id === null ? EMPTY_VIEW : sseMemoEncode(sseMemoId, id);
1777
+ const flags = (event === null ? 0 : 1) | (id === null ? 0 : 2) | (retry === null ? 0 : 4);
1778
+ const w = Number(sseEncodeIntoRaw(ev, lenOrView(ev), data, lenOrView(data), idv, lenOrView(idv), flags, retry ?? 0, output, lenOrView(output)));
1779
+ if (w === 0) {
1780
+ throw new Error("sse encode: invalid UTF-8 in event/id");
1781
+ }
1782
+ if (w > output.length) {
1783
+ throw new Error("sse encode: output buffer too small");
1784
+ }
1785
+ return w;
1786
+ }
1787
+ };
1788
+ }
1789
+ var sseMemoEvent, sseMemoId;
1790
+ var init_parse = __esm(() => {
1791
+ init_codec2();
1792
+ init_constants();
1793
+ init_util();
1794
+ sseMemoEvent = { str: null, bytes: EMPTY_VIEW };
1795
+ sseMemoId = { str: null, bytes: EMPTY_VIEW };
1796
+ });
1797
+
1798
+ // src/native/ffi/build/task.ts
1799
+ function buildTask(sym, ctx) {
1800
+ const { lenOrView } = ctx;
1801
+ const init = sym.castrum_task_init;
1802
+ const submit = sym.castrum_task_submit;
1803
+ const submitOut = sym.castrum_task_submit_out;
1804
+ const submitSlice = sym.castrum_task_submit_slice;
1805
+ const drain = sym.castrum_task_drain;
1806
+ const pending = sym.castrum_task_pending;
1807
+ const cancel = sym.castrum_task_cancel;
1808
+ const setDoorbell = sym.castrum_task_set_doorbell;
1809
+ const shutdown = sym.castrum_task_shutdown;
1810
+ const threads = sym.castrum_task_threads;
1811
+ return {
1812
+ taskInit: (n) => Number(init(n)),
1813
+ taskSubmit: (op, args, taskId) => Number(submit(op, args, lenOrView(args), taskId)),
1814
+ taskSubmitOut: (op, args, taskId, output) => Number(submitOut(op, args, lenOrView(args), taskId, output, lenOrView(output))),
1815
+ taskSubmitSlice: (op, hdr, data, taskId) => Number(submitSlice(op, hdr, lenOrView(hdr), data, lenOrView(data), taskId)),
1816
+ taskDrain: (output) => Number(drain(output, lenOrView(output))),
1817
+ taskPending: () => Number(pending()),
1818
+ taskCancel: (taskId) => Number(cancel(taskId)),
1819
+ taskSetDoorbell: (ptr) => Number(setDoorbell(ptr)),
1820
+ taskShutdown: () => Number(shutdown()),
1821
+ taskThreads: () => Number(threads())
1822
+ };
1823
+ }
1824
+ function selfTestTask(b) {
1825
+ try {
1826
+ if (typeof b.taskPending() !== "number")
1827
+ return false;
1828
+ if (typeof b.taskThreads() !== "number" || b.taskThreads() < 0)
1829
+ return false;
1830
+ if (b.taskSetDoorbell(0) !== 1)
1831
+ return false;
1832
+ if (typeof b.taskDrain(new Uint8Array(4)) !== "number")
1833
+ return false;
1834
+ if (typeof b.taskSubmitOut !== "function")
1835
+ return false;
1836
+ if (typeof b.taskSubmitSlice !== "function")
1837
+ return false;
1838
+ return true;
1839
+ } catch {
1840
+ return false;
1841
+ }
1842
+ }
1843
+
1844
+ // src/native/ffi/build.ts
1845
+ function build(sym, useBufferLength) {
1846
+ const oneArg = (raw, v) => useBufferLength ? raw(v, v) : raw(v, v.length);
1847
+ const lenOrView = (v) => useBufferLength ? v : v.length;
1848
+ let encodeScratch = new Uint8Array(0);
1849
+ const scratchFor = (size) => {
1850
+ if (encodeScratch.byteLength < size)
1851
+ encodeScratch = new Uint8Array(size);
1852
+ return encodeScratch;
1853
+ };
1854
+ const jsonSumOut = new Uint8Array(9);
1855
+ const jsonSumView = new DataView(jsonSumOut.buffer);
1856
+ const dateScratch = new Uint8Array(9);
1857
+ const dateScratchView = new DataView(dateScratch.buffer);
1858
+ const rateScratch = new Uint8Array(13);
1859
+ const rateScratchView = new DataView(rateScratch.buffer);
1860
+ const ctx = {
1861
+ useBufferLength,
1862
+ lenOrView,
1863
+ oneArg,
1864
+ scratchFor,
1865
+ jsonSumOut,
1866
+ jsonSumView,
1867
+ dateScratch,
1868
+ dateScratchView,
1869
+ rateScratch,
1870
+ rateScratchView
1871
+ };
1872
+ return {
1873
+ ...buildCodecs(sym, ctx),
1874
+ ...buildCompress(sym, ctx),
1875
+ ...buildParse(sym, ctx),
1876
+ ...buildInstances(sym, ctx),
1877
+ ...buildMetrics(sym, ctx),
1878
+ ...buildTask(sym, ctx)
1879
+ };
1880
+ }
1881
+ var init_build = __esm(() => {
1882
+ init_codecs();
1883
+ init_compress();
1884
+ init_instances();
1885
+ init_metrics();
1886
+ init_parse();
1887
+ });
1888
+
1889
+ // src/native/ffi/build/codecs-selftest.ts
1890
+ function selfTestCodecs(b) {
1891
+ const enc = { encode: encodeUtf8 };
1892
+ const dec = { decode: decodeUtf8 };
1893
+ if (b.crc32(enc.encode("123456789")) !== 3421780262)
1894
+ return false;
1895
+ if (b.fnv1a64(enc.encode("foobar")) !== 0x85944171f73967e8n)
1896
+ return false;
1897
+ if (b.xxh3(new Uint8Array(0)) !== 0x2d06800538d394c2n)
1898
+ return false;
1899
+ if (b.jsonValid(SELFTEST_JSON) !== true || b.jsonValid(enc.encode("{not json")) !== false) {
1900
+ return false;
1901
+ }
1902
+ if (!b.utf8Valid(enc.encode("héllo")) || b.utf8Valid(new Uint8Array([255, 254]))) {
1903
+ return false;
1904
+ }
1905
+ const hexOut = new Uint8Array(SELFTEST_HEX.length * 2);
1906
+ if (b.hexEncodeInto(SELFTEST_HEX, hexOut) !== 10 || dec.decode(hexOut) !== "68656c6c6f") {
1907
+ return false;
1908
+ }
1909
+ const urlInput = enc.encode("a b/c");
1910
+ const urlOut = new Uint8Array(9);
1911
+ if (b.urlEncodeInto(urlInput, urlOut) !== 9 || dec.decode(urlOut) !== "a%20b%2Fc") {
1912
+ return false;
1913
+ }
1914
+ 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")) {
1915
+ return false;
1916
+ }
1917
+ 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"))) {
1918
+ return false;
1919
+ }
1920
+ if (b.jsonSumIds(enc.encode(`[{"id":1},{"id":2}]`)) !== 3n)
1921
+ return false;
1922
+ if (b.jsonSumIds(enc.encode(`[{"id":0},{"id":0}]`)) !== 0n)
1923
+ return false;
1924
+ let sumInvalidThrew = false;
1925
+ try {
1926
+ b.jsonSumIds(enc.encode("nope"));
1927
+ } catch {
1928
+ sumInvalidThrew = true;
1929
+ }
1930
+ if (!sumInvalidThrew)
1931
+ return false;
1932
+ {
1933
+ const ids = enc.encode(`507f1f77bcf86cd799439011
1934
+ zz
1935
+ 507F1F77BCF86CD799439012`);
1936
+ const tiny = new Uint8Array(2);
1937
+ const needed = b.hexValidateBatchInto(ids, 24, tiny);
1938
+ if (needed !== 3)
1939
+ return false;
1940
+ const out = new Uint8Array(needed);
1941
+ if (b.hexValidateBatchInto(ids, 24, out) !== 3)
1942
+ return false;
1943
+ if (out[0] !== 1 || out[1] !== 0 || out[2] !== 1)
1944
+ return false;
1945
+ let widthThrew = false;
1946
+ try {
1947
+ b.hexValidateBatchInto(ids, 0, new Uint8Array(3));
1948
+ } catch {
1949
+ widthThrew = true;
1950
+ }
1951
+ if (!widthThrew)
1952
+ return false;
1953
+ }
1954
+ {
1955
+ const out = new Uint8Array(64);
1956
+ const w = b.regexEscapeInto(enc.encode("a.c(x)"), out);
1957
+ if (w !== 9 || dec.decode(out.subarray(0, w)) !== "a\\.c\\(x\\)")
1958
+ return false;
1959
+ const plain = new Uint8Array(16);
1960
+ const wp = b.regexEscapeInto(enc.encode("hello"), plain);
1961
+ if (wp !== 5 || dec.decode(plain.subarray(0, wp)) !== "hello")
1962
+ return false;
1963
+ if (b.regexEscapeInto(enc.encode("a.b"), new Uint8Array(2)) !== 4)
1964
+ return false;
1965
+ if (b.regexEscapeStr("a.c(x)") !== "a\\.c\\(x\\)")
1966
+ return false;
1967
+ if (b.regexEscapeStr("plain text") !== "plain text")
1968
+ return false;
1969
+ }
1970
+ {
1971
+ const out = new Uint8Array(8);
1972
+ const w = b.hexValidateBatchStr(`507f1f77bcf86cd799439011
1973
+ zz`, 24, out);
1974
+ if (w !== 2 || out[0] !== 1 || out[1] !== 0)
1975
+ return false;
1976
+ }
1977
+ const hmacKey = new Uint8Array(20).fill(11);
1978
+ const hmacData = enc.encode("Hi There");
1979
+ const hmacSig = enc.encode("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7");
1980
+ if (b.hmacSha256(hmacKey, hmacData) !== "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7") {
1981
+ return false;
1982
+ }
1983
+ if (!b.hmacSha256Verify(hmacKey, hmacData, hmacSig)) {
1984
+ return false;
1985
+ }
1986
+ const decoded = b.hexDecode(enc.encode("68656c6c6f"));
1987
+ if (dec.decode(decoded) !== "hello" || dec.decode(b.urlDecode(enc.encode("a%20b%2Fc"))) !== "a b/c") {
1988
+ return false;
1989
+ }
1990
+ if (b.wsAcceptKey("dGhlIHNhbXBsZSBub25jZQ==") !== "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") {
1991
+ return false;
1992
+ }
1993
+ if (b.etag(SELFTEST_HEX).length !== 10 || b.etag(SELFTEST_HEX, true).length !== 12) {
1994
+ return false;
1995
+ }
1996
+ if (b.base64Encode(SELFTEST_HEX) !== "aGVsbG8=") {
1997
+ return false;
1998
+ }
1999
+ if (dec.decode(b.base64Decode(enc.encode("aGVsbG8="))) !== "hello") {
2000
+ return false;
2001
+ }
2002
+ const secret = enc.encode("s3cr3t-secret");
2003
+ const signed = b.signCookie(SELFTEST_HEX, secret);
2004
+ const verified = b.verifyCookie(enc.encode(signed), secret);
2005
+ if (verified === null || verified !== "hello") {
2006
+ return false;
2007
+ }
2008
+ if (b.verifyCookie(enc.encode("tampered.0000"), secret) !== null) {
2009
+ return false;
2010
+ }
2011
+ const csrfTokenStr = b.csrfToken(secret);
2012
+ if (csrfTokenStr.length !== 129 || !b.csrfVerify(enc.encode(csrfTokenStr), secret)) {
2013
+ return false;
2014
+ }
2015
+ const pw = enc.encode("correct horse battery staple");
2016
+ const salt = enc.encode("salty-salt-16b");
2017
+ const phc = b.passwordHash(pw, salt, 8, 1, 1, 16);
2018
+ if (phc.length === 0 || !b.passwordVerify(pw, enc.encode(phc))) {
2019
+ return false;
2020
+ }
2021
+ const bcryptPhc = b.passwordHashBcrypt(pw, 4);
2022
+ if (bcryptPhc.length === 0 || !b.passwordVerifyBcrypt(pw, bcryptPhc)) {
2023
+ return false;
2024
+ }
2025
+ const dk = b.pbkdf2Sha256(enc.encode("password"), enc.encode("salt"), 1, 32);
2026
+ if (b.hexEncode(dk) !== "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b") {
2027
+ return false;
2028
+ }
2029
+ const aeadKey = new Uint8Array(32).fill(66);
2030
+ const nonce = new Uint8Array(12).fill(7);
2031
+ const ct = b.aeadEncrypt(aeadKey, nonce, SELFTEST_HEX, 0);
2032
+ const pt = b.aeadDecrypt(aeadKey, nonce, ct, 0);
2033
+ if (pt === null || dec.decode(pt) !== "hello") {
2034
+ return false;
2035
+ }
2036
+ const rtOut = new Uint8Array(32);
2037
+ const rtW = b.randomTokenInto(16, rtOut);
2038
+ if (rtW !== 32 || !/^[0-9a-f]{32}$/.test(dec.decode(rtOut))) {
2039
+ return false;
2040
+ }
2041
+ try {
2042
+ b.randomTokenInto(16, new Uint8Array(8));
2043
+ return false;
2044
+ } catch {}
2045
+ const waOut = new Uint8Array(28);
2046
+ const waW = b.wsAcceptKeyInto(enc.encode("dGhlIHNhbXBsZSBub25jZQ=="), waOut);
2047
+ const waExpected = b.wsAcceptKey("dGhlIHNhbXBsZSBub25jZQ==");
2048
+ if (waW !== 28 || dec.decode(waOut) !== waExpected) {
2049
+ return false;
2050
+ }
2051
+ const etagData = enc.encode("hello");
2052
+ const etagOut = new Uint8Array(16);
2053
+ const etagW = b.etagInto(etagData, etagOut);
2054
+ if (etagW !== 10 || dec.decode(etagOut.subarray(0, etagW)) !== b.etag(etagData)) {
2055
+ return false;
2056
+ }
2057
+ const csrfOut = new Uint8Array(129);
2058
+ const csrfW = b.csrfTokenInto(enc.encode("csrf-secret"), csrfOut);
2059
+ if (csrfW !== 129 || csrfOut[64] !== 46) {
2060
+ return false;
2061
+ }
2062
+ const ckVal = enc.encode("session-value");
2063
+ const ckSec = enc.encode("s3cr3t-secret");
2064
+ const ckOut = new Uint8Array(256);
2065
+ const ckW = b.signCookieInto(ckVal, ckSec, ckOut);
2066
+ if (ckW !== ckVal.length + 65) {
2067
+ return false;
2068
+ }
2069
+ const ckVerifyOut = new Uint8Array(256);
2070
+ const ckV = b.verifyCookieInto(ckOut.subarray(0, ckW), ckSec, ckVerifyOut);
2071
+ if (ckV !== ckVal.length || dec.decode(ckVerifyOut.subarray(0, ckV)) !== "session-value") {
2072
+ return false;
2073
+ }
2074
+ if (b.urlEncode(enc.encode("a b/c")) !== "a%20b%2Fc") {
2075
+ return false;
2076
+ }
2077
+ const hdInto = new Uint8Array(8);
2078
+ if (b.hexDecodeInto(enc.encode("68656c6c6f"), hdInto) !== 5 || dec.decode(hdInto.subarray(0, 5)) !== "hello") {
2079
+ return false;
2080
+ }
2081
+ const udInto = new Uint8Array(16);
2082
+ if (b.urlDecodeInto(enc.encode("a%20b%2Fc"), udInto) !== 5 || dec.decode(udInto.subarray(0, 5)) !== "a b/c") {
2083
+ return false;
2084
+ }
2085
+ const b64Into = new Uint8Array(16);
2086
+ if (b.base64EncodeInto(SELFTEST_HEX, b64Into) !== 8 || dec.decode(b64Into.subarray(0, 8)) !== "aGVsbG8=") {
2087
+ return false;
2088
+ }
2089
+ const bdInto = new Uint8Array(8);
2090
+ if (b.base64DecodeInto(enc.encode("aGVsbG8="), bdInto) !== 5 || dec.decode(bdInto.subarray(0, 5)) !== "hello") {
2091
+ return false;
2092
+ }
2093
+ if (!/^[0-9a-f]{32}$/.test(b.randomToken(16))) {
2094
+ return false;
2095
+ }
2096
+ const hmacInto = new Uint8Array(64);
2097
+ if (b.hmacSha256Into(hmacKey, hmacData, hmacInto) !== 64 || dec.decode(hmacInto) !== "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7") {
2098
+ return false;
2099
+ }
2100
+ const ctInto = new Uint8Array(64);
2101
+ const ctW = b.aeadEncryptInto(aeadKey, nonce, SELFTEST_HEX, ctInto, 0);
2102
+ if (ctW === 0 || b.aeadDecrypt(aeadKey, nonce, ctInto.subarray(0, ctW), 0) === null) {
2103
+ return false;
2104
+ }
2105
+ return true;
2106
+ }
2107
+ var init_codecs_selftest = __esm(() => {
2108
+ init_codec2();
2109
+ init_constants();
2110
+ });
2111
+
2112
+ // src/native/ffi/build/instances-selftest.ts
2113
+ function selfTestInstances(b) {
2114
+ const enc = { encode: encodeUtf8 };
2115
+ const dec = { decode: decodeUtf8 };
2116
+ const layoutBuf = new Uint8Array(38 * 4);
2117
+ b.ingressLayout(layoutBuf);
2118
+ const layoutView = new DataView(layoutBuf.buffer, layoutBuf.byteOffset, layoutBuf.byteLength);
2119
+ 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) {
2120
+ return false;
2121
+ }
2122
+ try {
2123
+ b.ingressHandlePacked(0, enc.encode("/"), null, new Uint8Array(64));
2124
+ return false;
2125
+ } catch {}
2126
+ try {
2127
+ b.ingressHandleComponents(0, 0, "/", "", enc.encode("rid"), new Uint8Array(2), null, new Uint8Array(64));
2128
+ return false;
2129
+ } catch {}
2130
+ const routeDesc = new Uint8Array(33);
2131
+ const rd = new DataView(routeDesc.buffer);
2132
+ rd.setUint32(0, 1380930900, true);
2133
+ rd.setUint32(4, 3, true);
2134
+ rd.setUint32(8, 2 * 1024 * 1024, true);
2135
+ rd.setUint32(12, 8192, true);
2136
+ rd.setUint32(16, 8192, true);
2137
+ rd.setUint32(20, 0, true);
2138
+ rd.setUint32(24, 1, true);
2139
+ routeDesc[28] = 0;
2140
+ rd.setUint32(29, 0, true);
2141
+ const routeHandle = b.routeCompile(routeDesc);
2142
+ if (routeHandle === 0) {
2143
+ return false;
2144
+ }
2145
+ const routeFrame = new Uint8Array(15);
2146
+ const rf = new DataView(routeFrame.buffer);
2147
+ rf.setUint32(0, 0, true);
2148
+ rf.setUint32(4, 3, true);
2149
+ routeFrame.set(enc.encode("a=1"), 8);
2150
+ rf.setUint32(11, 0, true);
2151
+ const routeOut = new Uint8Array(64);
2152
+ const routeW = b.routeRun(routeHandle, routeFrame, routeOut);
2153
+ if (routeW <= 8) {
2154
+ return false;
2155
+ }
2156
+ const rv = new DataView(routeOut.buffer);
2157
+ const rFlags = rv.getUint32(0, true);
2158
+ if ((rFlags & 1) === 0 || (rFlags & 4) === 0) {
2159
+ return false;
2160
+ }
2161
+ b.routeDestroy(routeHandle);
2162
+ if (b.conditionalIsNotModified(0, null, null) !== false) {
2163
+ return false;
2164
+ }
2165
+ if (b.mediaTypeMatcherMatches(0, enc.encode("x")) !== false)
2166
+ return false;
2167
+ if (b.acceptNegotiatorNegotiate(0, enc.encode("gzip")) !== null)
2168
+ return false;
2169
+ if (b.acceptNegotiatorNegotiateServer(0, enc.encode("gzip")) !== null)
2170
+ return false;
2171
+ if (b.schemaValidatorValidate(0, enc.encode("{}")) !== false)
2172
+ return false;
2173
+ if (b.jwtSignerVerify(0, enc.encode("a.b.c"), 0) !== null)
2174
+ return false;
2175
+ let phase6Threw = false;
2176
+ try {
2177
+ b.jwtSignerSign(0, enc.encode("{}"), 0);
2178
+ } catch {
2179
+ phase6Threw = true;
2180
+ }
2181
+ if (!phase6Threw)
2182
+ return false;
2183
+ phase6Threw = false;
2184
+ try {
2185
+ b.templateRender(0, enc.encode("{}"));
2186
+ } catch {
2187
+ phase6Threw = true;
2188
+ }
2189
+ if (!phase6Threw)
2190
+ return false;
2191
+ phase6Threw = false;
2192
+ try {
2193
+ b.rateLimiterCheck(0, "k", 0);
2194
+ } catch {
2195
+ phase6Threw = true;
2196
+ }
2197
+ if (!phase6Threw)
2198
+ return false;
2199
+ const jwtClaims = enc.encode('{"sub":"user-1"}');
2200
+ const jwtSecret2 = enc.encode("my-secret");
2201
+ const jwtOut = new Uint8Array(512);
2202
+ const jwtW = b.jwtSignBytesInto(jwtClaims, jwtSecret2, 0, 0, jwtOut);
2203
+ if (jwtW === 0 || dec.decode(jwtOut.subarray(0, jwtW)).split(".").length !== 3) {
2204
+ return false;
2205
+ }
2206
+ const jwtSecret = enc.encode("my-secret");
2207
+ const jwt = b.jwtSignBytes(enc.encode('{"sub":"user-1"}'), jwtSecret, 0, 0);
2208
+ const jwtStr = jwt;
2209
+ const segs = jwtStr.split(".");
2210
+ if (segs.length !== 3 || segs[0] === "" || segs[1] === "" || segs[2] === "") {
2211
+ return false;
2212
+ }
2213
+ const signingInput = enc.encode(`${segs[0]}.${segs[1]}`);
2214
+ const sigHex = b.hmacSha256(jwtSecret, signingInput);
2215
+ const sigBytes = b.base64Decode(enc.encode(segs[2] ?? ""), true, false);
2216
+ if (sigBytes === null || b.hexEncode(sigBytes) !== sigHex) {
2217
+ return false;
2218
+ }
2219
+ const vjwtSecret = enc.encode("verify-secret");
2220
+ const vjwt = b.jwtSignBytes(enc.encode('{"sub":"u-1"}'), vjwtSecret, 0, 0);
2221
+ const vjwtClaims = b.jwtVerify(enc.encode(vjwt), vjwtSecret, 0);
2222
+ if (vjwtClaims === null || !vjwtClaims.includes('"sub":"u-1"')) {
2223
+ return false;
2224
+ }
2225
+ if (b.jwtVerify(enc.encode("tampered.token.value"), vjwtSecret, 0) !== null) {
2226
+ return false;
2227
+ }
2228
+ const edKp = b.ed25519GenerateKeypair();
2229
+ const edMsg = enc.encode("ed25519 bind-time self-test");
2230
+ const edSig = b.ed25519Sign(edMsg, edKp.privateKey);
2231
+ if (edSig.byteLength !== 64 || !b.ed25519Verify(edMsg, edSig, edKp.publicKey)) {
2232
+ return false;
2233
+ }
2234
+ const edTampered = edSig.slice();
2235
+ edTampered[0] = (edTampered[0] ?? 0) ^ 255;
2236
+ if (b.ed25519Verify(edMsg, edTampered, edKp.publicKey)) {
2237
+ return false;
2238
+ }
2239
+ const edToken = b.jwtEdDSASign(enc.encode('{"sub":"ffi-self-test"}'), edKp.privateKey, 0, 0);
2240
+ if (edToken === null || edToken.split(".").length !== 3) {
2241
+ return false;
2242
+ }
2243
+ const edClaims = b.jwtEdDSAVerify(enc.encode(edToken), edKp.publicKey, 0);
2244
+ if (edClaims === null || !edClaims.includes('"sub":"ffi-self-test"')) {
2245
+ return false;
2246
+ }
2247
+ if (b.jwtEdDSAVerify(enc.encode("tampered.token.value"), edKp.publicKey, 0) !== null) {
2248
+ return false;
2249
+ }
2250
+ let urlBuilderThrew = false;
2251
+ try {
2252
+ b.urlBuilderResolve(0, enc.encode("g"));
2253
+ } catch {
2254
+ urlBuilderThrew = true;
2255
+ }
2256
+ if (!urlBuilderThrew) {
2257
+ return false;
2258
+ }
2259
+ if (b.queryValidate(0, "route=/a") !== false)
2260
+ return false;
2261
+ if (b.cookieValidate(0, "route=/a") !== false)
2262
+ return false;
2263
+ {
2264
+ const tok = b.sessionSeal("sess-9", '{"n":1}', 1234567, "sekrit");
2265
+ if (tok === null || !tok.includes(".") || !tok.startsWith('{"id":"sess-9"'))
2266
+ return false;
2267
+ const out = new Uint8Array(256);
2268
+ const w = b.sessionOpen(tok, "sekrit", out);
2269
+ if (w <= 13 || out[0] !== 1)
2270
+ return false;
2271
+ if (b.sessionOpen(tok, "wrong", out) !== 0)
2272
+ return false;
2273
+ const enc = new TextEncoder;
2274
+ const tokB = b.sessionSealBytes(enc.encode("sess-9"), enc.encode('{"n":1}'), 1234567, enc.encode("sekrit"));
2275
+ if (tokB === null || tokB !== tok)
2276
+ return false;
2277
+ const outB = new Uint8Array(256);
2278
+ const wB = b.sessionOpenBytes(enc.encode(tokB), enc.encode("sekrit"), outB);
2279
+ if (wB !== w || outB[0] !== 1)
2280
+ return false;
2281
+ if (b.sessionOpenBytes(enc.encode(tokB), enc.encode("wrong"), outB) !== 0)
2282
+ return false;
2283
+ if (b.sessionOpenBytes(enc.encode(tokB), enc.encode("sekrit"), outB.subarray(0, 4)) !== wB)
2284
+ return false;
2285
+ }
2286
+ let rlKeyThrew = false;
2287
+ try {
2288
+ b.rateLimiterCheckKey(0, 12345, 0);
2289
+ } catch {
2290
+ rlKeyThrew = true;
2291
+ }
2292
+ if (!rlKeyThrew) {
2293
+ return false;
1628
2294
  }
2295
+ return true;
1629
2296
  }
1630
- var METRICS_DECLARE_ERR = 4294967295, EMPTY;
1631
- var init_metrics = __esm(() => {
2297
+ var init_instances_selftest = __esm(() => {
1632
2298
  init_codec2();
1633
- init_util();
1634
- EMPTY = new Uint8Array(0);
1635
2299
  });
1636
2300
 
1637
2301
  // src/rust-ffi/scalar/json-packed.ts
@@ -1743,181 +2407,7 @@ var init_json_packed = __esm(() => {
1743
2407
  init_runtime();
1744
2408
  });
1745
2409
 
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;
1754
- }
1755
- function buildParse(sym, ctx) {
1756
- const { lenOrView, scratchFor, dateScratch, dateScratchView } = ctx;
1757
- const httpParsePacked = sym.castrum_http_parse_request_packed;
1758
- const queryParsePacked = sym.castrum_query_parse_packed;
1759
- const cookieParsePacked = sym.castrum_cookie_parse_packed;
1760
- const httpDateIntoRaw = sym.castrum_http_date_into;
1761
- const sseEncodeIntoRaw = sym.castrum_sse_encode_into;
1762
- const jsonParsePackedSym = sym.castrum_json_parse_packed;
1763
- const wsFrameDecodePacked = sym.castrum_ws_frame_decode_packed;
1764
- const multipartParsePacked = sym.castrum_multipart_parse_packed;
1765
- const formParsePacked = sym.castrum_form_parse_packed;
1766
- const wsFrameEncode = sym.castrum_ws_frame_encode;
1767
- const parseMediaTypeSym = sym.castrum_parse_media_type;
1768
- const parseHttpDateSym = sym.castrum_parse_http_date;
1769
- const parseAcceptEncodingSym = sym.castrum_parse_accept_encoding;
1770
- const urlEncodeQuerySym = sym.castrum_url_encode_query;
1771
- const urlResolveSym = sym.castrum_url_resolve;
1772
- const mimeFromExtensionSym = sym.castrum_mime_from_extension;
1773
- return {
1774
- httpParseRequestPackedInto(input, output) {
1775
- const w = Number(httpParsePacked(input, lenOrView(input), output, lenOrView(output)));
1776
- if (w === 0 && input.length !== 0) {
1777
- throw new Error("http parse: output buffer too small or malformed request");
1778
- }
1779
- return w;
1780
- },
1781
- queryParsePackedInto(input, output) {
1782
- const w = Number(queryParsePacked(input, lenOrView(input), output, lenOrView(output)));
1783
- if (w === 0 || w > output.length) {
1784
- throw new Error("query parse: output buffer too small");
1785
- }
1786
- return w;
1787
- },
1788
- cookieParsePackedInto(input, output) {
1789
- const w = Number(cookieParsePacked(input, lenOrView(input), output, lenOrView(output)));
1790
- if (w === 0 || w > output.length) {
1791
- throw new Error("cookie parse: output buffer too small");
1792
- }
1793
- return w;
1794
- },
1795
- wsFrameEncode(opcode, payload, mask, fin) {
1796
- const out = allocOut(payload.length + 14);
1797
- const w = Number(wsFrameEncode(opcode, payload, lenOrView(payload), flag(mask), flag(fin), out, lenOrView(out)));
1798
- if (w === 0) {
1799
- throw new Error("ws frame encode: output buffer too small");
1800
- }
1801
- return out.subarray(0, w);
1802
- },
1803
- wsFrameEncodeInto(opcode, payload, mask, fin, output) {
1804
- const need = payload.length + 14;
1805
- if (output.length < need) {
1806
- throw new Error("ws frame encode: output buffer too small");
1807
- }
1808
- const w = Number(wsFrameEncode(opcode, payload, lenOrView(payload), flag(mask), flag(fin), output, lenOrView(output)));
1809
- if (w === 0) {
1810
- throw new Error("ws frame encode: output buffer too small");
1811
- }
1812
- return w;
1813
- },
1814
- wsFrameDecodePacked(data) {
1815
- const out = new Uint8Array(data.length + 6);
1816
- const w = Number(wsFrameDecodePacked(data, lenOrView(data), out, lenOrView(out)));
1817
- return w === 0 ? null : out.subarray(0, w);
1818
- },
1819
- wsFrameDecodePackedInto(data, output) {
1820
- const w = Number(wsFrameDecodePacked(data, lenOrView(data), output, lenOrView(output)));
1821
- return w === 0 ? null : w;
1822
- },
1823
- multipartParsePacked(body, boundary) {
1824
- return growExact((out) => Number(multipartParsePacked(body, lenOrView(body), boundary, lenOrView(boundary), out, lenOrView(out))), Math.min(body.length + boundary.length + 64, 64 * 1024), 128 * 1024 * 1024, "multipart parse: output buffer too small");
1825
- },
1826
- multipartParsePackedInto(body, boundary, output) {
1827
- const w = Number(multipartParsePacked(body, lenOrView(body), boundary, lenOrView(boundary), output, lenOrView(output)));
1828
- if (w === 0 || w > output.length) {
1829
- throw new Error("multipart parse: output buffer too small or malformed body");
1830
- }
1831
- return w;
1832
- },
1833
- formParsePackedInto(input, output) {
1834
- const w = Number(formParsePacked(input, lenOrView(input), output, lenOrView(output)));
1835
- if (w === 0 || w > output.length) {
1836
- throw new Error("form parse: output buffer too small");
1837
- }
1838
- return w;
1839
- },
1840
- jsonParsePacked(input) {
1841
- return growExact((out) => Number(jsonParsePackedSym(input, lenOrView(input), out, lenOrView(out))), Math.min(input.length + (input.length >> 1), 16 * 1024 * 1024), Math.max(1024 * 1024, input.length * 16), "json parse packed: invalid JSON or output buffer too small");
1842
- },
1843
- parseMediaType(input) {
1844
- let out = scratchFor(input.length * 2 + 128);
1845
- let w = Number(parseMediaTypeSym(input, lenOrView(input), out, lenOrView(out)));
1846
- if (w > out.length) {
1847
- out = new Uint8Array(w);
1848
- w = Number(parseMediaTypeSym(input, lenOrView(input), out, lenOrView(out)));
1849
- }
1850
- if (w === 0 || w > out.length) {
1851
- throw new Error("media type parse: invalid media type or output buffer too small");
1852
- }
1853
- return out.subarray(0, w);
1854
- },
1855
- parseHttpDate(input) {
1856
- const out = dateScratch;
1857
- const w = Number(parseHttpDateSym(input, lenOrView(input), out, lenOrView(out)));
1858
- if (w === 0) {
1859
- throw new Error("http date parse: output buffer too small");
1860
- }
1861
- if (out[0] === 0)
1862
- return null;
1863
- return dateScratchView.getBigInt64(1, true);
1864
- },
1865
- parseAcceptEncoding(input) {
1866
- let out = scratchFor(input.length * 2 + 128);
1867
- let w = Number(parseAcceptEncodingSym(input, lenOrView(input), out, lenOrView(out)));
1868
- if (w > out.length) {
1869
- out = new Uint8Array(w);
1870
- w = Number(parseAcceptEncodingSym(input, lenOrView(input), out, lenOrView(out)));
1871
- }
1872
- if (w === 0 || w > out.length) {
1873
- throw new Error("accept encoding parse: output buffer too small");
1874
- }
1875
- return out.subarray(0, w);
1876
- },
1877
- urlEncodeQuery(input) {
1878
- return urlEncodeQuerySym(input, lenOrView(input));
1879
- },
1880
- urlResolve(base, reference) {
1881
- return urlResolveSym(base, lenOrView(base), reference, lenOrView(reference));
1882
- },
1883
- mimeFromExtension(ext) {
1884
- return mimeFromExtensionSym(ext);
1885
- },
1886
- httpDateInto(secs, output) {
1887
- const w = Number(httpDateIntoRaw(secs, output, lenOrView(output)));
1888
- if (w === 0) {
1889
- throw new Error("http date: output buffer too small or year out of range");
1890
- }
1891
- return w;
1892
- },
1893
- httpDate(secs) {
1894
- const out = scratchFor(32);
1895
- const w = Number(httpDateIntoRaw(secs ?? 0, out, lenOrView(out)));
1896
- if (w !== 0)
1897
- return decodeUtf8(out.subarray(0, w));
1898
- return new Date((secs ?? 0) * 1000).toUTCString();
1899
- },
1900
- sseEncodeEvent(event, data, id, retry) {
1901
- const ev = event === null ? EMPTY_VIEW : sseMemoEncode(sseMemoEvent, event);
1902
- const idv = id === null ? EMPTY_VIEW : sseMemoEncode(sseMemoId, id);
1903
- const flags = (event === null ? 0 : 1) | (id === null ? 0 : 2) | (retry === null ? 0 : 4);
1904
- return growExact((out) => Number(sseEncodeIntoRaw(ev, lenOrView(ev), data, lenOrView(data), idv, lenOrView(idv), flags, retry ?? 0, out, lenOrView(out))), data.length + 64, 1 << 20, "sse encode: output buffer too small");
1905
- },
1906
- sseEncodeEventInto(event, data, id, retry, output) {
1907
- const ev = event === null ? EMPTY_VIEW : sseMemoEncode(sseMemoEvent, event);
1908
- const idv = id === null ? EMPTY_VIEW : sseMemoEncode(sseMemoId, id);
1909
- const flags = (event === null ? 0 : 1) | (id === null ? 0 : 2) | (retry === null ? 0 : 4);
1910
- const w = Number(sseEncodeIntoRaw(ev, lenOrView(ev), data, lenOrView(data), idv, lenOrView(idv), flags, retry ?? 0, output, lenOrView(output)));
1911
- if (w === 0) {
1912
- throw new Error("sse encode: invalid UTF-8 in event/id");
1913
- }
1914
- if (w > output.length) {
1915
- throw new Error("sse encode: output buffer too small");
1916
- }
1917
- return w;
1918
- }
1919
- };
1920
- }
2410
+ // src/native/ffi/build/parse-selftest.ts
1921
2411
  function selfTestParse(b) {
1922
2412
  const enc = { encode: encodeUtf8 };
1923
2413
  const dec = { decode: decodeUtf8 };
@@ -2087,114 +2577,19 @@ data: hello
2087
2577
  if (b.mimeFromExtension("nope") !== "application/octet-stream") {
2088
2578
  return false;
2089
2579
  }
2090
- if (b.httpDate(784111777) !== "Sun, 06 Nov 1994 08:49:37 GMT") {
2091
- return false;
2092
- }
2093
- const frameInto = new Uint8Array(64);
2094
- if (b.wsFrameEncodeInto(1, SELFTEST_HEX, false, true, frameInto) === 0 || frameInto[0] !== 129) {
2095
- return false;
2096
- }
2097
- return true;
2098
- }
2099
- var sseMemoEvent, sseMemoId;
2100
- var init_parse = __esm(() => {
2101
- init_json_packed();
2102
- init_codec2();
2103
- 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
- };
2580
+ if (b.httpDate(784111777) !== "Sun, 06 Nov 1994 08:49:37 GMT") {
2581
+ return false;
2582
+ }
2583
+ const frameInto = new Uint8Array(64);
2584
+ if (b.wsFrameEncodeInto(1, SELFTEST_HEX, false, true, frameInto) === 0 || frameInto[0] !== 129) {
2585
+ return false;
2586
+ }
2587
+ return true;
2191
2588
  }
2192
- var init_build = __esm(() => {
2193
- init_codecs();
2194
- init_compress();
2195
- init_instances();
2196
- init_metrics();
2197
- init_parse();
2589
+ var init_parse_selftest = __esm(() => {
2590
+ init_json_packed();
2591
+ init_codec2();
2592
+ init_constants();
2198
2593
  });
2199
2594
 
2200
2595
  // src/native/ffi/selftest.ts
@@ -2202,11 +2597,11 @@ function selfTest(b) {
2202
2597
  return selfTestCodecs(b) && selfTestCompress(b) && selfTestParse(b) && selfTestInstances(b) && selfTestMetrics(b) && selfTestTask(b);
2203
2598
  }
2204
2599
  var init_selftest = __esm(() => {
2205
- init_codecs();
2600
+ init_codecs_selftest();
2206
2601
  init_compress();
2207
- init_instances();
2602
+ init_instances_selftest();
2208
2603
  init_metrics();
2209
- init_parse();
2604
+ init_parse_selftest();
2210
2605
  });
2211
2606
 
2212
2607
  // src/native/loader.ts
@@ -2366,6 +2761,12 @@ function getBunFFI() {
2366
2761
  cached = bind();
2367
2762
  return cached;
2368
2763
  }
2764
+ function ffiAddonPath() {
2765
+ if (cached === undefined) {
2766
+ getBunFFI();
2767
+ }
2768
+ return boundAddonPath;
2769
+ }
2369
2770
  function probeBufferLength(dlopen, path) {
2370
2771
  try {
2371
2772
  const { symbols, close } = dlopen(path, {
@@ -2399,358 +2800,7 @@ function bind(forcedPath, tried = []) {
2399
2800
  const { dlopen } = __require("bun:ffi");
2400
2801
  const useBufferLength = probeBufferLength(dlopen, path);
2401
2802
  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
- });
2803
+ const { symbols } = dlopen(path, buildFFISymbolMap(useBufferLength));
2754
2804
  const bindings = build(symbols, useBufferLength);
2755
2805
  if (!selfTest(bindings)) {
2756
2806
  throw new Error(`bun:ffi bind-time self-test failed on ${path}`);
@@ -3425,24 +3475,53 @@ var init_wire = __esm(() => {
3425
3475
  });
3426
3476
 
3427
3477
  // src/shared/packed/parsers.ts
3428
- function parseQueryString(query) {
3429
- const packed = addon2.queryParsePacked(encoder.encode(query));
3478
+ function scratchFor(kind, input) {
3479
+ const need = input.length * 9 + 16;
3480
+ let s = ffiCaches[kind];
3481
+ if (s === null || s.length < need) {
3482
+ s = new Uint8Array(need);
3483
+ ffiCaches[kind] = s;
3484
+ }
3485
+ return s;
3486
+ }
3487
+ function parsePackedInto(kind, input) {
3488
+ const f = getBunFFI();
3489
+ if (!f)
3490
+ return null;
3491
+ const out = scratchFor(kind, input);
3492
+ const w = kind === "query" ? f.queryParsePackedInto(input, out) : kind === "cookie" ? f.cookieParsePackedInto(input, out) : f.formParsePackedInto(input, out);
3493
+ if (w === 0 || w > out.length)
3494
+ return null;
3495
+ return pairsToObject(readPairsPacked(out.subarray(0, w)));
3496
+ }
3497
+ function parseVia(kind, input) {
3498
+ const fast = parsePackedInto(kind, input);
3499
+ if (fast)
3500
+ return fast;
3501
+ const packed = kind === "query" ? addon2.queryParsePacked(input) : kind === "cookie" ? addon2.cookieParsePacked(input) : addon2.formParsePacked(input);
3430
3502
  return pairsToObject(readPairsPacked(packed));
3431
3503
  }
3504
+ function parseQueryString(query) {
3505
+ return parseVia("query", encoder.encode(query));
3506
+ }
3432
3507
  function parseCookieHeader(header) {
3433
- const packed = addon2.cookieParsePacked(encoder.encode(header));
3434
- return pairsToObject(readPairsPacked(packed));
3508
+ return parseVia("cookie", encoder.encode(header));
3435
3509
  }
3436
3510
  function parseFormBody(body) {
3437
- const packed = addon2.formParsePacked(body);
3438
- return pairsToObject(readPairsPacked(packed));
3511
+ return parseVia("form", body);
3439
3512
  }
3440
- var addon2;
3513
+ var addon2, ffiCaches;
3441
3514
  var init_parsers = __esm(() => {
3515
+ init_ffi();
3442
3516
  init_native();
3443
3517
  init_bytes();
3444
3518
  init_wire();
3445
3519
  addon2 = lazyAddon(getAddon);
3520
+ ffiCaches = {
3521
+ query: null,
3522
+ cookie: null,
3523
+ form: null
3524
+ };
3446
3525
  });
3447
3526
 
3448
3527
  // src/shared/packed/schema.ts
@@ -3507,6 +3586,11 @@ function asNumber(value) {
3507
3586
  }
3508
3587
  return 0;
3509
3588
  }
3589
+ function assertNonEmptySecret(secret, op) {
3590
+ if (secret.byteLength === 0) {
3591
+ throw new Error(`${op}: secret must not be empty`);
3592
+ }
3593
+ }
3510
3594
  function normalizeExt(ext) {
3511
3595
  let s;
3512
3596
  try {
@@ -3674,17 +3758,29 @@ function buildBatch(ctx) {
3674
3758
  gzipDecompress: (items, maxDecompressed) => via(ctx, "gzipDecompressBatchPacked", unpackByteResults, [maxDecompressed ?? null])(items),
3675
3759
  brotliCompress: (items, quality) => via(ctx, "brotliCompressBatchPacked", unpackByteResults, [quality ?? null])(items),
3676
3760
  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),
3761
+ jwtVerify: (tokens, secret, nowSeconds) => {
3762
+ assertNonEmptySecret(secret, "batch.jwtVerify");
3763
+ return via(ctx, "jwtVerifyBatchPacked", unpackBitset, [secret, nowSeconds])(tokens);
3764
+ },
3765
+ hmacSha256: (items, key) => {
3766
+ assertNonEmptySecret(key, "batch.hmacSha256");
3767
+ return via(ctx, "hmacSha256BatchPacked", unpackByteResults, [key])(items);
3768
+ },
3769
+ hmacSha256Verify: (items, sigs, key) => {
3770
+ assertNonEmptySecret(key, "batch.hmacSha256Verify");
3771
+ return via2(ctx, "hmacSha256VerifyBatchPacked", unpackBitset, [key])(items, sigs);
3772
+ },
3680
3773
  passwordVerify: (items, phcs) => via2(ctx, "passwordVerifyBatchPacked", unpackBitset)(items, phcs),
3681
3774
  urlResolve: (bases, references) => via2(ctx, "urlResolveBatchPacked", unpackByteResults)(bases, references),
3682
3775
  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),
3776
+ jwtSign: (items, secret, ttlSeconds, nowSeconds) => {
3777
+ assertNonEmptySecret(secret, "batch.jwtSign");
3778
+ return via(ctx, "jwtSignBatchPacked", unpackByteResults, [
3779
+ secret,
3780
+ ttlSeconds ?? null,
3781
+ nowSeconds ?? Math.floor(Date.now() / 1000)
3782
+ ])(items);
3783
+ },
3688
3784
  sseEncode: (items, event, id, retry) => via(ctx, "sseEncodeBatchPacked", unpackByteResults, [
3689
3785
  event ?? null,
3690
3786
  id ?? null,
@@ -3698,6 +3794,7 @@ function buildBatch(ctx) {
3698
3794
  var init_build2 = __esm(() => {
3699
3795
  init_packed();
3700
3796
  init_context();
3797
+ init_options();
3701
3798
  });
3702
3799
 
3703
3800
  // src/rust-ffi/batch/index.ts
@@ -3959,18 +4056,22 @@ function buildCrypto(ctx) {
3959
4056
  return writeInto("verify cookie", output, value);
3960
4057
  },
3961
4058
  csrfToken(secret) {
4059
+ assertNonEmptySecret(secret, "csrfToken");
3962
4060
  return resolveNative(ctx, "csrfToken")(secret);
3963
4061
  },
3964
4062
  csrfTokenInto(secret, output) {
4063
+ assertNonEmptySecret(secret, "csrfTokenInto");
3965
4064
  const f = ffi();
3966
4065
  if (f)
3967
4066
  return f.csrfTokenInto(secret, output);
3968
4067
  return writeInto("csrf token", output, addon.csrfToken(secret));
3969
4068
  },
3970
4069
  csrfVerify(token, secret) {
4070
+ assertNonEmptySecret(secret, "csrfVerify");
3971
4071
  return resolveNative(ctx, "csrfVerify")(token, secret);
3972
4072
  },
3973
4073
  jwtSign(claims, secret, ttlSeconds, nowSeconds) {
4074
+ assertNonEmptySecret(secret, "jwtSign");
3974
4075
  const f = ffi();
3975
4076
  if (f) {
3976
4077
  const token = f.jwtSignBytes(encoder.encode(JSON.stringify(claims)), secret, ttlSeconds ?? 0, nowSeconds ?? Math.floor(Date.now() / 1000));
@@ -3979,6 +4080,7 @@ function buildCrypto(ctx) {
3979
4080
  return addon.jwtSign(claims, secret, ttlSeconds ?? null, nowSeconds ?? Math.floor(Date.now() / 1000));
3980
4081
  },
3981
4082
  jwtSignBytes(claimsJson, secret, ttlSeconds, nowSeconds) {
4083
+ assertNonEmptySecret(secret, "jwtSignBytes");
3982
4084
  const f = ffi();
3983
4085
  if (f) {
3984
4086
  return f.jwtSignBytes(claimsJson, secret, ttlSeconds ?? 0, nowSeconds ?? Math.floor(Date.now() / 1000));
@@ -3986,6 +4088,7 @@ function buildCrypto(ctx) {
3986
4088
  return addon.jwtSignBytes(claimsJson, secret, ttlSeconds ?? null, nowSeconds ?? Math.floor(Date.now() / 1000));
3987
4089
  },
3988
4090
  jwtSignBytesInto(claimsJson, secret, output, ttlSeconds, nowSeconds) {
4091
+ assertNonEmptySecret(secret, "jwtSignBytesInto");
3989
4092
  const f = ffi();
3990
4093
  if (f) {
3991
4094
  return f.jwtSignBytesInto(claimsJson, secret, ttlSeconds ?? 0, nowSeconds ?? Math.floor(Date.now() / 1000), output);
@@ -3993,6 +4096,7 @@ function buildCrypto(ctx) {
3993
4096
  return writeInto("jwt sign", output, addon.jwtSignBytes(claimsJson, secret, ttlSeconds ?? null, nowSeconds ?? Math.floor(Date.now() / 1000)));
3994
4097
  },
3995
4098
  jwtVerify(token, secret, nowSeconds) {
4099
+ assertNonEmptySecret(secret, "jwtVerify");
3996
4100
  const f = ffi();
3997
4101
  if (f) {
3998
4102
  const claims = f.jwtVerify(token, secret, nowSeconds ?? Math.floor(Date.now() / 1000));
@@ -4053,9 +4157,17 @@ function buildCrypto(ctx) {
4053
4157
  var init_crypto = __esm(() => {
4054
4158
  init_bytes();
4055
4159
  init_context();
4160
+ init_options();
4056
4161
  });
4057
4162
 
4058
4163
  // src/rust-ffi/scalar/factories.ts
4164
+ function handlePtr(inner) {
4165
+ const v = inner ?? 0n;
4166
+ if (v < 0n || v > BigInt(Number.MAX_SAFE_INTEGER)) {
4167
+ throw new Error("castrum: native handle exceeds Number.MAX_SAFE_INTEGER");
4168
+ }
4169
+ return Number(v);
4170
+ }
4059
4171
  function ffiHmacSigner(key, ffi) {
4060
4172
  return {
4061
4173
  sign(data) {
@@ -4236,47 +4348,55 @@ function ffiFormParser(ffi) {
4236
4348
  };
4237
4349
  }
4238
4350
  function ffiMetricsRegistry(ffi) {
4239
- const handle = ffi.metricsCreate();
4351
+ let handle = ffi.metricsCreate();
4352
+ const live = () => {
4353
+ if (handle === 0)
4354
+ throw new Error("metrics registry: already destroyed");
4355
+ return handle;
4356
+ };
4240
4357
  return {
4241
4358
  counter(name, labelKeys) {
4242
- return ffi.metricsCounter(handle, name, (labelKeys ?? []).join("\x1F"));
4359
+ return ffi.metricsCounter(live(), name, (labelKeys ?? []).join("\x1F"));
4243
4360
  },
4244
4361
  gauge(name, labelKeys) {
4245
- return ffi.metricsGauge(handle, name, (labelKeys ?? []).join("\x1F"));
4362
+ return ffi.metricsGauge(live(), name, (labelKeys ?? []).join("\x1F"));
4246
4363
  },
4247
4364
  histogram(name, labelKeys, buckets) {
4248
- return ffi.metricsHistogram(handle, name, (labelKeys ?? []).join("\x1F"), (buckets ?? []).join(","));
4365
+ return ffi.metricsHistogram(live(), name, (labelKeys ?? []).join("\x1F"), (buckets ?? []).join(","));
4249
4366
  },
4250
4367
  record(series, values, amount) {
4251
4368
  const joined = (values ?? []).join("\x1F");
4252
- if (!ffi.metricsRecordStr(handle, series, joined, amount ?? 1)) {
4369
+ if (!ffi.metricsRecordStr(live(), series, joined, amount ?? 1)) {
4253
4370
  throw new Error("metrics record: unknown series / arity mismatch / invalid amount");
4254
4371
  }
4255
4372
  },
4256
4373
  gaugeSet(series, values, value) {
4257
4374
  const joined = (values ?? []).join("\x1F");
4258
- if (!ffi.metricsGaugeSetStr(handle, series, joined, value)) {
4375
+ if (!ffi.metricsGaugeSetStr(live(), series, joined, value)) {
4259
4376
  throw new Error("metrics gauge set: unknown series / arity mismatch");
4260
4377
  }
4261
4378
  },
4262
4379
  render() {
4263
4380
  let out = new Uint8Array(8192);
4264
- let w = ffi.metricsRender(handle, out);
4381
+ let w = ffi.metricsRender(live(), out);
4265
4382
  if (w > out.length) {
4266
4383
  out = new Uint8Array(w);
4267
- w = ffi.metricsRender(handle, out);
4384
+ w = ffi.metricsRender(live(), out);
4268
4385
  }
4269
4386
  return decodeUtf8(out.subarray(0, w));
4270
4387
  },
4271
4388
  destroy() {
4272
- ffi.metricsDestroy(handle);
4389
+ if (handle !== 0) {
4390
+ ffi.metricsDestroy(handle);
4391
+ handle = 0;
4392
+ }
4273
4393
  },
4274
4394
  snapshot() {
4275
4395
  let out = new Uint8Array(4096);
4276
- let w = ffi.metricsSnapshot(handle, out);
4396
+ let w = ffi.metricsSnapshot(live(), out);
4277
4397
  if (w > out.length) {
4278
4398
  out = new Uint8Array(w);
4279
- w = ffi.metricsSnapshot(handle, out);
4399
+ w = ffi.metricsSnapshot(live(), out);
4280
4400
  }
4281
4401
  return out.subarray(0, w);
4282
4402
  }
@@ -4290,11 +4410,12 @@ function buildFactories(ctx) {
4290
4410
  const ffi = transport.ffi;
4291
4411
  if (ffi) {
4292
4412
  const napi = new addon.SchemaValidator(schema);
4293
- return ffiSchemaValidator(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4413
+ return ffiSchemaValidator(napi, handlePtr(napi.innerPtr?.()), ffi);
4294
4414
  }
4295
4415
  return new addon.SchemaValidator(schema);
4296
4416
  },
4297
4417
  createHmacSigner(key) {
4418
+ assertNonEmptySecret(key, "createHmacSigner");
4298
4419
  const ffi = transport.ffi;
4299
4420
  if (ffi)
4300
4421
  return ffiHmacSigner(key, ffi);
@@ -4304,7 +4425,7 @@ function buildFactories(ctx) {
4304
4425
  const ffi = transport.ffi;
4305
4426
  if (ffi) {
4306
4427
  const napi = new addon.TemplateRenderer(source);
4307
- return ffiTemplateRenderer(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4428
+ return ffiTemplateRenderer(napi, handlePtr(napi.innerPtr?.()), ffi);
4308
4429
  }
4309
4430
  return new addon.TemplateRenderer(source);
4310
4431
  },
@@ -4321,7 +4442,7 @@ function buildFactories(ctx) {
4321
4442
  const ffi = transport.ffi;
4322
4443
  if (ffi) {
4323
4444
  const napi = new addon.ConditionalRequest(etagValue, lastModifiedSecs ?? undefined);
4324
- return ffiConditionalRequest(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4445
+ return ffiConditionalRequest(napi, handlePtr(napi.innerPtr?.()), ffi);
4325
4446
  }
4326
4447
  return new addon.ConditionalRequest(etagValue, lastModifiedSecs ?? undefined);
4327
4448
  },
@@ -4329,7 +4450,7 @@ function buildFactories(ctx) {
4329
4450
  const ffi = transport.ffi;
4330
4451
  if (ffi) {
4331
4452
  const napi = new addon.AcceptNegotiator(supported);
4332
- return ffiAcceptNegotiator(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4453
+ return ffiAcceptNegotiator(napi, handlePtr(napi.innerPtr?.()), ffi);
4333
4454
  }
4334
4455
  return new addon.AcceptNegotiator(supported);
4335
4456
  },
@@ -4340,12 +4461,14 @@ function buildFactories(ctx) {
4340
4461
  return new addon.Base64Codec(urlSafe ?? undefined, padding ?? undefined);
4341
4462
  },
4342
4463
  createCookieSigner(secret) {
4464
+ assertNonEmptySecret(secret, "createCookieSigner");
4343
4465
  const ffi = transport.ffi;
4344
4466
  if (ffi)
4345
4467
  return ffiCookieSigner(secret, ffi);
4346
4468
  return new addon.CookieSigner(secret);
4347
4469
  },
4348
4470
  createCsrfProtector(secret) {
4471
+ assertNonEmptySecret(secret, "createCsrfProtector");
4349
4472
  const ffi = transport.ffi;
4350
4473
  if (ffi)
4351
4474
  return ffiCsrfProtector(secret, ffi);
@@ -4355,15 +4478,16 @@ function buildFactories(ctx) {
4355
4478
  const ffi = transport.ffi;
4356
4479
  if (ffi) {
4357
4480
  const napi = new addon.UrlBuilder(base);
4358
- return ffiUrlBuilder(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4481
+ return ffiUrlBuilder(napi, handlePtr(napi.innerPtr?.()), ffi);
4359
4482
  }
4360
4483
  return new addon.UrlBuilder(base);
4361
4484
  },
4362
4485
  createJwtSigner(secret, ttlSeconds) {
4486
+ assertNonEmptySecret(secret, "createJwtSigner");
4363
4487
  const ffi = transport.ffi;
4364
4488
  if (ffi) {
4365
4489
  const napi = new addon.JwtSigner(secret, ttlSeconds ?? undefined);
4366
- return ffiJwtSigner(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4490
+ return ffiJwtSigner(napi, handlePtr(napi.innerPtr?.()), ffi);
4367
4491
  }
4368
4492
  return new addon.JwtSigner(secret, ttlSeconds ?? undefined);
4369
4493
  },
@@ -4383,7 +4507,7 @@ function buildFactories(ctx) {
4383
4507
  const ffi = transport.ffi;
4384
4508
  if (ffi) {
4385
4509
  const napi = new addon.MediaTypeMatcher(expected);
4386
- return ffiMediaTypeMatcher(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4510
+ return ffiMediaTypeMatcher(napi, handlePtr(napi.innerPtr?.()), ffi);
4387
4511
  }
4388
4512
  return new addon.MediaTypeMatcher(expected);
4389
4513
  },
@@ -4391,7 +4515,7 @@ function buildFactories(ctx) {
4391
4515
  const ffi = transport.ffi;
4392
4516
  if (ffi) {
4393
4517
  const napi = new addon.RateLimiter(limit, windowMs, maxEntries ?? undefined);
4394
- return ffiRateLimiter(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4518
+ return ffiRateLimiter(napi, handlePtr(napi.innerPtr?.()), ffi);
4395
4519
  }
4396
4520
  return new addon.RateLimiter(limit, windowMs, maxEntries ?? undefined);
4397
4521
  },
@@ -4492,37 +4616,63 @@ function ensureValidUtf8(bytes, ffi) {
4492
4616
  function u32LE(b, off) {
4493
4617
  return ((b[off] ?? 0) | (b[off + 1] ?? 0) << 8 | (b[off + 2] ?? 0) << 16 | (b[off + 3] ?? 0) << 24) >>> 0;
4494
4618
  }
4619
+ function keyBytesEqual(b, off, lit) {
4620
+ for (let i = 0;i < lit.length; i++) {
4621
+ if ((b[off + i] ?? 0) !== (lit[i] ?? 0))
4622
+ return false;
4623
+ }
4624
+ return true;
4625
+ }
4495
4626
  function unpackMediaType(packed) {
4627
+ const view = Buffer.from(packed.buffer, packed.byteOffset, packed.byteLength);
4496
4628
  let off = 0;
4497
4629
  const mtLen = u32LE(packed, off);
4498
4630
  off += 4;
4499
- const mediaType = decodeUtf8Range(packed, off, off + mtLen);
4631
+ const mediaType = decodeUtf8RangeView(view, off, off + mtLen);
4500
4632
  off += mtLen;
4501
4633
  let charset = null;
4502
4634
  const csLen = u32LE(packed, off);
4503
4635
  off += 4;
4504
4636
  if (csLen !== 4294967295) {
4505
- charset = decodeUtf8Range(packed, off, off + csLen);
4637
+ charset = decodeUtf8RangeView(view, off, off + csLen);
4506
4638
  off += csLen;
4507
4639
  }
4508
4640
  let boundary = null;
4509
4641
  const bLen = u32LE(packed, off);
4510
4642
  off += 4;
4511
4643
  if (bLen !== 4294967295) {
4512
- boundary = decodeUtf8Range(packed, off, off + bLen);
4644
+ boundary = decodeUtf8RangeView(view, off, off + bLen);
4513
4645
  off += bLen;
4514
4646
  }
4515
4647
  const count = u32LE(packed, off);
4516
4648
  off += 4;
4517
4649
  const params = {};
4650
+ let charsetTaken = charset === null;
4651
+ let boundaryTaken = boundary === null;
4518
4652
  for (let i = 0;i < count; i++) {
4519
4653
  const kLen = u32LE(packed, off);
4520
4654
  off += 4;
4521
- const key = decodeUtf8Range(packed, off, off + kLen);
4655
+ let key;
4656
+ if (kLen === 7 && keyBytesEqual(packed, off, CHARSET_KEY_BYTES)) {
4657
+ key = "charset";
4658
+ } else if (kLen === 8 && keyBytesEqual(packed, off, BOUNDARY_KEY_BYTES)) {
4659
+ key = "boundary";
4660
+ } else {
4661
+ key = decodeUtf8RangeView(view, off, off + kLen);
4662
+ }
4522
4663
  off += kLen;
4523
4664
  const vLen = u32LE(packed, off);
4524
4665
  off += 4;
4525
- const val = decodeUtf8Range(packed, off, off + vLen);
4666
+ let val;
4667
+ if (!charsetTaken && key === "charset" && charset !== null) {
4668
+ val = charset;
4669
+ charsetTaken = true;
4670
+ } else if (!boundaryTaken && key === "boundary" && boundary !== null) {
4671
+ val = boundary;
4672
+ boundaryTaken = true;
4673
+ } else {
4674
+ val = decodeUtf8RangeView(view, off, off + vLen);
4675
+ }
4526
4676
  off += vLen;
4527
4677
  params[key] = val;
4528
4678
  }
@@ -4653,7 +4803,7 @@ function buildHttp(ctx) {
4653
4803
  queryParsePacked(input) {
4654
4804
  const f = ffi();
4655
4805
  if (f) {
4656
- const out = new Uint8Array(input.length * 9 + 16);
4806
+ const out = new Uint8Array(input.length * 5 + 8);
4657
4807
  const w = f.queryParsePackedInto(input, out);
4658
4808
  return out.subarray(0, w);
4659
4809
  }
@@ -4665,7 +4815,7 @@ function buildHttp(ctx) {
4665
4815
  cookieParsePacked(input) {
4666
4816
  const f = ffi();
4667
4817
  if (f) {
4668
- const out = new Uint8Array(input.length * 9 + 16);
4818
+ const out = new Uint8Array(input.length * 5 + 8);
4669
4819
  const w = f.cookieParsePackedInto(input, out);
4670
4820
  return out.subarray(0, w);
4671
4821
  }
@@ -4677,7 +4827,7 @@ function buildHttp(ctx) {
4677
4827
  formParsePacked(input) {
4678
4828
  const f = ffi();
4679
4829
  if (f) {
4680
- const out = new Uint8Array(input.length * 9 + 16);
4830
+ const out = new Uint8Array(input.length * 5 + 8);
4681
4831
  const w = f.formParsePackedInto(input, out);
4682
4832
  return out.subarray(0, w);
4683
4833
  }
@@ -4749,13 +4899,15 @@ function buildHttp(ctx) {
4749
4899
  }
4750
4900
  };
4751
4901
  }
4752
- var WS_ACCEPT_SCRATCH, f32Bits, f32BitsU32;
4902
+ var WS_ACCEPT_SCRATCH, CHARSET_KEY_BYTES, BOUNDARY_KEY_BYTES, f32Bits, f32BitsU32;
4753
4903
  var init_http = __esm(() => {
4754
4904
  init_bytes();
4755
4905
  init_codec2();
4756
4906
  init_packed();
4757
4907
  init_context();
4758
4908
  WS_ACCEPT_SCRATCH = new Uint8Array(64);
4909
+ CHARSET_KEY_BYTES = new Uint8Array([99, 104, 97, 114, 115, 101, 116]);
4910
+ BOUNDARY_KEY_BYTES = new Uint8Array([98, 111, 117, 110, 100, 97, 114, 121]);
4759
4911
  f32Bits = new Float32Array(1);
4760
4912
  f32BitsU32 = new Uint32Array(f32Bits.buffer);
4761
4913
  });
@@ -4835,6 +4987,7 @@ function buildPayload(ctx) {
4835
4987
  const { builtins, transport } = ctx.runtime;
4836
4988
  const HAS = { gzipCompress: builtins.has("gzipCompress") };
4837
4989
  const ffi = memoizeFfi(transport);
4990
+ let SCRATCH = null;
4838
4991
  return {
4839
4992
  gzipCompress(data, level) {
4840
4993
  if (HAS.gzipCompress)
@@ -4888,8 +5041,20 @@ function buildPayload(ctx) {
4888
5041
  },
4889
5042
  multipartParse(body, boundary) {
4890
5043
  const f = ffi();
4891
- if (f)
4892
- return unpackMultipart(f.multipartParsePacked(body, boundary));
5044
+ if (f) {
5045
+ const need = body.length + boundary.length + 64;
5046
+ if (need > 64 * 1024)
5047
+ return unpackMultipart(f.multipartParsePacked(body, boundary));
5048
+ if (!SCRATCH || SCRATCH.length < need)
5049
+ SCRATCH = new Uint8Array(need);
5050
+ let w;
5051
+ try {
5052
+ w = f.multipartParsePackedInto(body, boundary, SCRATCH);
5053
+ } catch {
5054
+ return unpackMultipart(f.multipartParsePacked(body, boundary));
5055
+ }
5056
+ return unpackMultipart(SCRATCH.subarray(0, w));
5057
+ }
4893
5058
  return addon.multipartParse(body, boundary).map((p) => ({
4894
5059
  name: p.name,
4895
5060
  filename: p.filename ?? null,
@@ -5318,10 +5483,13 @@ function createNativeRoute(plan = {}) {
5318
5483
  const parseQuery = plan.parseQuery === true;
5319
5484
  const parseCookies = plan.parseCookies === true;
5320
5485
  const bunFFI = getBunFFI();
5321
- const ffiHandle = bunFFI !== null ? compileFfi(bunFFI, descriptor) : 0;
5322
- const napiRoute = bunFFI === null ? compileNapi(descriptor) : null;
5486
+ let ffiHandle = bunFFI !== null ? compileFfi(bunFFI, descriptor) : 0;
5487
+ let napiRoute = bunFFI === null || ffiHandle === 0 ? compileNapi(descriptor) : null;
5323
5488
  let out = new Uint8Array(256);
5324
5489
  const runFrame = (frame) => {
5490
+ if (ffiHandle === 0 && napiRoute === null) {
5491
+ throw new Error("native route: already destroyed");
5492
+ }
5325
5493
  let written;
5326
5494
  if (ffiHandle !== 0 && bunFFI !== null) {
5327
5495
  written = bunFFI.routeRun(ffiHandle, frame, out);
@@ -5354,7 +5522,10 @@ function createNativeRoute(plan = {}) {
5354
5522
  destroy: () => {
5355
5523
  if (ffiHandle !== 0 && bunFFI !== null) {
5356
5524
  bunFFI.routeDestroy(ffiHandle);
5525
+ ffiHandle = 0;
5357
5526
  }
5527
+ napiRoute?.destroy?.();
5528
+ napiRoute = null;
5358
5529
  }
5359
5530
  };
5360
5531
  }
@@ -5378,6 +5549,17 @@ function compileNapi(descriptor) {
5378
5549
  }
5379
5550
  }
5380
5551
 
5552
+ // src/ingress/headers/memoized-headers.ts
5553
+ var CACHE = new WeakMap;
5554
+ function memoizedHeaders(entries) {
5555
+ const existing = CACHE.get(entries);
5556
+ if (existing !== undefined)
5557
+ return existing;
5558
+ const built = new Headers(entries);
5559
+ CACHE.set(entries, built);
5560
+ return built;
5561
+ }
5562
+
5381
5563
  // src/ingress/shared.ts
5382
5564
  var DEFAULT_MAX_BODY_BYTES = 1048576;
5383
5565
  var DEFAULT_FAST_OUTPUT_BUFFER_SIZE = 262144;
@@ -5420,10 +5602,9 @@ function resolveIp(req, srv, opts) {
5420
5602
  return opts.getIp ? opts.getIp(req, srv) : undefined;
5421
5603
  }
5422
5604
  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
- };
5605
+ const rateResetSecs = result.rateResetMs > 0 ? secondsFromMs(result.rateResetMs) : undefined;
5606
+ 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));
5607
+ return { status, headers };
5427
5608
  }
5428
5609
  function runBaked(ingress, opts, respond) {
5429
5610
  return (req, srv) => ingress.run(req, resolveIp(req, srv, opts), null, respond);
@@ -5802,6 +5983,18 @@ function rateLimitedBody(retryAfterMs) {
5802
5983
  }
5803
5984
 
5804
5985
  // src/ingress/routes/echo.ts
5986
+ var SAFE_ECHO_CONTENT_TYPES = new Set([
5987
+ "application/json",
5988
+ "application/octet-stream",
5989
+ "text/plain"
5990
+ ]);
5991
+ function echoMediaTypeOf(contentType) {
5992
+ const semi = contentType.indexOf(";");
5993
+ return (semi >= 0 ? contentType.slice(0, semi) : contentType).trim().toLowerCase();
5994
+ }
5995
+ function safeEchoContentType(contentType) {
5996
+ return SAFE_ECHO_CONTENT_TYPES.has(echoMediaTypeOf(contentType)) ? contentType : "application/octet-stream";
5997
+ }
5805
5998
  function echoHandler(ingress, opts = {}) {
5806
5999
  const maxBodyBytes = opts.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
5807
6000
  const bodyTimeoutMs = opts.bodyTimeoutMs ?? DEFAULT_BODY_TIMEOUT_MS;
@@ -5822,32 +6015,39 @@ function echoHandler(ingress, opts = {}) {
5822
6015
  if (prep.terminal)
5823
6016
  return prep.terminal;
5824
6017
  const baseHeaders = prep.headers ?? [];
5825
- const requestedContentType = req.headers.get("content-type") ?? "application/octet-stream";
6018
+ const requestedContentType = safeEchoContentType(req.headers.get("content-type") ?? "application/octet-stream");
6019
+ const withEchoHeaders = (contentType) => {
6020
+ const headers = ingress.withContentType(baseHeaders, contentType);
6021
+ if (!headers.some(([name]) => name.toLowerCase() === "x-content-type-options")) {
6022
+ headers.push(["x-content-type-options", "nosniff"]);
6023
+ }
6024
+ return headers;
6025
+ };
5826
6026
  const contentLengthHeader = req.headers.get("content-length");
5827
6027
  const contentLength = contentLengthHeader === null ? NaN : Number(contentLengthHeader);
5828
6028
  if (Number.isFinite(contentLength)) {
5829
6029
  if (contentLength > maxBodyBytes) {
5830
6030
  return new Response(ERROR_BODIES.body_too_large, {
5831
6031
  status: 413,
5832
- headers: ingress.withContentType(baseHeaders, "application/json")
6032
+ headers: withEchoHeaders("application/json")
5833
6033
  });
5834
6034
  }
5835
6035
  if (contentLength <= 0 || req.body === null) {
5836
6036
  return new Response(null, {
5837
6037
  status: 200,
5838
- headers: ingress.withContentType(baseHeaders, requestedContentType)
6038
+ headers: withEchoHeaders(requestedContentType)
5839
6039
  });
5840
6040
  }
5841
6041
  return new Response(req.body, {
5842
6042
  status: 200,
5843
- headers: ingress.withContentType(baseHeaders, requestedContentType)
6043
+ headers: withEchoHeaders(requestedContentType)
5844
6044
  });
5845
6045
  }
5846
6046
  try {
5847
6047
  const bodyBytes = await readBodyWithLimit(req, maxBodyBytes, true, bodyTimeoutMs);
5848
6048
  return new Response(bodyBytes.byteLength > 0 ? bodyBytes : null, {
5849
6049
  status: 200,
5850
- headers: ingress.withContentType(baseHeaders, requestedContentType)
6050
+ headers: withEchoHeaders(requestedContentType)
5851
6051
  });
5852
6052
  } catch (err) {
5853
6053
  const code = err?.code;
@@ -5855,7 +6055,7 @@ function echoHandler(ingress, opts = {}) {
5855
6055
  const isTimeout = code === "REQUEST_TIMEOUT";
5856
6056
  return new Response(isTooLarge ? ERROR_BODIES.body_too_large : isTimeout ? ERROR_BODIES.request_timeout : ERROR_BODIES.bad_request, {
5857
6057
  status: isTooLarge ? 413 : isTimeout ? 408 : 400,
5858
- headers: ingress.withContentType(baseHeaders, "application/json")
6058
+ headers: withEchoHeaders("application/json")
5859
6059
  });
5860
6060
  }
5861
6061
  };
@@ -5893,6 +6093,11 @@ function defaultJsonValid(bytes) {
5893
6093
  return false;
5894
6094
  }
5895
6095
  }
6096
+ function isJsonContentType(contentType) {
6097
+ const semi = contentType.indexOf(";");
6098
+ const mediaType = (semi >= 0 ? contentType.slice(0, semi) : contentType).trim().toLowerCase();
6099
+ return mediaType === "application/json" || mediaType.endsWith("+json");
6100
+ }
5896
6101
  function jsonWriteHandler(ingress, opts = {}) {
5897
6102
  const maxBodyBytes = opts.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
5898
6103
  const fallback = opts.fallback ?? ingress;
@@ -5902,13 +6107,8 @@ function jsonWriteHandler(ingress, opts = {}) {
5902
6107
  return async (req, srv) => {
5903
6108
  const ip = resolveIp(req, srv, opts);
5904
6109
  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
- });
6110
+ if (!isJsonContentType(contentType)) {
6111
+ return fallback.run(req, ip, null, (result, ctx) => fallback.errorResponse(req, result, 415, "unsupported_media_type", "Content-Type must be application/json", ctx));
5912
6112
  }
5913
6113
  let bodyBytes;
5914
6114
  try {
@@ -6093,7 +6293,7 @@ function safeTerminalStatus(r) {
6093
6293
  }
6094
6294
 
6095
6295
  // src/ingress/routes/responder.ts
6096
- var IGNGEX_SECURITY_HEADERS = [
6296
+ var IGNEX_SECURITY_HEADERS = [
6097
6297
  ["x-frame-options", "DENY"],
6098
6298
  ["x-content-type-options", "nosniff"],
6099
6299
  ["referrer-policy", "no-referrer"]
@@ -6101,7 +6301,7 @@ var IGNGEX_SECURITY_HEADERS = [
6101
6301
  function buildIgngexTerminal(result, _ctx) {
6102
6302
  const preflightAllowed = result.isPreflight && result.corsAllowed;
6103
6303
  if (preflightAllowed) {
6104
- return new Response(null, { status: 204, headers: IGNGEX_SECURITY_HEADERS });
6304
+ return new Response(null, { status: 204, headers: IGNEX_SECURITY_HEADERS });
6105
6305
  }
6106
6306
  const status = safeTerminalStatus(result);
6107
6307
  const body = JSON.stringify({
@@ -6109,7 +6309,7 @@ function buildIgngexTerminal(result, _ctx) {
6109
6309
  status,
6110
6310
  code: errorCodeName(result.errorCode)
6111
6311
  });
6112
- const headers = new Headers(IGNGEX_SECURITY_HEADERS);
6312
+ const headers = new Headers(IGNEX_SECURITY_HEADERS);
6113
6313
  headers.set("content-type", "application/json; charset=utf-8");
6114
6314
  return new Response(body, { status, headers });
6115
6315
  }
@@ -6137,7 +6337,7 @@ function nativeResponderRoute(ingress, responder, opts = {}) {
6137
6337
  const code = err.code;
6138
6338
  const status = code === "REQUEST_TIMEOUT" ? 408 : code === "BODY_TOO_LARGE" ? 413 : 400;
6139
6339
  const payload = JSON.stringify({ error: "Bad Request", status, code: "BAD_REQUEST" });
6140
- const headers = new Headers(IGNGEX_SECURITY_HEADERS);
6340
+ const headers = new Headers(IGNEX_SECURITY_HEADERS);
6141
6341
  headers.set("content-type", "application/json; charset=utf-8");
6142
6342
  return new Response(payload, { status, headers });
6143
6343
  }
@@ -6468,7 +6668,7 @@ function nodeRequestToWebRequest(req) {
6468
6668
  }
6469
6669
  const method = req.method ?? "GET";
6470
6670
  const bodyForbidden = method === "GET" || method === "HEAD";
6471
- const hasBody = Number(req.headers["content-length"] ?? 0) > 0 || req.headers["transfer-encoding"] !== undefined;
6671
+ const hasBody = requestHasBody(req);
6472
6672
  const init = { method, headers };
6473
6673
  if (hasBody && !bodyForbidden) {
6474
6674
  init.body = Readable.toWeb(req);
@@ -6512,7 +6712,7 @@ async function writeResponse(res, response) {
6512
6712
  }
6513
6713
  function drainUnreadBody(res) {
6514
6714
  const req = res.req;
6515
- if (req !== undefined && req !== null && !req.complete && hasBody(req)) {
6715
+ if (req !== undefined && req !== null && !req.complete && requestHasBody(req)) {
6516
6716
  req.resume();
6517
6717
  res.shouldKeepAlive = false;
6518
6718
  if (!res.headersSent) {
@@ -6520,7 +6720,7 @@ function drainUnreadBody(res) {
6520
6720
  }
6521
6721
  }
6522
6722
  }
6523
- function hasBody(req) {
6723
+ function requestHasBody(req) {
6524
6724
  return Number(req.headers["content-length"] ?? 0) > 0 || req.headers["transfer-encoding"] !== undefined;
6525
6725
  }
6526
6726
  function makeRequestListener(options, routes, baseOpts) {
@@ -6576,6 +6776,23 @@ function makeRequestListener(options, routes, baseOpts) {
6576
6776
  }
6577
6777
  };
6578
6778
  }
6779
+ var WS_PROTOCOL_TOKEN = /^[A-Za-z0-9!#$%&'*+\-.^_`|~]+$/;
6780
+ function formatWebSocketHandshake(accept, protocol) {
6781
+ if (/[\r\n]/.test(accept)) {
6782
+ throw new Error("invalid Sec-WebSocket-Accept value");
6783
+ }
6784
+ let raw = `HTTP/1.1 101 Switching Protocols\r
6785
+ ` + `Upgrade: websocket\r
6786
+ ` + `Connection: Upgrade\r
6787
+ ` + `Sec-WebSocket-Accept: ${accept}\r
6788
+ `;
6789
+ if (protocol && WS_PROTOCOL_TOKEN.test(protocol)) {
6790
+ raw += `Sec-WebSocket-Protocol: ${protocol}\r
6791
+ `;
6792
+ }
6793
+ return `${raw}\r
6794
+ `;
6795
+ }
6579
6796
  function makeUpgradeListener(options) {
6580
6797
  return async (req, socket, head) => {
6581
6798
  try {
@@ -6585,18 +6802,7 @@ function makeUpgradeListener(options) {
6585
6802
  socket.destroy();
6586
6803
  return;
6587
6804
  }
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);
6805
+ socket.write(formatWebSocketHandshake(handshake.accept, handshake.protocol));
6600
6806
  options.onUpgrade?.(socket, req, head);
6601
6807
  } catch {
6602
6808
  socket.destroy();
@@ -6820,6 +7026,7 @@ class IngressResultBase {
6820
7026
  bodyTruncated = false;
6821
7027
  body = EMPTY_BODY2;
6822
7028
  _buf = EMPTY_BODY2;
7029
+ _len = 0;
6823
7030
  _bodyJsonStart = OUT_DATA_START;
6824
7031
  _bodyJsonLen = 0;
6825
7032
  _cookiesBuf = null;
@@ -6845,6 +7052,7 @@ class IngressResultBase {
6845
7052
  this.bodyTruncated = false;
6846
7053
  this.body = EMPTY_BODY2;
6847
7054
  this._buf = EMPTY_BODY2;
7055
+ this._len = 0;
6848
7056
  this._bodyJsonStart = OUT_DATA_START;
6849
7057
  this._bodyJsonLen = 0;
6850
7058
  this._cookiesBuf = null;
@@ -6869,8 +7077,9 @@ class IngressResultBase {
6869
7077
  this.retryAfterMs = 0;
6870
7078
  }
6871
7079
  }
6872
- setSections(buf, layout, bodyJsonLen) {
7080
+ setSections(buf, layout, bodyJsonLen, len = buf.byteLength) {
6873
7081
  this._buf = buf;
7082
+ this._len = len;
6874
7083
  this._bodyJsonStart = layout.bodyJsonStart;
6875
7084
  this._bodyJsonLen = bodyJsonLen;
6876
7085
  this._cookiesBuf = layout.safeCookiesLen > 0 ? buf.subarray(OUT_DATA_START, OUT_DATA_START + layout.safeCookiesLen) : null;
@@ -6900,7 +7109,7 @@ class IngressResultBase {
6900
7109
  if (this._bodyJsonLen === 0)
6901
7110
  return EMPTY_BODY2;
6902
7111
  const end = this._bodyJsonStart + this._bodyJsonLen;
6903
- if (end > this._buf.byteLength)
7112
+ if (end > this._len)
6904
7113
  return EMPTY_BODY2;
6905
7114
  return this._buf.subarray(this._bodyJsonStart, end);
6906
7115
  }
@@ -6964,7 +7173,7 @@ class FastIngressResult extends IngressResultBase {
6964
7173
  this.isPreflight = (flags & FLAG_IS_PREFLIGHT) !== 0;
6965
7174
  this.rateLimited = (flags & FLAG_RATE_LIMITED) !== 0;
6966
7175
  const layout = sectionLayout(buf.byteLength, cookiesJsonLen, queryJsonLen, bodyJsonLen);
6967
- this.setSections(buf, layout, bodyJsonLen);
7176
+ this.setSections(buf, layout, bodyJsonLen, buf.byteLength);
6968
7177
  this.bodyTruncated = (flags & FLAG_BODY_TRUNCATED) !== 0 || layout.truncated;
6969
7178
  }
6970
7179
  invalidate() {
@@ -7056,7 +7265,14 @@ function warnTrustProxyDeprecated() {
7056
7265
  if (trustProxyWarned)
7057
7266
  return;
7058
7267
  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.");
7268
+ 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.");
7269
+ }
7270
+ var rateLimitNoIpWarned = false;
7271
+ function warnRateLimitWithoutGetIp() {
7272
+ if (rateLimitNoIpWarned)
7273
+ return;
7274
+ rateLimitNoIpWarned = true;
7275
+ 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
7276
  }
7061
7277
 
7062
7278
  // src/ingress/packing/header-packing.ts
@@ -7622,6 +7838,11 @@ class BufferPool {
7622
7838
  this.estimate = options.adaptive === true ? new AdaptiveEstimate({ alpha: 0.25, min: this.initialSize }) : null;
7623
7839
  this.free.push(new Uint8Array(this.initialSize));
7624
7840
  this.created = 1;
7841
+ 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);
7842
+ for (let i = 0;i < prefill; i++) {
7843
+ this.free.push(new Uint8Array(this.initialSize));
7844
+ this.created++;
7845
+ }
7625
7846
  }
7626
7847
  get freeCount() {
7627
7848
  return this.free.length;
@@ -7655,7 +7876,9 @@ class BufferPool {
7655
7876
  for (let i = 0;i < this.free.length; i++) {
7656
7877
  const candidate = this.free[i];
7657
7878
  if (candidate.byteLength >= minSize) {
7658
- this.free.splice(i, 1);
7879
+ const last = this.free.pop();
7880
+ if (i < this.free.length)
7881
+ this.free[i] = last;
7659
7882
  return candidate;
7660
7883
  }
7661
7884
  }
@@ -7670,7 +7893,9 @@ class BufferPool {
7670
7893
  }
7671
7894
  }
7672
7895
  if (largestBuf !== null) {
7673
- this.free.splice(largest, 1);
7896
+ const last = this.free.pop();
7897
+ if (largest < this.free.length)
7898
+ this.free[largest] = last;
7674
7899
  this.created++;
7675
7900
  return new Uint8Array(target);
7676
7901
  }
@@ -7679,16 +7904,15 @@ class BufferPool {
7679
7904
  }
7680
7905
  releaseBuffer(buffer) {
7681
7906
  if (this.free.length >= this.maxBuffers) {
7682
- let smallestBuf = null;
7683
7907
  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;
7908
+ for (let i = 1;i < this.free.length; i++) {
7909
+ if (this.free[i].byteLength < this.free[smallest].byteLength) {
7688
7910
  smallest = i;
7689
7911
  }
7690
7912
  }
7691
- this.free.splice(smallest, 1);
7913
+ const last = this.free.pop();
7914
+ if (smallest < this.free.length)
7915
+ this.free[smallest] = last;
7692
7916
  }
7693
7917
  this.free.push(buffer);
7694
7918
  }
@@ -7748,8 +7972,8 @@ function createStructuredLogger(stream = process.stderr, env = process.env) {
7748
7972
 
7749
7973
  // src/ingress/decode/baked-result.ts
7750
7974
  class BakedIngressResult extends IngressResultBase {
7751
- refresh(buf, body, view) {
7752
- if (buf.byteLength < OUT_DATA_START) {
7975
+ refresh(buf, body, view, len = buf.byteLength) {
7976
+ if (len < OUT_DATA_START) {
7753
7977
  this.setInternalError();
7754
7978
  return;
7755
7979
  }
@@ -7762,7 +7986,7 @@ class BakedIngressResult extends IngressResultBase {
7762
7986
  const queryLenRaw = view.getUint32(OUT_QUERY_JSON_LEN, true);
7763
7987
  const headerVariant = view.getUint8(OUT_HEADER_VARIANT);
7764
7988
  const bodyJsonLenRaw = view.getUint32(OUT_BODY_JSON_LEN, true);
7765
- const layout = sectionLayout(buf.byteLength, cookiesLenRaw, queryLenRaw, bodyJsonLenRaw);
7989
+ const layout = sectionLayout(len, cookiesLenRaw, queryLenRaw, bodyJsonLenRaw);
7766
7990
  if (h0 === 0 && h1 === 0) {
7767
7991
  this.verdict = 1;
7768
7992
  this.errorCode = ERR_CODE_INTERNAL;
@@ -7777,7 +8001,7 @@ class BakedIngressResult extends IngressResultBase {
7777
8001
  this.rateRemaining = h3;
7778
8002
  this.setRateWindow(h2, (flags & FLAG_RATE_LIMITED) !== 0, view);
7779
8003
  this.headerVariant = headerVariant;
7780
- this.setSections(buf, layout, layout.safeBodyJsonLen);
8004
+ this.setSections(buf, layout, layout.safeBodyJsonLen, len);
7781
8005
  this.updateOkTerminal();
7782
8006
  this.isPreflight = (flags & FLAG_IS_PREFLIGHT) !== 0;
7783
8007
  this.corsAllowed = (flags & FLAG_CORS_ALLOWED) !== 0;
@@ -7800,8 +8024,16 @@ class BakedIngressResult extends IngressResultBase {
7800
8024
  this.ok = false;
7801
8025
  }
7802
8026
  bodyJson(copy) {
8027
+ if (copy) {
8028
+ if (this._bodyJsonLen === 0)
8029
+ return this._buf.slice(0, 0);
8030
+ const end = this._bodyJsonStart + this._bodyJsonLen;
8031
+ if (end > this._len)
8032
+ return this._buf.slice(0, 0);
8033
+ return this._buf.slice(this._bodyJsonStart, end);
8034
+ }
7803
8035
  const slice = this.bodyJsonSlice();
7804
- return copy ? slice.slice() : slice;
8036
+ return slice;
7805
8037
  }
7806
8038
  }
7807
8039
 
@@ -7925,6 +8157,13 @@ function cachedOriginBlock(origin) {
7925
8157
  ORIGIN_BLOCK_CACHE.set(origin, block);
7926
8158
  return block;
7927
8159
  }
8160
+ function prewarmOriginBlocks(origins) {
8161
+ for (const origin of origins) {
8162
+ if (typeof origin === "string" && origin.length > 0 && origin.length <= MAX_SMALL_HEADER_BYTES && !origin.includes("*")) {
8163
+ cachedOriginBlock(origin);
8164
+ }
8165
+ }
8166
+ }
7928
8167
 
7929
8168
  // src/ingress/response/baked-response.ts
7930
8169
  init_bytes();
@@ -7977,6 +8216,12 @@ function pooledBodyResponse(handle, bytes, init = {}, timeoutMs = 0) {
7977
8216
 
7978
8217
  // src/ingress/response/baked-response.ts
7979
8218
  var EMPTY_BODY4 = new Uint8Array(0);
8219
+ var ORIGIN_HEADER_CACHE_MAX = 64;
8220
+ function prewarmOriginHeaders(responseHeaders, origins, variant) {
8221
+ for (const origin of origins) {
8222
+ memoizedHeaders(responseHeaders(variant, null, origin));
8223
+ }
8224
+ }
7980
8225
  function buildBakedResponseBuilders(deps) {
7981
8226
  const {
7982
8227
  headerTemplates,
@@ -7986,6 +8231,45 @@ function buildBakedResponseBuilders(deps) {
7986
8231
  originHeaderCache,
7987
8232
  state
7988
8233
  } = deps;
8234
+ function originBaseEntries(variant, origin) {
8235
+ const key = `${variant & 31}\x00${origin}`;
8236
+ const cached = originHeaderCache.get(key);
8237
+ if (cached !== undefined) {
8238
+ return cached;
8239
+ }
8240
+ const template = headerTemplates[variant & 31] ?? headerTemplates[0] ?? [];
8241
+ const entries = new Array(template.length + 1);
8242
+ let i = 0;
8243
+ for (const pair of template) {
8244
+ entries[i++] = pair;
8245
+ }
8246
+ entries[i] = ["access-control-allow-origin", origin];
8247
+ if (originHeaderCache.size >= ORIGIN_HEADER_CACHE_MAX) {
8248
+ const oldest = originHeaderCache.keys().next().value;
8249
+ if (oldest !== undefined)
8250
+ originHeaderCache.delete(oldest);
8251
+ }
8252
+ originHeaderCache.set(key, entries);
8253
+ return entries;
8254
+ }
8255
+ function successHeaders(variant, requestIdHeader, origin, rateRemaining, rateResetSecs) {
8256
+ const needsRequestId = emitRequestIdHeader && requestIdHeader !== null;
8257
+ const needsOrigin = ((variant & HV_CORS_SIMPLE) !== 0 || (variant & HV_CORS_PREFLIGHT) !== 0) && origin !== null;
8258
+ const needsRate = (variant & HV_RATE_ACTIVE) !== 0;
8259
+ const base = needsOrigin ? memoizedHeaders(originBaseEntries(variant, origin)) : memoizedHeaders(headerTemplates[variant & 31] ?? headerTemplates[0] ?? []);
8260
+ if (!needsRequestId && !needsRate) {
8261
+ return base;
8262
+ }
8263
+ const headers = new Headers(base);
8264
+ if (needsRequestId) {
8265
+ headers.set("x-request-id", requestIdHeader);
8266
+ }
8267
+ if (needsRate) {
8268
+ headers.set("ratelimit-remaining", String(rateRemaining ?? 0));
8269
+ headers.set("ratelimit-reset", String(rateResetSecs ?? 0));
8270
+ }
8271
+ return headers;
8272
+ }
7989
8273
  function responseHeaders(variant, requestIdHeader, origin, rateRemaining, rateResetSecs, retryAfterSecs) {
7990
8274
  const template = headerTemplates[variant & 31] ?? headerTemplates[0] ?? [];
7991
8275
  const needsRequestId = emitRequestIdHeader && requestIdHeader !== null;
@@ -7996,19 +8280,7 @@ function buildBakedResponseBuilders(deps) {
7996
8280
  return template;
7997
8281
  }
7998
8282
  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;
8283
+ return originBaseEntries(variant, origin);
8012
8284
  }
8013
8285
  let extra = 0;
8014
8286
  if (needsRequestId)
@@ -8069,27 +8341,27 @@ function buildBakedResponseBuilders(deps) {
8069
8341
  if (preflightAllowed) {
8070
8342
  return new Response(null, {
8071
8343
  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)
8344
+ 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
8345
  });
8074
8346
  }
8075
8347
  const status = safeTerminalStatus(result);
8076
8348
  const body = result.errorCode === ERR_CODE_RATE_LIMITED ? rateLimitedBody(result.retryAfterMs) : ERROR_CODE_BODIES[result.errorCode] ?? ERROR_BODIES.internal ?? EMPTY_BODY4;
8077
8349
  return new Response(body, {
8078
8350
  status,
8079
- headers: terminalHeaders(result.headerVariant, ctx, result)
8351
+ headers: memoizedHeaders(terminalHeaders(result.headerVariant, ctx, result))
8080
8352
  });
8081
8353
  }
8082
8354
  function errorResponse(_req, result, status, code, message, ctx) {
8083
8355
  const body = ERROR_BODIES[code] ?? encoder.encode(JSON.stringify({ ok: false, error: { code, message } }));
8084
8356
  return new Response(body, {
8085
8357
  status,
8086
- headers: terminalHeaders(result?.headerVariant ?? HV_JSON, ctx, result)
8358
+ headers: memoizedHeaders(terminalHeaders(result?.headerVariant ?? HV_JSON, ctx, result))
8087
8359
  });
8088
8360
  }
8089
8361
  function internalErrorResponse(ctx, result) {
8090
8362
  return new Response(ERROR_BODIES.internal, {
8091
8363
  status: 500,
8092
- headers: terminalHeaders(result?.headerVariant ?? HV_JSON, ctx, result ?? null)
8364
+ headers: memoizedHeaders(terminalHeaders(result?.headerVariant ?? HV_JSON, ctx, result ?? null))
8093
8365
  });
8094
8366
  }
8095
8367
  function withContentType(headers, contentType) {
@@ -8110,6 +8382,7 @@ function buildBakedResponseBuilders(deps) {
8110
8382
  }
8111
8383
  return {
8112
8384
  responseHeaders,
8385
+ successHeaders,
8113
8386
  terminalHeaders,
8114
8387
  terminalResponse,
8115
8388
  errorResponse,
@@ -8124,6 +8397,8 @@ var EMPTY_BODY5 = new Uint8Array(0);
8124
8397
  var EMPTY_IP = "0.0.0.0";
8125
8398
  var RATE_LIMIT_U32_MAX = 4294967295;
8126
8399
  var MAX_FFI_FAILURES = 3;
8400
+ var PREWARM_POOL_BUFFERS = 16;
8401
+ var PREWARM_POOL_BYTES = 4 * 1024 * 1024;
8127
8402
  function pathForLog(req) {
8128
8403
  try {
8129
8404
  return new URL(req.url).pathname;
@@ -8160,14 +8435,24 @@ function createIngressHandler(options = {}, runtime = {}) {
8160
8435
  const outputBufferSize = clampIngressBufferSize(runtime.outputBufferSize ?? DEFAULT_BAKED_OUTPUT_BUFFER_SIZE, OUT_DATA_START);
8161
8436
  const logger = runtime.logger ?? (runtime.structuredLog === true ? createStructuredLogger() : null);
8162
8437
  const needRequestIdString = !!(emitRequestIdHeader || runtime.onRequest || runtime.onError || runtime.onResponse || logger);
8438
+ const hasOnRequest = runtime.onRequest !== undefined;
8439
+ const hasOnResponse = runtime.onResponse !== undefined;
8163
8440
  const outputPool = new BufferPool({
8164
8441
  initialSize: outputBufferSize,
8165
- maxInFlight: runtime.maxInFlight
8442
+ maxInFlight: runtime.maxInFlight,
8443
+ prefill: PREWARM_POOL_BUFFERS,
8444
+ prefillBytes: PREWARM_POOL_BYTES
8166
8445
  });
8167
8446
  const inputPacker = new IngressInputPacker;
8168
8447
  const responseState = { currentHandle: null, responseBorrowsBuffer: false };
8169
8448
  let ffiFailures = 0;
8170
8449
  let ffiDisabled = false;
8450
+ const noteFfiFailure = () => {
8451
+ ffiFailures++;
8452
+ if (ffiFailures >= MAX_FFI_FAILURES) {
8453
+ ffiDisabled = true;
8454
+ }
8455
+ };
8171
8456
  const originHeaderCache = new Map;
8172
8457
  const cors = options.cors;
8173
8458
  const securityEntries = runtime.enableSecurityHeaders === false ? Object.freeze([]) : Object.freeze(buildBakedSecurityEntries(options.security, options.https, runtime.securityHeaders));
@@ -8193,6 +8478,17 @@ function createIngressHandler(options = {}, runtime = {}) {
8193
8478
  originHeaderCache,
8194
8479
  state: responseState
8195
8480
  });
8481
+ if (headerPlan.cors && typeof cors?.allowOrigin === "object") {
8482
+ const exactOrigins = [];
8483
+ for (const origin of cors.allowOrigin) {
8484
+ if (typeof origin === "string" && origin.length > 0 && origin.length <= 512 && !origin.includes("*")) {
8485
+ exactOrigins.push(origin);
8486
+ }
8487
+ }
8488
+ prewarmOriginBlocks(exactOrigins);
8489
+ const successVariant = HV_JSON | HV_CORS_SIMPLE | (rateEnabled ? HV_RATE_ACTIVE : 0);
8490
+ prewarmOriginHeaders(responseBuilders.responseHeaders, exactOrigins, successVariant);
8491
+ }
8196
8492
  const result = new BakedIngressResult;
8197
8493
  const ctx = {
8198
8494
  requestIdHeader: null,
@@ -8205,7 +8501,8 @@ function createIngressHandler(options = {}, runtime = {}) {
8205
8501
  const requestIdStr = needRequestIdString ? decoder.decode(ridBytes) : "";
8206
8502
  ctx.requestIdHeader = emitRequestIdHeader ? requestIdStr : null;
8207
8503
  ctx.origin = headerPlan.cors ? req.headers.get("origin") : null;
8208
- runtime.onRequest?.(req, requestIdStr, ip);
8504
+ if (hasOnRequest)
8505
+ runtime.onRequest?.(req, requestIdStr, ip);
8209
8506
  const ipStr = ip ?? EMPTY_IP;
8210
8507
  let handle = null;
8211
8508
  responseState.currentHandle = null;
@@ -8223,10 +8520,7 @@ function createIngressHandler(options = {}, runtime = {}) {
8223
8520
  written = bunFFI.ingressHandleComponents(ingressPtr, methodKind, packUrl, packIp ?? "", ridBytes, packedHeaders, body, handle.buffer);
8224
8521
  ffiFailures = 0;
8225
8522
  } catch {
8226
- ffiFailures++;
8227
- if (ffiFailures >= MAX_FFI_FAILURES) {
8228
- ffiDisabled = true;
8229
- }
8523
+ noteFfiFailure();
8230
8524
  const input = inputPacker.packParts(methodKind, packUrl, packIp, ridBytes, packedHeaders);
8231
8525
  written = handler.handleRequestPacked(input, body, handle.buffer);
8232
8526
  }
@@ -8236,18 +8530,14 @@ function createIngressHandler(options = {}, runtime = {}) {
8236
8530
  written = bunFFI.ingressHandlePacked(ingressPtr, input, body, handle.buffer);
8237
8531
  ffiFailures = 0;
8238
8532
  } catch {
8239
- ffiFailures++;
8240
- if (ffiFailures >= MAX_FFI_FAILURES) {
8241
- ffiDisabled = true;
8242
- }
8533
+ noteFfiFailure();
8243
8534
  written = handler.handleRequestPacked(input, body, handle.buffer);
8244
8535
  }
8245
8536
  } else {
8246
8537
  const input = inputPacker.packParts(methodKind, packUrl, packIp, ridBytes, packedHeaders);
8247
8538
  written = handler.handleRequestPacked(input, body, handle.buffer);
8248
8539
  }
8249
- const used = handle.buffer.subarray(0, written);
8250
- result.refresh(used, body ?? EMPTY_BODY5, viewForArrayBuffer(used.buffer, used.byteOffset));
8540
+ result.refresh(handle.buffer, body ?? EMPTY_BODY5, viewForArrayBuffer(handle.buffer.buffer, handle.buffer.byteOffset), written);
8251
8541
  } catch (err) {
8252
8542
  result.setInternalError();
8253
8543
  const error = err instanceof Error ? err : new Error(String(err));
@@ -8263,17 +8553,20 @@ function createIngressHandler(options = {}, runtime = {}) {
8263
8553
  try {
8264
8554
  const out = fn(result, ctx);
8265
8555
  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
- });
8556
+ if (hasOnResponse || logger !== null) {
8557
+ const status = out instanceof Response ? out.status : safeTerminalStatus(result);
8558
+ if (hasOnResponse)
8559
+ runtime.onResponse?.(req, result, status, requestIdStr);
8560
+ if (logger !== null) {
8561
+ logger.request({
8562
+ requestId: requestIdStr,
8563
+ method: req.method,
8564
+ path: pathForLog(req),
8565
+ status,
8566
+ durationMs: Math.round((performance.now() - startedAt) * 1000) / 1000,
8567
+ ip: ipStr
8568
+ });
8569
+ }
8277
8570
  }
8278
8571
  return out;
8279
8572
  } finally {
@@ -8587,6 +8880,10 @@ function createIngressRouter(options) {
8587
8880
  }
8588
8881
  const handler = createIngressHandler(spec.options ?? {}, runtime);
8589
8882
  compiled[path] = handler;
8883
+ const routeLimit = spec.options?.rateLimit?.limit;
8884
+ if (typeof routeLimit === "number" && routeLimit > 0 && routeLimit !== RATE_LIMIT_U32_MAX && options.getIp === undefined) {
8885
+ warnRateLimitWithoutGetIp();
8886
+ }
8590
8887
  if (spec.responder) {
8591
8888
  bakedRoutes[path] = {
8592
8889
  responder: {
@@ -8811,6 +9108,22 @@ function createIngress(options = {}) {
8811
9108
  }
8812
9109
  };
8813
9110
  }
9111
+ // src/native/ingress-binding.ts
9112
+ init_ffi();
9113
+ var cached2;
9114
+ function getIngressBinding() {
9115
+ if (cached2 !== undefined)
9116
+ return cached2;
9117
+ const ffi = getBunFFI();
9118
+ const addonPath = ffiAddonPath();
9119
+ cached2 = ffi && addonPath ? {
9120
+ addonPath,
9121
+ ingressHandleComponents: ffi.ingressHandleComponents,
9122
+ ingressHandlePacked: ffi.ingressHandlePacked,
9123
+ ingressLayout: ffi.ingressLayout
9124
+ } : null;
9125
+ return cached2;
9126
+ }
8814
9127
  // src/loader/index.ts
8815
9128
  init_rust_ffi();
8816
9129
  init_bytes();
@@ -10228,6 +10541,7 @@ function createTaskRuntime(options = {}) {
10228
10541
  queueMicrotask(drainNow);
10229
10542
  }
10230
10543
  function submit(op, args, runOptions) {
10544
+ ensureDoorbell();
10231
10545
  const id = nextId++;
10232
10546
  return new Promise((resolve, reject) => {
10233
10547
  inflight.set(id, { out: null, keep: null, resolve, reject });
@@ -10249,6 +10563,7 @@ function createTaskRuntime(options = {}) {
10249
10563
  });
10250
10564
  }
10251
10565
  function submitInto(op, args, output, runOptions) {
10566
+ ensureDoorbell();
10252
10567
  const id = nextId++;
10253
10568
  return new Promise((resolve, reject) => {
10254
10569
  inflight.set(id, { out: output, keep: null, resolve, reject });
@@ -10270,6 +10585,7 @@ function createTaskRuntime(options = {}) {
10270
10585
  });
10271
10586
  }
10272
10587
  function submitSlice(op, hdr, data, runOptions) {
10588
+ ensureDoorbell();
10273
10589
  const id = nextId++;
10274
10590
  return new Promise((resolve, reject) => {
10275
10591
  inflight.set(id, { out: null, keep: data, resolve, reject });
@@ -10291,11 +10607,16 @@ function createTaskRuntime(options = {}) {
10291
10607
  });
10292
10608
  }
10293
10609
  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));
10610
+ function ensureDoorbell() {
10611
+ if (doorbell !== null)
10612
+ return;
10613
+ const cb = new JSCallback(() => {
10614
+ drainNow();
10615
+ }, { args: [], returns: "void", threadsafe: true });
10616
+ doorbell = cb;
10617
+ ffi.taskSetDoorbell(Number(cb.ptr));
10618
+ }
10619
+ ensureDoorbell();
10299
10620
  schedule();
10300
10621
  const runtime = {
10301
10622
  gzipDecompress(data, runOptions) {
@@ -10339,6 +10660,7 @@ function createTaskRuntime(options = {}) {
10339
10660
  deferred.reject(cancelledError());
10340
10661
  }
10341
10662
  inflight.clear();
10663
+ ffi.taskSetDoorbell(0);
10342
10664
  ffi.taskShutdown();
10343
10665
  release();
10344
10666
  doorbell?.close();
@@ -10475,6 +10797,7 @@ export {
10475
10797
  errorMessage,
10476
10798
  fallbackHandler,
10477
10799
  generateRequestId,
10800
+ getIngressBinding,
10478
10801
  gracefulShutdown,
10479
10802
  gzipCompressUpperBound,
10480
10803
  headHandler,