castrum 0.9.6 → 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 (88) hide show
  1. package/Cargo.lock +4 -3
  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 +1658 -1314
  13. package/docs/ARCHITECTURE.md +1 -1
  14. package/docs/BENCHMARKS.md +13 -0
  15. package/docs/CASE_STUDY.md +2 -2
  16. package/docs/ENVIRONMENT.md +1 -1
  17. package/docs/FFI_BUN_GUIDE.md +13 -1
  18. package/docs/HOW_TO_ADD_AN_OP.md +24 -0
  19. package/docs/INGRESS.md +4 -4
  20. package/docs/REPO_MAP.md +8 -6
  21. package/docs/superpowers/plans/2026-09-18-castrum-hardening-cleanup.md +617 -0
  22. package/index.ts +1 -0
  23. package/package.json +3 -2
  24. package/rust/crypto/aead.rs +44 -0
  25. package/rust/crypto/argon2.rs +32 -2
  26. package/rust/crypto/jwt/tests.rs +19 -0
  27. package/rust/crypto/jwt/token.rs +43 -28
  28. package/rust/crypto/session.rs +170 -7
  29. package/rust/ffi/crypto.rs +182 -61
  30. package/rust/ffi/hashing.rs +48 -33
  31. package/rust/ffi/http.rs +62 -36
  32. package/rust/ffi/metrics.rs +6 -2
  33. package/rust/ffi/payload.rs +47 -27
  34. package/rust/ffi/task.rs +51 -32
  35. package/rust/ffi/tests.rs +197 -0
  36. package/rust/ffi/util.rs +40 -0
  37. package/rust/http/headers.rs +13 -0
  38. package/rust/http/multipart.rs +139 -0
  39. package/rust/ingress/native_route.rs +21 -3
  40. package/rust/task/completion.rs +7 -0
  41. package/rust/task/mod.rs +1 -1
  42. package/rust/task/tests.rs +30 -0
  43. package/src/ingress/decode/baked-result.ts +13 -5
  44. package/src/ingress/decode/fast-result.ts +1 -1
  45. package/src/ingress/decode/result-base.ts +16 -2
  46. package/src/ingress/handlers.ts +103 -31
  47. package/src/ingress/headers/memoized-headers.ts +40 -0
  48. package/src/ingress/native-route.ts +25 -6
  49. package/src/ingress/options.ts +26 -2
  50. package/src/ingress/packing/gather-raw-headers.ts +24 -8
  51. package/src/ingress/response/baked-response.ts +118 -28
  52. package/src/ingress/router.ts +14 -2
  53. package/src/ingress/routes/common.ts +26 -10
  54. package/src/ingress/routes/echo.ts +50 -7
  55. package/src/ingress/routes/json-write.ts +21 -8
  56. package/src/ingress/routes/responder.ts +4 -4
  57. package/src/ingress/server-node.ts +33 -14
  58. package/src/ingress/server.ts +3 -4
  59. package/src/ingress/shared.ts +1 -1
  60. package/src/ingress/types.ts +15 -0
  61. package/src/loader/ops.ts +1 -1
  62. package/src/native/ffi/build/codecs-selftest.ts +305 -0
  63. package/src/native/ffi/build/codecs.ts +0 -296
  64. package/src/native/ffi/build/instances-selftest.ts +264 -0
  65. package/src/native/ffi/build/instances.ts +39 -241
  66. package/src/native/ffi/build/metrics.ts +0 -7
  67. package/src/native/ffi/build/parse-selftest.ts +238 -0
  68. package/src/native/ffi/build/parse.ts +11 -234
  69. package/src/native/ffi/build.ts +5 -1
  70. package/src/native/ffi/selftest.ts +3 -3
  71. package/src/native/ffi/symbols.ts +460 -0
  72. package/src/native/ffi/types.ts +17 -0
  73. package/src/native/ffi.ts +2 -422
  74. package/src/native/ingress-binding.ts +39 -0
  75. package/src/runtime/codec.ts +24 -0
  76. package/src/runtime/types.ts +11 -0
  77. package/src/rust-ffi/batch/build.ts +18 -8
  78. package/src/rust-ffi/batch/types.ts +15 -0
  79. package/src/rust-ffi/options.ts +19 -0
  80. package/src/rust-ffi/scalar/crypto.ts +8 -0
  81. package/src/rust-ffi/scalar/factories.ts +47 -20
  82. package/src/rust-ffi/scalar/http.ts +60 -10
  83. package/src/rust-ffi/scalar/interface.ts +4 -1
  84. package/src/rust-ffi/scalar/payload.ts +27 -1
  85. package/src/shared/buffer-pool.ts +44 -9
  86. package/src/shared/codec.ts +26 -0
  87. package/src/shared/packed/parsers.ts +64 -7
  88. 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;
@@ -1085,6 +1241,8 @@ function buildInstances(sym, ctx) {
1085
1241
  const cookieValidateSym = sym.castrum_cookie_validate;
1086
1242
  const sessionSealSym = sym.castrum_session_seal;
1087
1243
  const sessionOpenSym = sym.castrum_session_open;
1244
+ const sessionSealBytesSym = sym.castrum_session_seal_bytes;
1245
+ const sessionOpenBytesSym = sym.castrum_session_open_bytes;
1088
1246
  const rateLimiterCheckRaw = sym.castrum_rate_limiter_check;
1089
1247
  const rateLimiterCheckKeyRaw = sym.castrum_rate_limiter_check_key;
1090
1248
  const jwtSignBytes = sym.castrum_jwt_sign_bytes;
@@ -1111,7 +1269,10 @@ function buildInstances(sym, ctx) {
1111
1269
  return acceptNegotiatorNegotiateRaw(inner, header, lenOrView(header));
1112
1270
  },
1113
1271
  acceptNegotiatorNegotiateServer(inner, header) {
1114
- return acceptNegotiatorNegotiateServerRaw(inner, decodeUtf8(header));
1272
+ const text = decodeUtf8(header);
1273
+ if (hasNul(text))
1274
+ return null;
1275
+ return acceptNegotiatorNegotiateServerRaw(inner, text);
1115
1276
  },
1116
1277
  jwtSignerSign(inner, claimsJson, nowSeconds) {
1117
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");
@@ -1151,7 +1312,15 @@ function buildInstances(sym, ctx) {
1151
1312
  const w = Number(sessionOpenSym(token, secret, output, lenOrView(output)));
1152
1313
  return w;
1153
1314
  },
1315
+ sessionSealBytes(id, dataJson, expSecs, secret) {
1316
+ return sessionSealBytesSym(id, lenOrView(id), dataJson, lenOrView(dataJson), BigInt(expSecs), secret, lenOrView(secret));
1317
+ },
1318
+ sessionOpenBytes(token, secret, output) {
1319
+ return Number(sessionOpenBytesSym(token, lenOrView(token), secret, lenOrView(secret), output, lenOrView(output)));
1320
+ },
1154
1321
  rateLimiterCheck(inner, key, nowMs) {
1322
+ if (hasNul(key))
1323
+ return { allowed: false, remaining: 0, resetMs: 0 };
1155
1324
  const out = rateScratch;
1156
1325
  const w = Number(rateLimiterCheckRaw(inner, key, BigInt(Math.trunc(nowMs)), out, lenOrView(out)));
1157
1326
  if (w === 0)
@@ -1244,178 +1413,6 @@ function buildInstances(sym, ctx) {
1244
1413
  }
1245
1414
  };
1246
1415
  }
1247
- function selfTestInstances(b) {
1248
- const enc = { encode: encodeUtf8 };
1249
- const dec = { decode: decodeUtf8 };
1250
- const layoutBuf = new Uint8Array(38 * 4);
1251
- b.ingressLayout(layoutBuf);
1252
- const layoutView = new DataView(layoutBuf.buffer, layoutBuf.byteOffset, layoutBuf.byteLength);
1253
- 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) {
1254
- return false;
1255
- }
1256
- try {
1257
- b.ingressHandlePacked(0, enc.encode("/"), null, new Uint8Array(64));
1258
- return false;
1259
- } catch {}
1260
- try {
1261
- b.ingressHandleComponents(0, 0, "/", "", enc.encode("rid"), new Uint8Array(2), null, new Uint8Array(64));
1262
- return false;
1263
- } catch {}
1264
- const routeDesc = new Uint8Array(33);
1265
- const rd = new DataView(routeDesc.buffer);
1266
- rd.setUint32(0, 1380930900, true);
1267
- rd.setUint32(4, 3, true);
1268
- rd.setUint32(8, 2 * 1024 * 1024, true);
1269
- rd.setUint32(12, 8192, true);
1270
- rd.setUint32(16, 8192, true);
1271
- rd.setUint32(20, 0, true);
1272
- rd.setUint32(24, 1, true);
1273
- routeDesc[28] = 0;
1274
- rd.setUint32(29, 0, true);
1275
- const routeHandle = b.routeCompile(routeDesc);
1276
- if (routeHandle === 0) {
1277
- return false;
1278
- }
1279
- const routeFrame = new Uint8Array(15);
1280
- const rf = new DataView(routeFrame.buffer);
1281
- rf.setUint32(0, 0, true);
1282
- rf.setUint32(4, 3, true);
1283
- routeFrame.set(enc.encode("a=1"), 8);
1284
- rf.setUint32(11, 0, true);
1285
- const routeOut = new Uint8Array(64);
1286
- const routeW = b.routeRun(routeHandle, routeFrame, routeOut);
1287
- if (routeW <= 8) {
1288
- return false;
1289
- }
1290
- const rv = new DataView(routeOut.buffer);
1291
- const rFlags = rv.getUint32(0, true);
1292
- if ((rFlags & 1) === 0 || (rFlags & 4) === 0) {
1293
- return false;
1294
- }
1295
- b.routeDestroy(routeHandle);
1296
- if (b.conditionalIsNotModified(0, null, null) !== false) {
1297
- return false;
1298
- }
1299
- if (b.mediaTypeMatcherMatches(0, enc.encode("x")) !== false)
1300
- return false;
1301
- if (b.acceptNegotiatorNegotiate(0, enc.encode("gzip")) !== null)
1302
- return false;
1303
- if (b.acceptNegotiatorNegotiateServer(0, enc.encode("gzip")) !== null)
1304
- return false;
1305
- if (b.schemaValidatorValidate(0, enc.encode("{}")) !== false)
1306
- return false;
1307
- if (b.jwtSignerVerify(0, enc.encode("a.b.c"), 0) !== null)
1308
- return false;
1309
- let phase6Threw = false;
1310
- try {
1311
- b.jwtSignerSign(0, enc.encode("{}"), 0);
1312
- } catch {
1313
- phase6Threw = true;
1314
- }
1315
- if (!phase6Threw)
1316
- return false;
1317
- phase6Threw = false;
1318
- try {
1319
- b.templateRender(0, enc.encode("{}"));
1320
- } catch {
1321
- phase6Threw = true;
1322
- }
1323
- if (!phase6Threw)
1324
- return false;
1325
- phase6Threw = false;
1326
- try {
1327
- b.rateLimiterCheck(0, "k", 0);
1328
- } catch {
1329
- phase6Threw = true;
1330
- }
1331
- if (!phase6Threw)
1332
- return false;
1333
- const jwtClaims = enc.encode('{"sub":"user-1"}');
1334
- const jwtSecret2 = enc.encode("my-secret");
1335
- const jwtOut = new Uint8Array(512);
1336
- const jwtW = b.jwtSignBytesInto(jwtClaims, jwtSecret2, 0, 0, jwtOut);
1337
- if (jwtW === 0 || dec.decode(jwtOut.subarray(0, jwtW)).split(".").length !== 3) {
1338
- return false;
1339
- }
1340
- const jwtSecret = enc.encode("my-secret");
1341
- const jwt = b.jwtSignBytes(enc.encode('{"sub":"user-1"}'), jwtSecret, 0, 0);
1342
- const jwtStr = jwt;
1343
- const segs = jwtStr.split(".");
1344
- if (segs.length !== 3 || segs[0] === "" || segs[1] === "" || segs[2] === "") {
1345
- return false;
1346
- }
1347
- const signingInput = enc.encode(`${segs[0]}.${segs[1]}`);
1348
- const sigHex = b.hmacSha256(jwtSecret, signingInput);
1349
- const sigBytes = b.base64Decode(enc.encode(segs[2] ?? ""), true, false);
1350
- if (sigBytes === null || b.hexEncode(sigBytes) !== sigHex) {
1351
- return false;
1352
- }
1353
- const vjwtSecret = enc.encode("verify-secret");
1354
- const vjwt = b.jwtSignBytes(enc.encode('{"sub":"u-1"}'), vjwtSecret, 0, 0);
1355
- const vjwtClaims = b.jwtVerify(enc.encode(vjwt), vjwtSecret, 0);
1356
- if (vjwtClaims === null || !vjwtClaims.includes('"sub":"u-1"')) {
1357
- return false;
1358
- }
1359
- if (b.jwtVerify(enc.encode("tampered.token.value"), vjwtSecret, 0) !== null) {
1360
- return false;
1361
- }
1362
- const edKp = b.ed25519GenerateKeypair();
1363
- const edMsg = enc.encode("ed25519 bind-time self-test");
1364
- const edSig = b.ed25519Sign(edMsg, edKp.privateKey);
1365
- if (edSig.byteLength !== 64 || !b.ed25519Verify(edMsg, edSig, edKp.publicKey)) {
1366
- return false;
1367
- }
1368
- const edTampered = edSig.slice();
1369
- edTampered[0] = (edTampered[0] ?? 0) ^ 255;
1370
- if (b.ed25519Verify(edMsg, edTampered, edKp.publicKey)) {
1371
- return false;
1372
- }
1373
- const edToken = b.jwtEdDSASign(enc.encode('{"sub":"ffi-self-test"}'), edKp.privateKey, 0, 0);
1374
- if (edToken === null || edToken.split(".").length !== 3) {
1375
- return false;
1376
- }
1377
- const edClaims = b.jwtEdDSAVerify(enc.encode(edToken), edKp.publicKey, 0);
1378
- if (edClaims === null || !edClaims.includes('"sub":"ffi-self-test"')) {
1379
- return false;
1380
- }
1381
- if (b.jwtEdDSAVerify(enc.encode("tampered.token.value"), edKp.publicKey, 0) !== null) {
1382
- return false;
1383
- }
1384
- let urlBuilderThrew = false;
1385
- try {
1386
- b.urlBuilderResolve(0, enc.encode("g"));
1387
- } catch {
1388
- urlBuilderThrew = true;
1389
- }
1390
- if (!urlBuilderThrew) {
1391
- return false;
1392
- }
1393
- if (b.queryValidate(0, "route=/a") !== false)
1394
- return false;
1395
- if (b.cookieValidate(0, "route=/a") !== false)
1396
- return false;
1397
- {
1398
- const tok = b.sessionSeal("sess-9", '{"n":1}', 1234567, "sekrit");
1399
- if (tok === null || !tok.includes(".") || !tok.startsWith('{"id":"sess-9"'))
1400
- return false;
1401
- const out = new Uint8Array(256);
1402
- const w = b.sessionOpen(tok, "sekrit", out);
1403
- if (w <= 13 || out[0] !== 1)
1404
- return false;
1405
- if (b.sessionOpen(tok, "wrong", out) !== 0)
1406
- return false;
1407
- }
1408
- let rlKeyThrew = false;
1409
- try {
1410
- b.rateLimiterCheckKey(0, 12345, 0);
1411
- } catch {
1412
- rlKeyThrew = true;
1413
- }
1414
- if (!rlKeyThrew) {
1415
- return false;
1416
- }
1417
- return true;
1418
- }
1419
1416
  var init_instances = __esm(() => {
1420
1417
  init_codec2();
1421
1418
  init_constants();
@@ -1528,7 +1525,6 @@ function selfTestMetrics(b) {
1528
1525
  console.error("FAIL: recordStr");
1529
1526
  return false;
1530
1527
  }
1531
- console.error("CK: counter");
1532
1528
  const gauge = b.metricsGauge(handle, "ct_selftest_depth", "q");
1533
1529
  if (gauge === METRICS_DECLARE_ERR)
1534
1530
  return false;
@@ -1536,7 +1532,6 @@ function selfTestMetrics(b) {
1536
1532
  return false;
1537
1533
  if (!b.metricsGaugeSetStr(handle, gauge, "io", 6))
1538
1534
  return false;
1539
- console.error("CK: gauge");
1540
1535
  const hist = b.metricsHistogram(handle, "ct_selftest_latency", "", "0.1,0.5");
1541
1536
  if (hist === METRICS_DECLARE_ERR)
1542
1537
  return false;
@@ -1544,10 +1539,8 @@ function selfTestMetrics(b) {
1544
1539
  return false;
1545
1540
  if (b.metricsRecord(handle, hist, EMPTY, -1))
1546
1541
  return false;
1547
- console.error("CK: hist");
1548
1542
  if (b.metricsRecord(handle, counter, encodeUtf8("/a"), 1))
1549
1543
  return false;
1550
- console.error("CK: arity");
1551
1544
  const probe = new Uint8Array(16);
1552
1545
  const needed = b.metricsRender(handle, probe);
1553
1546
  if (needed <= probe.length)
@@ -1566,7 +1559,6 @@ function selfTestMetrics(b) {
1566
1559
  `)) {
1567
1560
  return false;
1568
1561
  }
1569
- console.error("CK: render");
1570
1562
  {
1571
1563
  const probe2 = new Uint8Array(16);
1572
1564
  const need = b.metricsSnapshot(handle, probe2);
@@ -1581,7 +1573,6 @@ function selfTestMetrics(b) {
1581
1573
  if (sv.getUint32(4, true) < 3)
1582
1574
  return false;
1583
1575
  }
1584
- console.error("CK: snapshot");
1585
1576
  {
1586
1577
  const c2 = b.metricsCounter(handle, "ct_selftest_batch", "k");
1587
1578
  const buf = new Uint8Array(64);
@@ -1601,7 +1592,6 @@ function selfTestMetrics(b) {
1601
1592
  return false;
1602
1593
  }
1603
1594
  }
1604
- console.error("CK: batch");
1605
1595
  try {
1606
1596
  b.metricsCounter(0, "ct_selftest_x", "");
1607
1597
  return false;
@@ -1610,15 +1600,702 @@ function selfTestMetrics(b) {
1610
1600
  } catch (err) {
1611
1601
  console.error("METRICS SELFTEST THROW:", err);
1612
1602
  return false;
1613
- } finally {
1614
- 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;
1615
2294
  }
2295
+ return true;
1616
2296
  }
1617
- var METRICS_DECLARE_ERR = 4294967295, EMPTY;
1618
- var init_metrics = __esm(() => {
2297
+ var init_instances_selftest = __esm(() => {
1619
2298
  init_codec2();
1620
- init_util();
1621
- EMPTY = new Uint8Array(0);
1622
2299
  });
1623
2300
 
1624
2301
  // src/rust-ffi/scalar/json-packed.ts
@@ -1730,181 +2407,7 @@ var init_json_packed = __esm(() => {
1730
2407
  init_runtime();
1731
2408
  });
1732
2409
 
1733
- // src/native/ffi/build/parse.ts
1734
- function sseMemoEncode(slot, s) {
1735
- if (s === slot.str)
1736
- return slot.bytes;
1737
- const bytes = encodeUtf8(s);
1738
- slot.str = s;
1739
- slot.bytes = bytes;
1740
- return bytes;
1741
- }
1742
- function buildParse(sym, ctx) {
1743
- const { lenOrView, scratchFor, dateScratch, dateScratchView } = ctx;
1744
- const httpParsePacked = sym.castrum_http_parse_request_packed;
1745
- const queryParsePacked = sym.castrum_query_parse_packed;
1746
- const cookieParsePacked = sym.castrum_cookie_parse_packed;
1747
- const httpDateIntoRaw = sym.castrum_http_date_into;
1748
- const sseEncodeIntoRaw = sym.castrum_sse_encode_into;
1749
- const jsonParsePackedSym = sym.castrum_json_parse_packed;
1750
- const wsFrameDecodePacked = sym.castrum_ws_frame_decode_packed;
1751
- const multipartParsePacked = sym.castrum_multipart_parse_packed;
1752
- const formParsePacked = sym.castrum_form_parse_packed;
1753
- const wsFrameEncode = sym.castrum_ws_frame_encode;
1754
- const parseMediaTypeSym = sym.castrum_parse_media_type;
1755
- const parseHttpDateSym = sym.castrum_parse_http_date;
1756
- const parseAcceptEncodingSym = sym.castrum_parse_accept_encoding;
1757
- const urlEncodeQuerySym = sym.castrum_url_encode_query;
1758
- const urlResolveSym = sym.castrum_url_resolve;
1759
- const mimeFromExtensionSym = sym.castrum_mime_from_extension;
1760
- return {
1761
- httpParseRequestPackedInto(input, output) {
1762
- const w = Number(httpParsePacked(input, lenOrView(input), output, lenOrView(output)));
1763
- if (w === 0 && input.length !== 0) {
1764
- throw new Error("http parse: output buffer too small or malformed request");
1765
- }
1766
- return w;
1767
- },
1768
- queryParsePackedInto(input, output) {
1769
- const w = Number(queryParsePacked(input, lenOrView(input), output, lenOrView(output)));
1770
- if (w === 0 || w > output.length) {
1771
- throw new Error("query parse: output buffer too small");
1772
- }
1773
- return w;
1774
- },
1775
- cookieParsePackedInto(input, output) {
1776
- const w = Number(cookieParsePacked(input, lenOrView(input), output, lenOrView(output)));
1777
- if (w === 0 || w > output.length) {
1778
- throw new Error("cookie parse: output buffer too small");
1779
- }
1780
- return w;
1781
- },
1782
- wsFrameEncode(opcode, payload, mask, fin) {
1783
- const out = allocOut(payload.length + 14);
1784
- const w = Number(wsFrameEncode(opcode, payload, lenOrView(payload), flag(mask), flag(fin), out, lenOrView(out)));
1785
- if (w === 0) {
1786
- throw new Error("ws frame encode: output buffer too small");
1787
- }
1788
- return out.subarray(0, w);
1789
- },
1790
- wsFrameEncodeInto(opcode, payload, mask, fin, output) {
1791
- const need = payload.length + 14;
1792
- if (output.length < need) {
1793
- throw new Error("ws frame encode: output buffer too small");
1794
- }
1795
- const w = Number(wsFrameEncode(opcode, payload, lenOrView(payload), flag(mask), flag(fin), output, lenOrView(output)));
1796
- if (w === 0) {
1797
- throw new Error("ws frame encode: output buffer too small");
1798
- }
1799
- return w;
1800
- },
1801
- wsFrameDecodePacked(data) {
1802
- const out = new Uint8Array(data.length + 6);
1803
- const w = Number(wsFrameDecodePacked(data, lenOrView(data), out, lenOrView(out)));
1804
- return w === 0 ? null : out.subarray(0, w);
1805
- },
1806
- wsFrameDecodePackedInto(data, output) {
1807
- const w = Number(wsFrameDecodePacked(data, lenOrView(data), output, lenOrView(output)));
1808
- return w === 0 ? null : w;
1809
- },
1810
- multipartParsePacked(body, boundary) {
1811
- 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");
1812
- },
1813
- multipartParsePackedInto(body, boundary, output) {
1814
- const w = Number(multipartParsePacked(body, lenOrView(body), boundary, lenOrView(boundary), output, lenOrView(output)));
1815
- if (w === 0 || w > output.length) {
1816
- throw new Error("multipart parse: output buffer too small or malformed body");
1817
- }
1818
- return w;
1819
- },
1820
- formParsePackedInto(input, output) {
1821
- const w = Number(formParsePacked(input, lenOrView(input), output, lenOrView(output)));
1822
- if (w === 0 || w > output.length) {
1823
- throw new Error("form parse: output buffer too small");
1824
- }
1825
- return w;
1826
- },
1827
- jsonParsePacked(input) {
1828
- 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");
1829
- },
1830
- parseMediaType(input) {
1831
- let out = scratchFor(input.length * 2 + 128);
1832
- let w = Number(parseMediaTypeSym(input, lenOrView(input), out, lenOrView(out)));
1833
- if (w > out.length) {
1834
- out = new Uint8Array(w);
1835
- w = Number(parseMediaTypeSym(input, lenOrView(input), out, lenOrView(out)));
1836
- }
1837
- if (w === 0 || w > out.length) {
1838
- throw new Error("media type parse: invalid media type or output buffer too small");
1839
- }
1840
- return out.subarray(0, w);
1841
- },
1842
- parseHttpDate(input) {
1843
- const out = dateScratch;
1844
- const w = Number(parseHttpDateSym(input, lenOrView(input), out, lenOrView(out)));
1845
- if (w === 0) {
1846
- throw new Error("http date parse: output buffer too small");
1847
- }
1848
- if (out[0] === 0)
1849
- return null;
1850
- return dateScratchView.getBigInt64(1, true);
1851
- },
1852
- parseAcceptEncoding(input) {
1853
- let out = scratchFor(input.length * 2 + 128);
1854
- let w = Number(parseAcceptEncodingSym(input, lenOrView(input), out, lenOrView(out)));
1855
- if (w > out.length) {
1856
- out = new Uint8Array(w);
1857
- w = Number(parseAcceptEncodingSym(input, lenOrView(input), out, lenOrView(out)));
1858
- }
1859
- if (w === 0 || w > out.length) {
1860
- throw new Error("accept encoding parse: output buffer too small");
1861
- }
1862
- return out.subarray(0, w);
1863
- },
1864
- urlEncodeQuery(input) {
1865
- return urlEncodeQuerySym(input, lenOrView(input));
1866
- },
1867
- urlResolve(base, reference) {
1868
- return urlResolveSym(base, lenOrView(base), reference, lenOrView(reference));
1869
- },
1870
- mimeFromExtension(ext) {
1871
- return mimeFromExtensionSym(ext);
1872
- },
1873
- httpDateInto(secs, output) {
1874
- const w = Number(httpDateIntoRaw(secs, output, lenOrView(output)));
1875
- if (w === 0) {
1876
- throw new Error("http date: output buffer too small or year out of range");
1877
- }
1878
- return w;
1879
- },
1880
- httpDate(secs) {
1881
- const out = scratchFor(32);
1882
- const w = Number(httpDateIntoRaw(secs ?? 0, out, lenOrView(out)));
1883
- if (w !== 0)
1884
- return decodeUtf8(out.subarray(0, w));
1885
- return new Date((secs ?? 0) * 1000).toUTCString();
1886
- },
1887
- sseEncodeEvent(event, data, id, retry) {
1888
- const ev = event === null ? EMPTY_VIEW : sseMemoEncode(sseMemoEvent, event);
1889
- const idv = id === null ? EMPTY_VIEW : sseMemoEncode(sseMemoId, id);
1890
- const flags = (event === null ? 0 : 1) | (id === null ? 0 : 2) | (retry === null ? 0 : 4);
1891
- 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");
1892
- },
1893
- sseEncodeEventInto(event, data, id, retry, output) {
1894
- const ev = event === null ? EMPTY_VIEW : sseMemoEncode(sseMemoEvent, event);
1895
- const idv = id === null ? EMPTY_VIEW : sseMemoEncode(sseMemoId, id);
1896
- const flags = (event === null ? 0 : 1) | (id === null ? 0 : 2) | (retry === null ? 0 : 4);
1897
- const w = Number(sseEncodeIntoRaw(ev, lenOrView(ev), data, lenOrView(data), idv, lenOrView(idv), flags, retry ?? 0, output, lenOrView(output)));
1898
- if (w === 0) {
1899
- throw new Error("sse encode: invalid UTF-8 in event/id");
1900
- }
1901
- if (w > output.length) {
1902
- throw new Error("sse encode: output buffer too small");
1903
- }
1904
- return w;
1905
- }
1906
- };
1907
- }
2410
+ // src/native/ffi/build/parse-selftest.ts
1908
2411
  function selfTestParse(b) {
1909
2412
  const enc = { encode: encodeUtf8 };
1910
2413
  const dec = { decode: decodeUtf8 };
@@ -2070,118 +2573,23 @@ data: hello
2070
2573
  }
2071
2574
  if (b.mimeFromExtension(".js") !== "text/javascript") {
2072
2575
  return false;
2073
- }
2074
- if (b.mimeFromExtension("nope") !== "application/octet-stream") {
2075
- return false;
2076
- }
2077
- if (b.httpDate(784111777) !== "Sun, 06 Nov 1994 08:49:37 GMT") {
2078
- return false;
2079
- }
2080
- const frameInto = new Uint8Array(64);
2081
- if (b.wsFrameEncodeInto(1, SELFTEST_HEX, false, true, frameInto) === 0 || frameInto[0] !== 129) {
2082
- return false;
2083
- }
2084
- return true;
2085
- }
2086
- var sseMemoEvent, sseMemoId;
2087
- var init_parse = __esm(() => {
2088
- init_json_packed();
2089
- init_codec2();
2090
- init_constants();
2091
- init_util();
2092
- sseMemoEvent = { str: null, bytes: EMPTY_VIEW };
2093
- sseMemoId = { str: null, bytes: EMPTY_VIEW };
2094
- });
2095
-
2096
- // src/native/ffi/build/task.ts
2097
- function buildTask(sym, ctx) {
2098
- const { lenOrView } = ctx;
2099
- const init = sym.castrum_task_init;
2100
- const submit = sym.castrum_task_submit;
2101
- const submitOut = sym.castrum_task_submit_out;
2102
- const submitSlice = sym.castrum_task_submit_slice;
2103
- const drain = sym.castrum_task_drain;
2104
- const pending = sym.castrum_task_pending;
2105
- const cancel = sym.castrum_task_cancel;
2106
- const setDoorbell = sym.castrum_task_set_doorbell;
2107
- const shutdown = sym.castrum_task_shutdown;
2108
- const threads = sym.castrum_task_threads;
2109
- return {
2110
- taskInit: (n) => Number(init(n)),
2111
- taskSubmit: (op, args, taskId) => Number(submit(op, args, lenOrView(args), taskId)),
2112
- taskSubmitOut: (op, args, taskId, output) => Number(submitOut(op, args, lenOrView(args), taskId, output, lenOrView(output))),
2113
- taskSubmitSlice: (op, hdr, data, taskId) => Number(submitSlice(op, hdr, lenOrView(hdr), data, lenOrView(data), taskId)),
2114
- taskDrain: (output) => Number(drain(output, lenOrView(output))),
2115
- taskPending: () => Number(pending()),
2116
- taskCancel: (taskId) => Number(cancel(taskId)),
2117
- taskSetDoorbell: (ptr) => Number(setDoorbell(ptr)),
2118
- taskShutdown: () => Number(shutdown()),
2119
- taskThreads: () => Number(threads())
2120
- };
2121
- }
2122
- function selfTestTask(b) {
2123
- try {
2124
- if (typeof b.taskPending() !== "number")
2125
- return false;
2126
- if (typeof b.taskThreads() !== "number" || b.taskThreads() < 0)
2127
- return false;
2128
- if (b.taskSetDoorbell(0) !== 1)
2129
- return false;
2130
- if (typeof b.taskDrain(new Uint8Array(4)) !== "number")
2131
- return false;
2132
- if (typeof b.taskSubmitOut !== "function")
2133
- return false;
2134
- if (typeof b.taskSubmitSlice !== "function")
2135
- return false;
2136
- return true;
2137
- } catch {
2576
+ }
2577
+ if (b.mimeFromExtension("nope") !== "application/octet-stream") {
2138
2578
  return false;
2139
2579
  }
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;
2140
2588
  }
2141
-
2142
- // src/native/ffi/build.ts
2143
- function build(sym, useBufferLength) {
2144
- const oneArg = (raw, v) => useBufferLength ? raw(v, v) : raw(v, v.length);
2145
- const lenOrView = (v) => useBufferLength ? v : v.length;
2146
- let encodeScratch = new Uint8Array(0);
2147
- const scratchFor = (size) => {
2148
- if (encodeScratch.byteLength < size)
2149
- encodeScratch = new Uint8Array(size);
2150
- return encodeScratch;
2151
- };
2152
- const jsonSumOut = new Uint8Array(9);
2153
- const jsonSumView = new DataView(jsonSumOut.buffer);
2154
- const dateScratch = new Uint8Array(9);
2155
- const dateScratchView = new DataView(dateScratch.buffer);
2156
- const rateScratch = new Uint8Array(13);
2157
- const rateScratchView = new DataView(rateScratch.buffer);
2158
- const ctx = {
2159
- useBufferLength,
2160
- lenOrView,
2161
- oneArg,
2162
- scratchFor,
2163
- jsonSumOut,
2164
- jsonSumView,
2165
- dateScratch,
2166
- dateScratchView,
2167
- rateScratch,
2168
- rateScratchView
2169
- };
2170
- return {
2171
- ...buildCodecs(sym, ctx),
2172
- ...buildCompress(sym, ctx),
2173
- ...buildParse(sym, ctx),
2174
- ...buildInstances(sym, ctx),
2175
- ...buildMetrics(sym, ctx),
2176
- ...buildTask(sym, ctx)
2177
- };
2178
- }
2179
- var init_build = __esm(() => {
2180
- init_codecs();
2181
- init_compress();
2182
- init_instances();
2183
- init_metrics();
2184
- init_parse();
2589
+ var init_parse_selftest = __esm(() => {
2590
+ init_json_packed();
2591
+ init_codec2();
2592
+ init_constants();
2185
2593
  });
2186
2594
 
2187
2595
  // src/native/ffi/selftest.ts
@@ -2189,11 +2597,11 @@ function selfTest(b) {
2189
2597
  return selfTestCodecs(b) && selfTestCompress(b) && selfTestParse(b) && selfTestInstances(b) && selfTestMetrics(b) && selfTestTask(b);
2190
2598
  }
2191
2599
  var init_selftest = __esm(() => {
2192
- init_codecs();
2600
+ init_codecs_selftest();
2193
2601
  init_compress();
2194
- init_instances();
2602
+ init_instances_selftest();
2195
2603
  init_metrics();
2196
- init_parse();
2604
+ init_parse_selftest();
2197
2605
  });
2198
2606
 
2199
2607
  // src/native/loader.ts
@@ -2353,6 +2761,12 @@ function getBunFFI() {
2353
2761
  cached = bind();
2354
2762
  return cached;
2355
2763
  }
2764
+ function ffiAddonPath() {
2765
+ if (cached === undefined) {
2766
+ getBunFFI();
2767
+ }
2768
+ return boundAddonPath;
2769
+ }
2356
2770
  function probeBufferLength(dlopen, path) {
2357
2771
  try {
2358
2772
  const { symbols, close } = dlopen(path, {
@@ -2386,350 +2800,7 @@ function bind(forcedPath, tried = []) {
2386
2800
  const { dlopen } = __require("bun:ffi");
2387
2801
  const useBufferLength = probeBufferLength(dlopen, path);
2388
2802
  bufferAbiMode = useBufferLength ? "buffer-pair" : "ptr-len";
2389
- const inputAbi = useBufferLength ? ["buffer", "buffer_length"] : ["ptr", "usize"];
2390
- const abi = (shape) => {
2391
- if (!useBufferLength)
2392
- return shape;
2393
- const out = [];
2394
- for (let i = 0;i < shape.length; i++) {
2395
- const t = shape[i];
2396
- if (t === "ptr") {
2397
- out.push("buffer");
2398
- out.push("buffer_length");
2399
- i++;
2400
- } else {
2401
- out.push(t);
2402
- }
2403
- }
2404
- return out;
2405
- };
2406
- const U64_FAST = "u64_fast";
2407
- const { symbols } = dlopen(path, {
2408
- castrum_crc32: { args: inputAbi, returns: "u32" },
2409
- castrum_fnv1a64: { args: inputAbi, returns: "u64" },
2410
- castrum_xxh3: { args: inputAbi, returns: "u64" },
2411
- castrum_json_valid: { args: inputAbi, returns: "u8" },
2412
- castrum_utf8_valid: { args: inputAbi, returns: "u8" },
2413
- castrum_hex_encode: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
2414
- castrum_hex_decode: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
2415
- castrum_url_encode: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
2416
- castrum_url_decode: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
2417
- castrum_validate_email: { args: ["cstring"], returns: "u8" },
2418
- castrum_validate_uuid: { args: ["cstring"], returns: "u8" },
2419
- castrum_validate_ipv4: { args: ["cstring"], returns: "u8" },
2420
- castrum_validate_ipv6: { args: ["cstring"], returns: "u8" },
2421
- castrum_validate_email_bytes: { args: inputAbi, returns: "u8" },
2422
- castrum_validate_uuid_bytes: { args: inputAbi, returns: "u8" },
2423
- castrum_validate_ipv4_bytes: { args: inputAbi, returns: "u8" },
2424
- castrum_validate_ipv6_bytes: { args: inputAbi, returns: "u8" },
2425
- castrum_json_sum_ids: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
2426
- castrum_hmac_sha256_verify: {
2427
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2428
- returns: "u8"
2429
- },
2430
- castrum_csrf_verify: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: "u8" },
2431
- castrum_password_verify: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: "u8" },
2432
- castrum_password_verify_bcrypt: {
2433
- args: abi(["ptr", "usize", "cstring"]),
2434
- returns: "u8"
2435
- },
2436
- castrum_ws_accept_key: { args: ["cstring"], returns: "cstring" },
2437
- castrum_ws_accept_key_into: {
2438
- args: abi(["ptr", "usize", "ptr", "usize"]),
2439
- returns: U64_FAST
2440
- },
2441
- castrum_etag: { args: abi(["ptr", "usize", "u8"]), returns: "cstring" },
2442
- castrum_etag_into: { args: abi(["ptr", "usize", "u8", "ptr", "usize"]), returns: U64_FAST },
2443
- castrum_conditional_is_not_modified: {
2444
- args: abi(["usize", "ptr", "usize", "ptr", "usize", "u8"]),
2445
- returns: "u8"
2446
- },
2447
- castrum_media_type_matcher_matches: {
2448
- args: abi(["usize", "ptr", "usize"]),
2449
- returns: "u8"
2450
- },
2451
- castrum_accept_negotiator_negotiate: {
2452
- args: abi(["usize", "ptr", "usize"]),
2453
- returns: "cstring"
2454
- },
2455
- castrum_accept_negotiator_negotiate_server: {
2456
- args: ["usize", "cstring"],
2457
- returns: "cstring"
2458
- },
2459
- castrum_jwt_signer_sign: {
2460
- args: abi(["usize", "ptr", "usize", "i64", "ptr", "usize"]),
2461
- returns: U64_FAST
2462
- },
2463
- castrum_jwt_signer_verify: {
2464
- args: abi(["usize", "ptr", "usize", "i64", "ptr", "usize"]),
2465
- returns: U64_FAST
2466
- },
2467
- castrum_template_render: {
2468
- args: abi(["usize", "ptr", "usize", "ptr", "usize"]),
2469
- returns: U64_FAST
2470
- },
2471
- castrum_schema_validator_validate: {
2472
- args: abi(["usize", "ptr", "usize"]),
2473
- returns: "u8"
2474
- },
2475
- castrum_rate_limiter_check: {
2476
- args: abi(["usize", "cstring", "i64", "ptr", "usize"]),
2477
- returns: U64_FAST
2478
- },
2479
- castrum_rate_limiter_check_key: {
2480
- args: abi(["usize", "i64", "i64", "ptr", "usize"]),
2481
- returns: U64_FAST
2482
- },
2483
- castrum_random_token: { args: ["u32"], returns: "cstring" },
2484
- castrum_random_token_into: { args: ["u32", ...abi(["ptr", "usize"])], returns: U64_FAST },
2485
- castrum_base64_encode: {
2486
- args: abi(["ptr", "usize", "ptr", "usize", "u8", "u8"]),
2487
- returns: U64_FAST
2488
- },
2489
- castrum_base64_decode: {
2490
- args: abi(["ptr", "usize", "ptr", "usize", "u8", "u8"]),
2491
- returns: U64_FAST
2492
- },
2493
- castrum_hmac_sha256: {
2494
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2495
- returns: U64_FAST
2496
- },
2497
- castrum_sign_cookie: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: "cstring" },
2498
- castrum_sign_cookie_into: {
2499
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2500
- returns: U64_FAST
2501
- },
2502
- castrum_verify_cookie: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: "cstring" },
2503
- castrum_verify_cookie_into: {
2504
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2505
- returns: U64_FAST
2506
- },
2507
- castrum_csrf_token: { args: abi(["ptr", "usize"]), returns: "cstring" },
2508
- castrum_csrf_token_into: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
2509
- castrum_password_hash: {
2510
- args: abi(["ptr", "usize", "ptr", "usize", "u32", "u32", "u32", "u32", "ptr", "usize"]),
2511
- returns: U64_FAST
2512
- },
2513
- castrum_password_hash_bcrypt: {
2514
- args: abi(["ptr", "usize", "u32", "ptr", "usize"]),
2515
- returns: U64_FAST
2516
- },
2517
- castrum_pbkdf2_sha256: {
2518
- args: abi(["ptr", "usize", "ptr", "usize", "u32", "u32", "ptr", "usize"]),
2519
- returns: U64_FAST
2520
- },
2521
- castrum_aead_encrypt: {
2522
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize", "u8", "ptr", "usize"]),
2523
- returns: U64_FAST
2524
- },
2525
- castrum_aead_decrypt: {
2526
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize", "u8", "ptr", "usize"]),
2527
- returns: U64_FAST
2528
- },
2529
- castrum_ws_frame_encode: {
2530
- args: abi(["u8", "ptr", "usize", "u8", "u8", "ptr", "usize"]),
2531
- returns: U64_FAST
2532
- },
2533
- castrum_json_patch: {
2534
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2535
- returns: U64_FAST
2536
- },
2537
- castrum_gzip_compress: {
2538
- args: abi(["ptr", "usize", "u32", "ptr", "usize"]),
2539
- returns: U64_FAST
2540
- },
2541
- castrum_gzip_decompress: {
2542
- args: abi(["ptr", "usize", "usize", "ptr", "usize"]),
2543
- returns: U64_FAST
2544
- },
2545
- castrum_brotli_compress: {
2546
- args: abi(["ptr", "usize", "u32", "ptr", "usize"]),
2547
- returns: U64_FAST
2548
- },
2549
- castrum_brotli_decompress: {
2550
- args: abi(["ptr", "usize", "usize", "ptr", "usize"]),
2551
- returns: U64_FAST
2552
- },
2553
- castrum_gzip_isize: { args: abi(["ptr", "usize"]), returns: "u32" },
2554
- castrum_http_parse_request_packed: {
2555
- args: abi(["ptr", "usize", "ptr", "usize"]),
2556
- returns: U64_FAST
2557
- },
2558
- castrum_query_parse_packed: {
2559
- args: abi(["ptr", "usize", "ptr", "usize"]),
2560
- returns: U64_FAST
2561
- },
2562
- castrum_cookie_parse_packed: {
2563
- args: abi(["ptr", "usize", "ptr", "usize"]),
2564
- returns: U64_FAST
2565
- },
2566
- castrum_http_date_into: { args: ["f64", ...abi(["ptr", "usize"])], returns: U64_FAST },
2567
- castrum_sse_encode_into: {
2568
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize", "u8", "u32", "ptr", "usize"]),
2569
- returns: U64_FAST
2570
- },
2571
- castrum_jwt_sign_bytes: {
2572
- args: abi(["ptr", "usize", "ptr", "usize", "i64", "i64"]),
2573
- returns: "cstring"
2574
- },
2575
- castrum_jwt_sign_bytes_into: {
2576
- args: abi(["ptr", "usize", "ptr", "usize", "i64", "i64", "ptr", "usize"]),
2577
- returns: U64_FAST
2578
- },
2579
- castrum_ed25519_generate_keypair: {
2580
- args: abi(["ptr", "usize"]),
2581
- returns: U64_FAST
2582
- },
2583
- castrum_ed25519_sign: {
2584
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2585
- returns: U64_FAST
2586
- },
2587
- castrum_ed25519_verify: {
2588
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2589
- returns: "u32"
2590
- },
2591
- castrum_jwt_eddsa_sign: {
2592
- args: abi(["ptr", "usize", "ptr", "usize", "i64", "i64"]),
2593
- returns: "cstring"
2594
- },
2595
- castrum_jwt_eddsa_verify: {
2596
- args: abi(["ptr", "usize", "ptr", "usize", "i64"]),
2597
- returns: "cstring"
2598
- },
2599
- castrum_ws_frame_decode_packed: {
2600
- args: abi(["ptr", "usize", "ptr", "usize"]),
2601
- returns: U64_FAST
2602
- },
2603
- castrum_multipart_parse_packed: {
2604
- args: abi(["ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2605
- returns: U64_FAST
2606
- },
2607
- castrum_form_parse_packed: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
2608
- castrum_json_parse_packed: {
2609
- args: abi(["ptr", "usize", "ptr", "usize"]),
2610
- returns: U64_FAST
2611
- },
2612
- castrum_parse_media_type: {
2613
- args: abi(["ptr", "usize", "ptr", "usize"]),
2614
- returns: U64_FAST
2615
- },
2616
- castrum_parse_http_date: {
2617
- args: abi(["ptr", "usize", "ptr", "usize"]),
2618
- returns: U64_FAST
2619
- },
2620
- castrum_parse_accept_encoding: {
2621
- args: abi(["ptr", "usize", "ptr", "usize"]),
2622
- returns: U64_FAST
2623
- },
2624
- castrum_url_encode_query: { args: abi(["ptr", "usize"]), returns: "cstring" },
2625
- castrum_url_resolve: {
2626
- args: abi(["ptr", "usize", "ptr", "usize"]),
2627
- returns: "cstring"
2628
- },
2629
- castrum_url_builder_resolve: {
2630
- args: abi(["usize", "ptr", "usize", "ptr", "usize"]),
2631
- returns: U64_FAST
2632
- },
2633
- castrum_mime_from_extension: { args: ["cstring"], returns: "cstring" },
2634
- castrum_jwt_verify: {
2635
- args: abi(["ptr", "usize", "ptr", "usize", "i64"]),
2636
- returns: "cstring"
2637
- },
2638
- castrum_ingress_handle_packed: {
2639
- args: abi(["usize", "ptr", "usize", "ptr", "usize", "ptr", "usize"]),
2640
- returns: U64_FAST
2641
- },
2642
- castrum_ingress_handle_components: {
2643
- args: abi([
2644
- "usize",
2645
- "u8",
2646
- "cstring",
2647
- "cstring",
2648
- "ptr",
2649
- "usize",
2650
- "ptr",
2651
- "usize",
2652
- "ptr",
2653
- "usize",
2654
- "ptr",
2655
- "usize"
2656
- ]),
2657
- returns: U64_FAST
2658
- },
2659
- castrum_ingress_layout: { args: abi(["ptr", "usize"]), returns: U64_FAST },
2660
- castrum_route_compile: { args: abi(["ptr", "usize"]), returns: U64_FAST },
2661
- castrum_route_run: {
2662
- args: abi(["usize", "ptr", "usize", "ptr", "usize"]),
2663
- returns: U64_FAST
2664
- },
2665
- castrum_route_destroy: { args: ["usize"], returns: "void" },
2666
- castrum_hex_validate_batch: {
2667
- args: abi(["ptr", "usize", "u32", "ptr", "usize"]),
2668
- returns: U64_FAST
2669
- },
2670
- castrum_regex_escape: { args: abi(["ptr", "usize", "ptr", "usize"]), returns: U64_FAST },
2671
- castrum_metrics_create: { args: [], returns: U64_FAST },
2672
- castrum_metrics_counter: { args: ["usize", "cstring", "cstring"], returns: "u32" },
2673
- castrum_metrics_gauge: { args: ["usize", "cstring", "cstring"], returns: "u32" },
2674
- castrum_metrics_histogram: {
2675
- args: ["usize", "cstring", "cstring", "cstring"],
2676
- returns: "u32"
2677
- },
2678
- castrum_metrics_record: {
2679
- args: abi(["usize", "u32", "ptr", "usize", "f64"]),
2680
- returns: "u8"
2681
- },
2682
- castrum_metrics_gauge_set: {
2683
- args: abi(["usize", "u32", "ptr", "usize", "f64"]),
2684
- returns: "u8"
2685
- },
2686
- castrum_metrics_record_str: {
2687
- args: ["usize", "u32", "cstring", "f64"],
2688
- returns: "u8"
2689
- },
2690
- castrum_metrics_gauge_set_str: {
2691
- args: ["usize", "u32", "cstring", "f64"],
2692
- returns: "u8"
2693
- },
2694
- castrum_regex_escape_str: { args: ["cstring"], returns: "cstring" },
2695
- castrum_hex_validate_batch_str: {
2696
- args: abi(["cstring", "u32", "ptr", "usize"]),
2697
- returns: U64_FAST
2698
- },
2699
- castrum_metrics_render: { args: abi(["usize", "ptr", "usize"]), returns: U64_FAST },
2700
- castrum_metrics_snapshot: { args: abi(["usize", "ptr", "usize"]), returns: U64_FAST },
2701
- castrum_metrics_record_batch: {
2702
- args: abi(["usize", "ptr", "usize"]),
2703
- returns: "u8"
2704
- },
2705
- castrum_query_validate: { args: ["usize", "cstring"], returns: "u8" },
2706
- castrum_cookie_validate: { args: ["usize", "cstring"], returns: "u8" },
2707
- castrum_session_seal: {
2708
- args: ["cstring", "cstring", "i64", "cstring"],
2709
- returns: "cstring"
2710
- },
2711
- castrum_session_open: {
2712
- args: abi(["cstring", "cstring", "ptr", "usize"]),
2713
- returns: U64_FAST
2714
- },
2715
- castrum_task_init: { args: ["u32"], returns: "u32" },
2716
- castrum_task_submit: { args: abi(["u32", "ptr", "usize", "usize"]), returns: "u32" },
2717
- castrum_task_submit_slice: {
2718
- args: abi(["u32", "ptr", "usize", "ptr", "usize", "usize"]),
2719
- returns: "u32"
2720
- },
2721
- castrum_task_submit_out: {
2722
- args: abi(["u32", "ptr", "usize", "usize", "ptr", "usize"]),
2723
- returns: "u32"
2724
- },
2725
- castrum_task_drain: { args: abi(["ptr", "usize"]), returns: U64_FAST },
2726
- castrum_task_pending: { args: [], returns: "u32" },
2727
- castrum_task_cancel: { args: ["usize"], returns: "u32" },
2728
- castrum_task_set_doorbell: { args: ["ptr"], returns: "u32" },
2729
- castrum_task_shutdown: { args: [], returns: "u32" },
2730
- castrum_task_threads: { args: [], returns: "u32" },
2731
- castrum_metrics_destroy: { args: ["usize"], returns: "void" }
2732
- });
2803
+ const { symbols } = dlopen(path, buildFFISymbolMap(useBufferLength));
2733
2804
  const bindings = build(symbols, useBufferLength);
2734
2805
  if (!selfTest(bindings)) {
2735
2806
  throw new Error(`bun:ffi bind-time self-test failed on ${path}`);
@@ -3404,24 +3475,53 @@ var init_wire = __esm(() => {
3404
3475
  });
3405
3476
 
3406
3477
  // src/shared/packed/parsers.ts
3407
- function parseQueryString(query) {
3408
- 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);
3409
3502
  return pairsToObject(readPairsPacked(packed));
3410
3503
  }
3504
+ function parseQueryString(query) {
3505
+ return parseVia("query", encoder.encode(query));
3506
+ }
3411
3507
  function parseCookieHeader(header) {
3412
- const packed = addon2.cookieParsePacked(encoder.encode(header));
3413
- return pairsToObject(readPairsPacked(packed));
3508
+ return parseVia("cookie", encoder.encode(header));
3414
3509
  }
3415
3510
  function parseFormBody(body) {
3416
- const packed = addon2.formParsePacked(body);
3417
- return pairsToObject(readPairsPacked(packed));
3511
+ return parseVia("form", body);
3418
3512
  }
3419
- var addon2;
3513
+ var addon2, ffiCaches;
3420
3514
  var init_parsers = __esm(() => {
3515
+ init_ffi();
3421
3516
  init_native();
3422
3517
  init_bytes();
3423
3518
  init_wire();
3424
3519
  addon2 = lazyAddon(getAddon);
3520
+ ffiCaches = {
3521
+ query: null,
3522
+ cookie: null,
3523
+ form: null
3524
+ };
3425
3525
  });
3426
3526
 
3427
3527
  // src/shared/packed/schema.ts
@@ -3486,6 +3586,11 @@ function asNumber(value) {
3486
3586
  }
3487
3587
  return 0;
3488
3588
  }
3589
+ function assertNonEmptySecret(secret, op) {
3590
+ if (secret.byteLength === 0) {
3591
+ throw new Error(`${op}: secret must not be empty`);
3592
+ }
3593
+ }
3489
3594
  function normalizeExt(ext) {
3490
3595
  let s;
3491
3596
  try {
@@ -3653,17 +3758,29 @@ function buildBatch(ctx) {
3653
3758
  gzipDecompress: (items, maxDecompressed) => via(ctx, "gzipDecompressBatchPacked", unpackByteResults, [maxDecompressed ?? null])(items),
3654
3759
  brotliCompress: (items, quality) => via(ctx, "brotliCompressBatchPacked", unpackByteResults, [quality ?? null])(items),
3655
3760
  brotliDecompress: (items, maxDecompressed) => via(ctx, "brotliDecompressBatchPacked", unpackByteResults, [maxDecompressed ?? null])(items),
3656
- jwtVerify: (tokens, secret, nowSeconds) => via(ctx, "jwtVerifyBatchPacked", unpackBitset, [secret, nowSeconds])(tokens),
3657
- hmacSha256: (items, key) => via(ctx, "hmacSha256BatchPacked", unpackByteResults, [key])(items),
3658
- 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
+ },
3659
3773
  passwordVerify: (items, phcs) => via2(ctx, "passwordVerifyBatchPacked", unpackBitset)(items, phcs),
3660
3774
  urlResolve: (bases, references) => via2(ctx, "urlResolveBatchPacked", unpackByteResults)(bases, references),
3661
3775
  templateRender: (source, contexts) => via(ctx, "templateRenderBatchPacked", unpackByteResults, [source])(contexts),
3662
- jwtSign: (items, secret, ttlSeconds, nowSeconds) => via(ctx, "jwtSignBatchPacked", unpackByteResults, [
3663
- secret,
3664
- ttlSeconds ?? null,
3665
- nowSeconds ?? Math.floor(Date.now() / 1000)
3666
- ])(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
+ },
3667
3784
  sseEncode: (items, event, id, retry) => via(ctx, "sseEncodeBatchPacked", unpackByteResults, [
3668
3785
  event ?? null,
3669
3786
  id ?? null,
@@ -3677,6 +3794,7 @@ function buildBatch(ctx) {
3677
3794
  var init_build2 = __esm(() => {
3678
3795
  init_packed();
3679
3796
  init_context();
3797
+ init_options();
3680
3798
  });
3681
3799
 
3682
3800
  // src/rust-ffi/batch/index.ts
@@ -3938,18 +4056,22 @@ function buildCrypto(ctx) {
3938
4056
  return writeInto("verify cookie", output, value);
3939
4057
  },
3940
4058
  csrfToken(secret) {
4059
+ assertNonEmptySecret(secret, "csrfToken");
3941
4060
  return resolveNative(ctx, "csrfToken")(secret);
3942
4061
  },
3943
4062
  csrfTokenInto(secret, output) {
4063
+ assertNonEmptySecret(secret, "csrfTokenInto");
3944
4064
  const f = ffi();
3945
4065
  if (f)
3946
4066
  return f.csrfTokenInto(secret, output);
3947
4067
  return writeInto("csrf token", output, addon.csrfToken(secret));
3948
4068
  },
3949
4069
  csrfVerify(token, secret) {
4070
+ assertNonEmptySecret(secret, "csrfVerify");
3950
4071
  return resolveNative(ctx, "csrfVerify")(token, secret);
3951
4072
  },
3952
4073
  jwtSign(claims, secret, ttlSeconds, nowSeconds) {
4074
+ assertNonEmptySecret(secret, "jwtSign");
3953
4075
  const f = ffi();
3954
4076
  if (f) {
3955
4077
  const token = f.jwtSignBytes(encoder.encode(JSON.stringify(claims)), secret, ttlSeconds ?? 0, nowSeconds ?? Math.floor(Date.now() / 1000));
@@ -3958,6 +4080,7 @@ function buildCrypto(ctx) {
3958
4080
  return addon.jwtSign(claims, secret, ttlSeconds ?? null, nowSeconds ?? Math.floor(Date.now() / 1000));
3959
4081
  },
3960
4082
  jwtSignBytes(claimsJson, secret, ttlSeconds, nowSeconds) {
4083
+ assertNonEmptySecret(secret, "jwtSignBytes");
3961
4084
  const f = ffi();
3962
4085
  if (f) {
3963
4086
  return f.jwtSignBytes(claimsJson, secret, ttlSeconds ?? 0, nowSeconds ?? Math.floor(Date.now() / 1000));
@@ -3965,6 +4088,7 @@ function buildCrypto(ctx) {
3965
4088
  return addon.jwtSignBytes(claimsJson, secret, ttlSeconds ?? null, nowSeconds ?? Math.floor(Date.now() / 1000));
3966
4089
  },
3967
4090
  jwtSignBytesInto(claimsJson, secret, output, ttlSeconds, nowSeconds) {
4091
+ assertNonEmptySecret(secret, "jwtSignBytesInto");
3968
4092
  const f = ffi();
3969
4093
  if (f) {
3970
4094
  return f.jwtSignBytesInto(claimsJson, secret, ttlSeconds ?? 0, nowSeconds ?? Math.floor(Date.now() / 1000), output);
@@ -3972,6 +4096,7 @@ function buildCrypto(ctx) {
3972
4096
  return writeInto("jwt sign", output, addon.jwtSignBytes(claimsJson, secret, ttlSeconds ?? null, nowSeconds ?? Math.floor(Date.now() / 1000)));
3973
4097
  },
3974
4098
  jwtVerify(token, secret, nowSeconds) {
4099
+ assertNonEmptySecret(secret, "jwtVerify");
3975
4100
  const f = ffi();
3976
4101
  if (f) {
3977
4102
  const claims = f.jwtVerify(token, secret, nowSeconds ?? Math.floor(Date.now() / 1000));
@@ -4032,9 +4157,17 @@ function buildCrypto(ctx) {
4032
4157
  var init_crypto = __esm(() => {
4033
4158
  init_bytes();
4034
4159
  init_context();
4160
+ init_options();
4035
4161
  });
4036
4162
 
4037
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
+ }
4038
4171
  function ffiHmacSigner(key, ffi) {
4039
4172
  return {
4040
4173
  sign(data) {
@@ -4215,47 +4348,55 @@ function ffiFormParser(ffi) {
4215
4348
  };
4216
4349
  }
4217
4350
  function ffiMetricsRegistry(ffi) {
4218
- 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
+ };
4219
4357
  return {
4220
4358
  counter(name, labelKeys) {
4221
- return ffi.metricsCounter(handle, name, (labelKeys ?? []).join("\x1F"));
4359
+ return ffi.metricsCounter(live(), name, (labelKeys ?? []).join("\x1F"));
4222
4360
  },
4223
4361
  gauge(name, labelKeys) {
4224
- return ffi.metricsGauge(handle, name, (labelKeys ?? []).join("\x1F"));
4362
+ return ffi.metricsGauge(live(), name, (labelKeys ?? []).join("\x1F"));
4225
4363
  },
4226
4364
  histogram(name, labelKeys, buckets) {
4227
- return ffi.metricsHistogram(handle, name, (labelKeys ?? []).join("\x1F"), (buckets ?? []).join(","));
4365
+ return ffi.metricsHistogram(live(), name, (labelKeys ?? []).join("\x1F"), (buckets ?? []).join(","));
4228
4366
  },
4229
4367
  record(series, values, amount) {
4230
4368
  const joined = (values ?? []).join("\x1F");
4231
- if (!ffi.metricsRecordStr(handle, series, joined, amount ?? 1)) {
4369
+ if (!ffi.metricsRecordStr(live(), series, joined, amount ?? 1)) {
4232
4370
  throw new Error("metrics record: unknown series / arity mismatch / invalid amount");
4233
4371
  }
4234
4372
  },
4235
4373
  gaugeSet(series, values, value) {
4236
4374
  const joined = (values ?? []).join("\x1F");
4237
- if (!ffi.metricsGaugeSetStr(handle, series, joined, value)) {
4375
+ if (!ffi.metricsGaugeSetStr(live(), series, joined, value)) {
4238
4376
  throw new Error("metrics gauge set: unknown series / arity mismatch");
4239
4377
  }
4240
4378
  },
4241
4379
  render() {
4242
4380
  let out = new Uint8Array(8192);
4243
- let w = ffi.metricsRender(handle, out);
4381
+ let w = ffi.metricsRender(live(), out);
4244
4382
  if (w > out.length) {
4245
4383
  out = new Uint8Array(w);
4246
- w = ffi.metricsRender(handle, out);
4384
+ w = ffi.metricsRender(live(), out);
4247
4385
  }
4248
4386
  return decodeUtf8(out.subarray(0, w));
4249
4387
  },
4250
4388
  destroy() {
4251
- ffi.metricsDestroy(handle);
4389
+ if (handle !== 0) {
4390
+ ffi.metricsDestroy(handle);
4391
+ handle = 0;
4392
+ }
4252
4393
  },
4253
4394
  snapshot() {
4254
4395
  let out = new Uint8Array(4096);
4255
- let w = ffi.metricsSnapshot(handle, out);
4396
+ let w = ffi.metricsSnapshot(live(), out);
4256
4397
  if (w > out.length) {
4257
4398
  out = new Uint8Array(w);
4258
- w = ffi.metricsSnapshot(handle, out);
4399
+ w = ffi.metricsSnapshot(live(), out);
4259
4400
  }
4260
4401
  return out.subarray(0, w);
4261
4402
  }
@@ -4269,11 +4410,12 @@ function buildFactories(ctx) {
4269
4410
  const ffi = transport.ffi;
4270
4411
  if (ffi) {
4271
4412
  const napi = new addon.SchemaValidator(schema);
4272
- return ffiSchemaValidator(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4413
+ return ffiSchemaValidator(napi, handlePtr(napi.innerPtr?.()), ffi);
4273
4414
  }
4274
4415
  return new addon.SchemaValidator(schema);
4275
4416
  },
4276
4417
  createHmacSigner(key) {
4418
+ assertNonEmptySecret(key, "createHmacSigner");
4277
4419
  const ffi = transport.ffi;
4278
4420
  if (ffi)
4279
4421
  return ffiHmacSigner(key, ffi);
@@ -4283,7 +4425,7 @@ function buildFactories(ctx) {
4283
4425
  const ffi = transport.ffi;
4284
4426
  if (ffi) {
4285
4427
  const napi = new addon.TemplateRenderer(source);
4286
- return ffiTemplateRenderer(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4428
+ return ffiTemplateRenderer(napi, handlePtr(napi.innerPtr?.()), ffi);
4287
4429
  }
4288
4430
  return new addon.TemplateRenderer(source);
4289
4431
  },
@@ -4300,7 +4442,7 @@ function buildFactories(ctx) {
4300
4442
  const ffi = transport.ffi;
4301
4443
  if (ffi) {
4302
4444
  const napi = new addon.ConditionalRequest(etagValue, lastModifiedSecs ?? undefined);
4303
- return ffiConditionalRequest(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4445
+ return ffiConditionalRequest(napi, handlePtr(napi.innerPtr?.()), ffi);
4304
4446
  }
4305
4447
  return new addon.ConditionalRequest(etagValue, lastModifiedSecs ?? undefined);
4306
4448
  },
@@ -4308,7 +4450,7 @@ function buildFactories(ctx) {
4308
4450
  const ffi = transport.ffi;
4309
4451
  if (ffi) {
4310
4452
  const napi = new addon.AcceptNegotiator(supported);
4311
- return ffiAcceptNegotiator(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4453
+ return ffiAcceptNegotiator(napi, handlePtr(napi.innerPtr?.()), ffi);
4312
4454
  }
4313
4455
  return new addon.AcceptNegotiator(supported);
4314
4456
  },
@@ -4319,12 +4461,14 @@ function buildFactories(ctx) {
4319
4461
  return new addon.Base64Codec(urlSafe ?? undefined, padding ?? undefined);
4320
4462
  },
4321
4463
  createCookieSigner(secret) {
4464
+ assertNonEmptySecret(secret, "createCookieSigner");
4322
4465
  const ffi = transport.ffi;
4323
4466
  if (ffi)
4324
4467
  return ffiCookieSigner(secret, ffi);
4325
4468
  return new addon.CookieSigner(secret);
4326
4469
  },
4327
4470
  createCsrfProtector(secret) {
4471
+ assertNonEmptySecret(secret, "createCsrfProtector");
4328
4472
  const ffi = transport.ffi;
4329
4473
  if (ffi)
4330
4474
  return ffiCsrfProtector(secret, ffi);
@@ -4334,15 +4478,16 @@ function buildFactories(ctx) {
4334
4478
  const ffi = transport.ffi;
4335
4479
  if (ffi) {
4336
4480
  const napi = new addon.UrlBuilder(base);
4337
- return ffiUrlBuilder(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4481
+ return ffiUrlBuilder(napi, handlePtr(napi.innerPtr?.()), ffi);
4338
4482
  }
4339
4483
  return new addon.UrlBuilder(base);
4340
4484
  },
4341
4485
  createJwtSigner(secret, ttlSeconds) {
4486
+ assertNonEmptySecret(secret, "createJwtSigner");
4342
4487
  const ffi = transport.ffi;
4343
4488
  if (ffi) {
4344
4489
  const napi = new addon.JwtSigner(secret, ttlSeconds ?? undefined);
4345
- return ffiJwtSigner(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4490
+ return ffiJwtSigner(napi, handlePtr(napi.innerPtr?.()), ffi);
4346
4491
  }
4347
4492
  return new addon.JwtSigner(secret, ttlSeconds ?? undefined);
4348
4493
  },
@@ -4362,7 +4507,7 @@ function buildFactories(ctx) {
4362
4507
  const ffi = transport.ffi;
4363
4508
  if (ffi) {
4364
4509
  const napi = new addon.MediaTypeMatcher(expected);
4365
- return ffiMediaTypeMatcher(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4510
+ return ffiMediaTypeMatcher(napi, handlePtr(napi.innerPtr?.()), ffi);
4366
4511
  }
4367
4512
  return new addon.MediaTypeMatcher(expected);
4368
4513
  },
@@ -4370,7 +4515,7 @@ function buildFactories(ctx) {
4370
4515
  const ffi = transport.ffi;
4371
4516
  if (ffi) {
4372
4517
  const napi = new addon.RateLimiter(limit, windowMs, maxEntries ?? undefined);
4373
- return ffiRateLimiter(napi, Number(napi.innerPtr?.() ?? 0n), ffi);
4518
+ return ffiRateLimiter(napi, handlePtr(napi.innerPtr?.()), ffi);
4374
4519
  }
4375
4520
  return new addon.RateLimiter(limit, windowMs, maxEntries ?? undefined);
4376
4521
  },
@@ -4471,37 +4616,63 @@ function ensureValidUtf8(bytes, ffi) {
4471
4616
  function u32LE(b, off) {
4472
4617
  return ((b[off] ?? 0) | (b[off + 1] ?? 0) << 8 | (b[off + 2] ?? 0) << 16 | (b[off + 3] ?? 0) << 24) >>> 0;
4473
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
+ }
4474
4626
  function unpackMediaType(packed) {
4627
+ const view = Buffer.from(packed.buffer, packed.byteOffset, packed.byteLength);
4475
4628
  let off = 0;
4476
4629
  const mtLen = u32LE(packed, off);
4477
4630
  off += 4;
4478
- const mediaType = decodeUtf8Range(packed, off, off + mtLen);
4631
+ const mediaType = decodeUtf8RangeView(view, off, off + mtLen);
4479
4632
  off += mtLen;
4480
4633
  let charset = null;
4481
4634
  const csLen = u32LE(packed, off);
4482
4635
  off += 4;
4483
4636
  if (csLen !== 4294967295) {
4484
- charset = decodeUtf8Range(packed, off, off + csLen);
4637
+ charset = decodeUtf8RangeView(view, off, off + csLen);
4485
4638
  off += csLen;
4486
4639
  }
4487
4640
  let boundary = null;
4488
4641
  const bLen = u32LE(packed, off);
4489
4642
  off += 4;
4490
4643
  if (bLen !== 4294967295) {
4491
- boundary = decodeUtf8Range(packed, off, off + bLen);
4644
+ boundary = decodeUtf8RangeView(view, off, off + bLen);
4492
4645
  off += bLen;
4493
4646
  }
4494
4647
  const count = u32LE(packed, off);
4495
4648
  off += 4;
4496
4649
  const params = {};
4650
+ let charsetTaken = charset === null;
4651
+ let boundaryTaken = boundary === null;
4497
4652
  for (let i = 0;i < count; i++) {
4498
4653
  const kLen = u32LE(packed, off);
4499
4654
  off += 4;
4500
- 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
+ }
4501
4663
  off += kLen;
4502
4664
  const vLen = u32LE(packed, off);
4503
4665
  off += 4;
4504
- 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
+ }
4505
4676
  off += vLen;
4506
4677
  params[key] = val;
4507
4678
  }
@@ -4632,7 +4803,7 @@ function buildHttp(ctx) {
4632
4803
  queryParsePacked(input) {
4633
4804
  const f = ffi();
4634
4805
  if (f) {
4635
- const out = new Uint8Array(input.length * 9 + 16);
4806
+ const out = new Uint8Array(input.length * 5 + 8);
4636
4807
  const w = f.queryParsePackedInto(input, out);
4637
4808
  return out.subarray(0, w);
4638
4809
  }
@@ -4644,7 +4815,7 @@ function buildHttp(ctx) {
4644
4815
  cookieParsePacked(input) {
4645
4816
  const f = ffi();
4646
4817
  if (f) {
4647
- const out = new Uint8Array(input.length * 9 + 16);
4818
+ const out = new Uint8Array(input.length * 5 + 8);
4648
4819
  const w = f.cookieParsePackedInto(input, out);
4649
4820
  return out.subarray(0, w);
4650
4821
  }
@@ -4656,7 +4827,7 @@ function buildHttp(ctx) {
4656
4827
  formParsePacked(input) {
4657
4828
  const f = ffi();
4658
4829
  if (f) {
4659
- const out = new Uint8Array(input.length * 9 + 16);
4830
+ const out = new Uint8Array(input.length * 5 + 8);
4660
4831
  const w = f.formParsePackedInto(input, out);
4661
4832
  return out.subarray(0, w);
4662
4833
  }
@@ -4728,13 +4899,15 @@ function buildHttp(ctx) {
4728
4899
  }
4729
4900
  };
4730
4901
  }
4731
- var WS_ACCEPT_SCRATCH, f32Bits, f32BitsU32;
4902
+ var WS_ACCEPT_SCRATCH, CHARSET_KEY_BYTES, BOUNDARY_KEY_BYTES, f32Bits, f32BitsU32;
4732
4903
  var init_http = __esm(() => {
4733
4904
  init_bytes();
4734
4905
  init_codec2();
4735
4906
  init_packed();
4736
4907
  init_context();
4737
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]);
4738
4911
  f32Bits = new Float32Array(1);
4739
4912
  f32BitsU32 = new Uint32Array(f32Bits.buffer);
4740
4913
  });
@@ -4814,6 +4987,7 @@ function buildPayload(ctx) {
4814
4987
  const { builtins, transport } = ctx.runtime;
4815
4988
  const HAS = { gzipCompress: builtins.has("gzipCompress") };
4816
4989
  const ffi = memoizeFfi(transport);
4990
+ let SCRATCH = null;
4817
4991
  return {
4818
4992
  gzipCompress(data, level) {
4819
4993
  if (HAS.gzipCompress)
@@ -4867,8 +5041,20 @@ function buildPayload(ctx) {
4867
5041
  },
4868
5042
  multipartParse(body, boundary) {
4869
5043
  const f = ffi();
4870
- if (f)
4871
- 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
+ }
4872
5058
  return addon.multipartParse(body, boundary).map((p) => ({
4873
5059
  name: p.name,
4874
5060
  filename: p.filename ?? null,
@@ -5297,10 +5483,13 @@ function createNativeRoute(plan = {}) {
5297
5483
  const parseQuery = plan.parseQuery === true;
5298
5484
  const parseCookies = plan.parseCookies === true;
5299
5485
  const bunFFI = getBunFFI();
5300
- const ffiHandle = bunFFI !== null ? compileFfi(bunFFI, descriptor) : 0;
5301
- 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;
5302
5488
  let out = new Uint8Array(256);
5303
5489
  const runFrame = (frame) => {
5490
+ if (ffiHandle === 0 && napiRoute === null) {
5491
+ throw new Error("native route: already destroyed");
5492
+ }
5304
5493
  let written;
5305
5494
  if (ffiHandle !== 0 && bunFFI !== null) {
5306
5495
  written = bunFFI.routeRun(ffiHandle, frame, out);
@@ -5333,7 +5522,10 @@ function createNativeRoute(plan = {}) {
5333
5522
  destroy: () => {
5334
5523
  if (ffiHandle !== 0 && bunFFI !== null) {
5335
5524
  bunFFI.routeDestroy(ffiHandle);
5525
+ ffiHandle = 0;
5336
5526
  }
5527
+ napiRoute?.destroy?.();
5528
+ napiRoute = null;
5337
5529
  }
5338
5530
  };
5339
5531
  }
@@ -5357,6 +5549,17 @@ function compileNapi(descriptor) {
5357
5549
  }
5358
5550
  }
5359
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
+
5360
5563
  // src/ingress/shared.ts
5361
5564
  var DEFAULT_MAX_BODY_BYTES = 1048576;
5362
5565
  var DEFAULT_FAST_OUTPUT_BUFFER_SIZE = 262144;
@@ -5399,10 +5602,9 @@ function resolveIp(req, srv, opts) {
5399
5602
  return opts.getIp ? opts.getIp(req, srv) : undefined;
5400
5603
  }
5401
5604
  function buildSuccessInit(ingress, result, ctx, status = 200) {
5402
- return {
5403
- status,
5404
- headers: ingress.responseHeaders(result.headerVariant, ctx.requestIdHeader, ctx.origin, result.rateRemaining, result.rateResetMs > 0 ? secondsFromMs(result.rateResetMs) : undefined)
5405
- };
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 };
5406
5608
  }
5407
5609
  function runBaked(ingress, opts, respond) {
5408
5610
  return (req, srv) => ingress.run(req, resolveIp(req, srv, opts), null, respond);
@@ -5781,6 +5983,18 @@ function rateLimitedBody(retryAfterMs) {
5781
5983
  }
5782
5984
 
5783
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
+ }
5784
5998
  function echoHandler(ingress, opts = {}) {
5785
5999
  const maxBodyBytes = opts.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
5786
6000
  const bodyTimeoutMs = opts.bodyTimeoutMs ?? DEFAULT_BODY_TIMEOUT_MS;
@@ -5801,32 +6015,39 @@ function echoHandler(ingress, opts = {}) {
5801
6015
  if (prep.terminal)
5802
6016
  return prep.terminal;
5803
6017
  const baseHeaders = prep.headers ?? [];
5804
- 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
+ };
5805
6026
  const contentLengthHeader = req.headers.get("content-length");
5806
6027
  const contentLength = contentLengthHeader === null ? NaN : Number(contentLengthHeader);
5807
6028
  if (Number.isFinite(contentLength)) {
5808
6029
  if (contentLength > maxBodyBytes) {
5809
6030
  return new Response(ERROR_BODIES.body_too_large, {
5810
6031
  status: 413,
5811
- headers: ingress.withContentType(baseHeaders, "application/json")
6032
+ headers: withEchoHeaders("application/json")
5812
6033
  });
5813
6034
  }
5814
6035
  if (contentLength <= 0 || req.body === null) {
5815
6036
  return new Response(null, {
5816
6037
  status: 200,
5817
- headers: ingress.withContentType(baseHeaders, requestedContentType)
6038
+ headers: withEchoHeaders(requestedContentType)
5818
6039
  });
5819
6040
  }
5820
6041
  return new Response(req.body, {
5821
6042
  status: 200,
5822
- headers: ingress.withContentType(baseHeaders, requestedContentType)
6043
+ headers: withEchoHeaders(requestedContentType)
5823
6044
  });
5824
6045
  }
5825
6046
  try {
5826
6047
  const bodyBytes = await readBodyWithLimit(req, maxBodyBytes, true, bodyTimeoutMs);
5827
6048
  return new Response(bodyBytes.byteLength > 0 ? bodyBytes : null, {
5828
6049
  status: 200,
5829
- headers: ingress.withContentType(baseHeaders, requestedContentType)
6050
+ headers: withEchoHeaders(requestedContentType)
5830
6051
  });
5831
6052
  } catch (err) {
5832
6053
  const code = err?.code;
@@ -5834,7 +6055,7 @@ function echoHandler(ingress, opts = {}) {
5834
6055
  const isTimeout = code === "REQUEST_TIMEOUT";
5835
6056
  return new Response(isTooLarge ? ERROR_BODIES.body_too_large : isTimeout ? ERROR_BODIES.request_timeout : ERROR_BODIES.bad_request, {
5836
6057
  status: isTooLarge ? 413 : isTimeout ? 408 : 400,
5837
- headers: ingress.withContentType(baseHeaders, "application/json")
6058
+ headers: withEchoHeaders("application/json")
5838
6059
  });
5839
6060
  }
5840
6061
  };
@@ -5872,6 +6093,11 @@ function defaultJsonValid(bytes) {
5872
6093
  return false;
5873
6094
  }
5874
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
+ }
5875
6101
  function jsonWriteHandler(ingress, opts = {}) {
5876
6102
  const maxBodyBytes = opts.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
5877
6103
  const fallback = opts.fallback ?? ingress;
@@ -5881,13 +6107,8 @@ function jsonWriteHandler(ingress, opts = {}) {
5881
6107
  return async (req, srv) => {
5882
6108
  const ip = resolveIp(req, srv, opts);
5883
6109
  const contentType = req.headers.get("content-type") ?? "";
5884
- if (!contentType.includes("application/json")) {
5885
- return fallback.run(req, ip, null, (result, ctx) => {
5886
- const terminal = fallback.terminalResponse(req, result, ctx);
5887
- if (terminal)
5888
- return terminal;
5889
- return fallback.errorResponse(req, result, 415, "unsupported_media_type", "Content-Type must be application/json", ctx);
5890
- });
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));
5891
6112
  }
5892
6113
  let bodyBytes;
5893
6114
  try {
@@ -6072,7 +6293,7 @@ function safeTerminalStatus(r) {
6072
6293
  }
6073
6294
 
6074
6295
  // src/ingress/routes/responder.ts
6075
- var IGNGEX_SECURITY_HEADERS = [
6296
+ var IGNEX_SECURITY_HEADERS = [
6076
6297
  ["x-frame-options", "DENY"],
6077
6298
  ["x-content-type-options", "nosniff"],
6078
6299
  ["referrer-policy", "no-referrer"]
@@ -6080,7 +6301,7 @@ var IGNGEX_SECURITY_HEADERS = [
6080
6301
  function buildIgngexTerminal(result, _ctx) {
6081
6302
  const preflightAllowed = result.isPreflight && result.corsAllowed;
6082
6303
  if (preflightAllowed) {
6083
- return new Response(null, { status: 204, headers: IGNGEX_SECURITY_HEADERS });
6304
+ return new Response(null, { status: 204, headers: IGNEX_SECURITY_HEADERS });
6084
6305
  }
6085
6306
  const status = safeTerminalStatus(result);
6086
6307
  const body = JSON.stringify({
@@ -6088,7 +6309,7 @@ function buildIgngexTerminal(result, _ctx) {
6088
6309
  status,
6089
6310
  code: errorCodeName(result.errorCode)
6090
6311
  });
6091
- const headers = new Headers(IGNGEX_SECURITY_HEADERS);
6312
+ const headers = new Headers(IGNEX_SECURITY_HEADERS);
6092
6313
  headers.set("content-type", "application/json; charset=utf-8");
6093
6314
  return new Response(body, { status, headers });
6094
6315
  }
@@ -6116,7 +6337,7 @@ function nativeResponderRoute(ingress, responder, opts = {}) {
6116
6337
  const code = err.code;
6117
6338
  const status = code === "REQUEST_TIMEOUT" ? 408 : code === "BODY_TOO_LARGE" ? 413 : 400;
6118
6339
  const payload = JSON.stringify({ error: "Bad Request", status, code: "BAD_REQUEST" });
6119
- const headers = new Headers(IGNGEX_SECURITY_HEADERS);
6340
+ const headers = new Headers(IGNEX_SECURITY_HEADERS);
6120
6341
  headers.set("content-type", "application/json; charset=utf-8");
6121
6342
  return new Response(payload, { status, headers });
6122
6343
  }
@@ -6447,7 +6668,7 @@ function nodeRequestToWebRequest(req) {
6447
6668
  }
6448
6669
  const method = req.method ?? "GET";
6449
6670
  const bodyForbidden = method === "GET" || method === "HEAD";
6450
- const hasBody = Number(req.headers["content-length"] ?? 0) > 0 || req.headers["transfer-encoding"] !== undefined;
6671
+ const hasBody = requestHasBody(req);
6451
6672
  const init = { method, headers };
6452
6673
  if (hasBody && !bodyForbidden) {
6453
6674
  init.body = Readable.toWeb(req);
@@ -6491,7 +6712,7 @@ async function writeResponse(res, response) {
6491
6712
  }
6492
6713
  function drainUnreadBody(res) {
6493
6714
  const req = res.req;
6494
- if (req !== undefined && req !== null && !req.complete && hasBody(req)) {
6715
+ if (req !== undefined && req !== null && !req.complete && requestHasBody(req)) {
6495
6716
  req.resume();
6496
6717
  res.shouldKeepAlive = false;
6497
6718
  if (!res.headersSent) {
@@ -6499,7 +6720,7 @@ function drainUnreadBody(res) {
6499
6720
  }
6500
6721
  }
6501
6722
  }
6502
- function hasBody(req) {
6723
+ function requestHasBody(req) {
6503
6724
  return Number(req.headers["content-length"] ?? 0) > 0 || req.headers["transfer-encoding"] !== undefined;
6504
6725
  }
6505
6726
  function makeRequestListener(options, routes, baseOpts) {
@@ -6555,6 +6776,23 @@ function makeRequestListener(options, routes, baseOpts) {
6555
6776
  }
6556
6777
  };
6557
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
+ }
6558
6796
  function makeUpgradeListener(options) {
6559
6797
  return async (req, socket, head) => {
6560
6798
  try {
@@ -6564,18 +6802,7 @@ function makeUpgradeListener(options) {
6564
6802
  socket.destroy();
6565
6803
  return;
6566
6804
  }
6567
- let raw = `HTTP/1.1 101 Switching Protocols\r
6568
- ` + `Upgrade: websocket\r
6569
- ` + `Connection: Upgrade\r
6570
- ` + `Sec-WebSocket-Accept: ${handshake.accept}\r
6571
- `;
6572
- if (handshake.protocol) {
6573
- raw += `Sec-WebSocket-Protocol: ${handshake.protocol}\r
6574
- `;
6575
- }
6576
- raw += `\r
6577
- `;
6578
- socket.write(raw);
6805
+ socket.write(formatWebSocketHandshake(handshake.accept, handshake.protocol));
6579
6806
  options.onUpgrade?.(socket, req, head);
6580
6807
  } catch {
6581
6808
  socket.destroy();
@@ -6799,6 +7026,7 @@ class IngressResultBase {
6799
7026
  bodyTruncated = false;
6800
7027
  body = EMPTY_BODY2;
6801
7028
  _buf = EMPTY_BODY2;
7029
+ _len = 0;
6802
7030
  _bodyJsonStart = OUT_DATA_START;
6803
7031
  _bodyJsonLen = 0;
6804
7032
  _cookiesBuf = null;
@@ -6824,6 +7052,7 @@ class IngressResultBase {
6824
7052
  this.bodyTruncated = false;
6825
7053
  this.body = EMPTY_BODY2;
6826
7054
  this._buf = EMPTY_BODY2;
7055
+ this._len = 0;
6827
7056
  this._bodyJsonStart = OUT_DATA_START;
6828
7057
  this._bodyJsonLen = 0;
6829
7058
  this._cookiesBuf = null;
@@ -6848,8 +7077,9 @@ class IngressResultBase {
6848
7077
  this.retryAfterMs = 0;
6849
7078
  }
6850
7079
  }
6851
- setSections(buf, layout, bodyJsonLen) {
7080
+ setSections(buf, layout, bodyJsonLen, len = buf.byteLength) {
6852
7081
  this._buf = buf;
7082
+ this._len = len;
6853
7083
  this._bodyJsonStart = layout.bodyJsonStart;
6854
7084
  this._bodyJsonLen = bodyJsonLen;
6855
7085
  this._cookiesBuf = layout.safeCookiesLen > 0 ? buf.subarray(OUT_DATA_START, OUT_DATA_START + layout.safeCookiesLen) : null;
@@ -6879,7 +7109,7 @@ class IngressResultBase {
6879
7109
  if (this._bodyJsonLen === 0)
6880
7110
  return EMPTY_BODY2;
6881
7111
  const end = this._bodyJsonStart + this._bodyJsonLen;
6882
- if (end > this._buf.byteLength)
7112
+ if (end > this._len)
6883
7113
  return EMPTY_BODY2;
6884
7114
  return this._buf.subarray(this._bodyJsonStart, end);
6885
7115
  }
@@ -6943,7 +7173,7 @@ class FastIngressResult extends IngressResultBase {
6943
7173
  this.isPreflight = (flags & FLAG_IS_PREFLIGHT) !== 0;
6944
7174
  this.rateLimited = (flags & FLAG_RATE_LIMITED) !== 0;
6945
7175
  const layout = sectionLayout(buf.byteLength, cookiesJsonLen, queryJsonLen, bodyJsonLen);
6946
- this.setSections(buf, layout, bodyJsonLen);
7176
+ this.setSections(buf, layout, bodyJsonLen, buf.byteLength);
6947
7177
  this.bodyTruncated = (flags & FLAG_BODY_TRUNCATED) !== 0 || layout.truncated;
6948
7178
  }
6949
7179
  invalidate() {
@@ -7035,7 +7265,14 @@ function warnTrustProxyDeprecated() {
7035
7265
  if (trustProxyWarned)
7036
7266
  return;
7037
7267
  trustProxyWarned = true;
7038
- 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).");
7039
7276
  }
7040
7277
 
7041
7278
  // src/ingress/packing/header-packing.ts
@@ -7601,6 +7838,11 @@ class BufferPool {
7601
7838
  this.estimate = options.adaptive === true ? new AdaptiveEstimate({ alpha: 0.25, min: this.initialSize }) : null;
7602
7839
  this.free.push(new Uint8Array(this.initialSize));
7603
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
+ }
7604
7846
  }
7605
7847
  get freeCount() {
7606
7848
  return this.free.length;
@@ -7634,7 +7876,9 @@ class BufferPool {
7634
7876
  for (let i = 0;i < this.free.length; i++) {
7635
7877
  const candidate = this.free[i];
7636
7878
  if (candidate.byteLength >= minSize) {
7637
- this.free.splice(i, 1);
7879
+ const last = this.free.pop();
7880
+ if (i < this.free.length)
7881
+ this.free[i] = last;
7638
7882
  return candidate;
7639
7883
  }
7640
7884
  }
@@ -7649,7 +7893,9 @@ class BufferPool {
7649
7893
  }
7650
7894
  }
7651
7895
  if (largestBuf !== null) {
7652
- this.free.splice(largest, 1);
7896
+ const last = this.free.pop();
7897
+ if (largest < this.free.length)
7898
+ this.free[largest] = last;
7653
7899
  this.created++;
7654
7900
  return new Uint8Array(target);
7655
7901
  }
@@ -7658,16 +7904,15 @@ class BufferPool {
7658
7904
  }
7659
7905
  releaseBuffer(buffer) {
7660
7906
  if (this.free.length >= this.maxBuffers) {
7661
- let smallestBuf = null;
7662
7907
  let smallest = 0;
7663
- for (let i = 0;i < this.free.length; i++) {
7664
- const candidate = this.free[i];
7665
- if (smallestBuf === null || candidate.byteLength < smallestBuf.byteLength) {
7666
- smallestBuf = candidate;
7908
+ for (let i = 1;i < this.free.length; i++) {
7909
+ if (this.free[i].byteLength < this.free[smallest].byteLength) {
7667
7910
  smallest = i;
7668
7911
  }
7669
7912
  }
7670
- this.free.splice(smallest, 1);
7913
+ const last = this.free.pop();
7914
+ if (smallest < this.free.length)
7915
+ this.free[smallest] = last;
7671
7916
  }
7672
7917
  this.free.push(buffer);
7673
7918
  }
@@ -7727,8 +7972,8 @@ function createStructuredLogger(stream = process.stderr, env = process.env) {
7727
7972
 
7728
7973
  // src/ingress/decode/baked-result.ts
7729
7974
  class BakedIngressResult extends IngressResultBase {
7730
- refresh(buf, body, view) {
7731
- if (buf.byteLength < OUT_DATA_START) {
7975
+ refresh(buf, body, view, len = buf.byteLength) {
7976
+ if (len < OUT_DATA_START) {
7732
7977
  this.setInternalError();
7733
7978
  return;
7734
7979
  }
@@ -7741,7 +7986,7 @@ class BakedIngressResult extends IngressResultBase {
7741
7986
  const queryLenRaw = view.getUint32(OUT_QUERY_JSON_LEN, true);
7742
7987
  const headerVariant = view.getUint8(OUT_HEADER_VARIANT);
7743
7988
  const bodyJsonLenRaw = view.getUint32(OUT_BODY_JSON_LEN, true);
7744
- const layout = sectionLayout(buf.byteLength, cookiesLenRaw, queryLenRaw, bodyJsonLenRaw);
7989
+ const layout = sectionLayout(len, cookiesLenRaw, queryLenRaw, bodyJsonLenRaw);
7745
7990
  if (h0 === 0 && h1 === 0) {
7746
7991
  this.verdict = 1;
7747
7992
  this.errorCode = ERR_CODE_INTERNAL;
@@ -7756,7 +8001,7 @@ class BakedIngressResult extends IngressResultBase {
7756
8001
  this.rateRemaining = h3;
7757
8002
  this.setRateWindow(h2, (flags & FLAG_RATE_LIMITED) !== 0, view);
7758
8003
  this.headerVariant = headerVariant;
7759
- this.setSections(buf, layout, layout.safeBodyJsonLen);
8004
+ this.setSections(buf, layout, layout.safeBodyJsonLen, len);
7760
8005
  this.updateOkTerminal();
7761
8006
  this.isPreflight = (flags & FLAG_IS_PREFLIGHT) !== 0;
7762
8007
  this.corsAllowed = (flags & FLAG_CORS_ALLOWED) !== 0;
@@ -7779,8 +8024,16 @@ class BakedIngressResult extends IngressResultBase {
7779
8024
  this.ok = false;
7780
8025
  }
7781
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
+ }
7782
8035
  const slice = this.bodyJsonSlice();
7783
- return copy ? slice.slice() : slice;
8036
+ return slice;
7784
8037
  }
7785
8038
  }
7786
8039
 
@@ -7904,6 +8157,13 @@ function cachedOriginBlock(origin) {
7904
8157
  ORIGIN_BLOCK_CACHE.set(origin, block);
7905
8158
  return block;
7906
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
+ }
7907
8167
 
7908
8168
  // src/ingress/response/baked-response.ts
7909
8169
  init_bytes();
@@ -7956,6 +8216,12 @@ function pooledBodyResponse(handle, bytes, init = {}, timeoutMs = 0) {
7956
8216
 
7957
8217
  // src/ingress/response/baked-response.ts
7958
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
+ }
7959
8225
  function buildBakedResponseBuilders(deps) {
7960
8226
  const {
7961
8227
  headerTemplates,
@@ -7965,6 +8231,45 @@ function buildBakedResponseBuilders(deps) {
7965
8231
  originHeaderCache,
7966
8232
  state
7967
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
+ }
7968
8273
  function responseHeaders(variant, requestIdHeader, origin, rateRemaining, rateResetSecs, retryAfterSecs) {
7969
8274
  const template = headerTemplates[variant & 31] ?? headerTemplates[0] ?? [];
7970
8275
  const needsRequestId = emitRequestIdHeader && requestIdHeader !== null;
@@ -7975,19 +8280,7 @@ function buildBakedResponseBuilders(deps) {
7975
8280
  return template;
7976
8281
  }
7977
8282
  if (!needsRequestId && !needsRate && !needsRetry && needsOrigin) {
7978
- const key = `${variant & 31}\x00${origin}`;
7979
- const cached = originHeaderCache.get(key);
7980
- if (cached !== undefined) {
7981
- return cached;
7982
- }
7983
- const entries = new Array(template.length + 1);
7984
- let i = 0;
7985
- for (const pair of template) {
7986
- entries[i++] = pair;
7987
- }
7988
- entries[i] = ["access-control-allow-origin", origin];
7989
- originHeaderCache.set(key, entries);
7990
- return entries;
8283
+ return originBaseEntries(variant, origin);
7991
8284
  }
7992
8285
  let extra = 0;
7993
8286
  if (needsRequestId)
@@ -8048,27 +8341,27 @@ function buildBakedResponseBuilders(deps) {
8048
8341
  if (preflightAllowed) {
8049
8342
  return new Response(null, {
8050
8343
  status: 204,
8051
- 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))
8052
8345
  });
8053
8346
  }
8054
8347
  const status = safeTerminalStatus(result);
8055
8348
  const body = result.errorCode === ERR_CODE_RATE_LIMITED ? rateLimitedBody(result.retryAfterMs) : ERROR_CODE_BODIES[result.errorCode] ?? ERROR_BODIES.internal ?? EMPTY_BODY4;
8056
8349
  return new Response(body, {
8057
8350
  status,
8058
- headers: terminalHeaders(result.headerVariant, ctx, result)
8351
+ headers: memoizedHeaders(terminalHeaders(result.headerVariant, ctx, result))
8059
8352
  });
8060
8353
  }
8061
8354
  function errorResponse(_req, result, status, code, message, ctx) {
8062
8355
  const body = ERROR_BODIES[code] ?? encoder.encode(JSON.stringify({ ok: false, error: { code, message } }));
8063
8356
  return new Response(body, {
8064
8357
  status,
8065
- headers: terminalHeaders(result?.headerVariant ?? HV_JSON, ctx, result)
8358
+ headers: memoizedHeaders(terminalHeaders(result?.headerVariant ?? HV_JSON, ctx, result))
8066
8359
  });
8067
8360
  }
8068
8361
  function internalErrorResponse(ctx, result) {
8069
8362
  return new Response(ERROR_BODIES.internal, {
8070
8363
  status: 500,
8071
- headers: terminalHeaders(result?.headerVariant ?? HV_JSON, ctx, result ?? null)
8364
+ headers: memoizedHeaders(terminalHeaders(result?.headerVariant ?? HV_JSON, ctx, result ?? null))
8072
8365
  });
8073
8366
  }
8074
8367
  function withContentType(headers, contentType) {
@@ -8089,6 +8382,7 @@ function buildBakedResponseBuilders(deps) {
8089
8382
  }
8090
8383
  return {
8091
8384
  responseHeaders,
8385
+ successHeaders,
8092
8386
  terminalHeaders,
8093
8387
  terminalResponse,
8094
8388
  errorResponse,
@@ -8103,6 +8397,8 @@ var EMPTY_BODY5 = new Uint8Array(0);
8103
8397
  var EMPTY_IP = "0.0.0.0";
8104
8398
  var RATE_LIMIT_U32_MAX = 4294967295;
8105
8399
  var MAX_FFI_FAILURES = 3;
8400
+ var PREWARM_POOL_BUFFERS = 16;
8401
+ var PREWARM_POOL_BYTES = 4 * 1024 * 1024;
8106
8402
  function pathForLog(req) {
8107
8403
  try {
8108
8404
  return new URL(req.url).pathname;
@@ -8139,14 +8435,24 @@ function createIngressHandler(options = {}, runtime = {}) {
8139
8435
  const outputBufferSize = clampIngressBufferSize(runtime.outputBufferSize ?? DEFAULT_BAKED_OUTPUT_BUFFER_SIZE, OUT_DATA_START);
8140
8436
  const logger = runtime.logger ?? (runtime.structuredLog === true ? createStructuredLogger() : null);
8141
8437
  const needRequestIdString = !!(emitRequestIdHeader || runtime.onRequest || runtime.onError || runtime.onResponse || logger);
8438
+ const hasOnRequest = runtime.onRequest !== undefined;
8439
+ const hasOnResponse = runtime.onResponse !== undefined;
8142
8440
  const outputPool = new BufferPool({
8143
8441
  initialSize: outputBufferSize,
8144
- maxInFlight: runtime.maxInFlight
8442
+ maxInFlight: runtime.maxInFlight,
8443
+ prefill: PREWARM_POOL_BUFFERS,
8444
+ prefillBytes: PREWARM_POOL_BYTES
8145
8445
  });
8146
8446
  const inputPacker = new IngressInputPacker;
8147
8447
  const responseState = { currentHandle: null, responseBorrowsBuffer: false };
8148
8448
  let ffiFailures = 0;
8149
8449
  let ffiDisabled = false;
8450
+ const noteFfiFailure = () => {
8451
+ ffiFailures++;
8452
+ if (ffiFailures >= MAX_FFI_FAILURES) {
8453
+ ffiDisabled = true;
8454
+ }
8455
+ };
8150
8456
  const originHeaderCache = new Map;
8151
8457
  const cors = options.cors;
8152
8458
  const securityEntries = runtime.enableSecurityHeaders === false ? Object.freeze([]) : Object.freeze(buildBakedSecurityEntries(options.security, options.https, runtime.securityHeaders));
@@ -8172,6 +8478,17 @@ function createIngressHandler(options = {}, runtime = {}) {
8172
8478
  originHeaderCache,
8173
8479
  state: responseState
8174
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
+ }
8175
8492
  const result = new BakedIngressResult;
8176
8493
  const ctx = {
8177
8494
  requestIdHeader: null,
@@ -8184,7 +8501,8 @@ function createIngressHandler(options = {}, runtime = {}) {
8184
8501
  const requestIdStr = needRequestIdString ? decoder.decode(ridBytes) : "";
8185
8502
  ctx.requestIdHeader = emitRequestIdHeader ? requestIdStr : null;
8186
8503
  ctx.origin = headerPlan.cors ? req.headers.get("origin") : null;
8187
- runtime.onRequest?.(req, requestIdStr, ip);
8504
+ if (hasOnRequest)
8505
+ runtime.onRequest?.(req, requestIdStr, ip);
8188
8506
  const ipStr = ip ?? EMPTY_IP;
8189
8507
  let handle = null;
8190
8508
  responseState.currentHandle = null;
@@ -8202,10 +8520,7 @@ function createIngressHandler(options = {}, runtime = {}) {
8202
8520
  written = bunFFI.ingressHandleComponents(ingressPtr, methodKind, packUrl, packIp ?? "", ridBytes, packedHeaders, body, handle.buffer);
8203
8521
  ffiFailures = 0;
8204
8522
  } catch {
8205
- ffiFailures++;
8206
- if (ffiFailures >= MAX_FFI_FAILURES) {
8207
- ffiDisabled = true;
8208
- }
8523
+ noteFfiFailure();
8209
8524
  const input = inputPacker.packParts(methodKind, packUrl, packIp, ridBytes, packedHeaders);
8210
8525
  written = handler.handleRequestPacked(input, body, handle.buffer);
8211
8526
  }
@@ -8215,18 +8530,14 @@ function createIngressHandler(options = {}, runtime = {}) {
8215
8530
  written = bunFFI.ingressHandlePacked(ingressPtr, input, body, handle.buffer);
8216
8531
  ffiFailures = 0;
8217
8532
  } catch {
8218
- ffiFailures++;
8219
- if (ffiFailures >= MAX_FFI_FAILURES) {
8220
- ffiDisabled = true;
8221
- }
8533
+ noteFfiFailure();
8222
8534
  written = handler.handleRequestPacked(input, body, handle.buffer);
8223
8535
  }
8224
8536
  } else {
8225
8537
  const input = inputPacker.packParts(methodKind, packUrl, packIp, ridBytes, packedHeaders);
8226
8538
  written = handler.handleRequestPacked(input, body, handle.buffer);
8227
8539
  }
8228
- const used = handle.buffer.subarray(0, written);
8229
- 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);
8230
8541
  } catch (err) {
8231
8542
  result.setInternalError();
8232
8543
  const error = err instanceof Error ? err : new Error(String(err));
@@ -8242,17 +8553,20 @@ function createIngressHandler(options = {}, runtime = {}) {
8242
8553
  try {
8243
8554
  const out = fn(result, ctx);
8244
8555
  assertSyncCallback(out, "createIngressHandler().run()");
8245
- const status = out instanceof Response ? out.status : safeTerminalStatus(result);
8246
- runtime.onResponse?.(req, result, status, requestIdStr);
8247
- if (logger) {
8248
- logger.request({
8249
- requestId: requestIdStr,
8250
- method: req.method,
8251
- path: pathForLog(req),
8252
- status,
8253
- durationMs: Math.round((performance.now() - startedAt) * 1000) / 1000,
8254
- ip: ipStr
8255
- });
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
+ }
8256
8570
  }
8257
8571
  return out;
8258
8572
  } finally {
@@ -8566,6 +8880,10 @@ function createIngressRouter(options) {
8566
8880
  }
8567
8881
  const handler = createIngressHandler(spec.options ?? {}, runtime);
8568
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
+ }
8569
8887
  if (spec.responder) {
8570
8888
  bakedRoutes[path] = {
8571
8889
  responder: {
@@ -8790,6 +9108,22 @@ function createIngress(options = {}) {
8790
9108
  }
8791
9109
  };
8792
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
+ }
8793
9127
  // src/loader/index.ts
8794
9128
  init_rust_ffi();
8795
9129
  init_bytes();
@@ -10207,6 +10541,7 @@ function createTaskRuntime(options = {}) {
10207
10541
  queueMicrotask(drainNow);
10208
10542
  }
10209
10543
  function submit(op, args, runOptions) {
10544
+ ensureDoorbell();
10210
10545
  const id = nextId++;
10211
10546
  return new Promise((resolve, reject) => {
10212
10547
  inflight.set(id, { out: null, keep: null, resolve, reject });
@@ -10228,6 +10563,7 @@ function createTaskRuntime(options = {}) {
10228
10563
  });
10229
10564
  }
10230
10565
  function submitInto(op, args, output, runOptions) {
10566
+ ensureDoorbell();
10231
10567
  const id = nextId++;
10232
10568
  return new Promise((resolve, reject) => {
10233
10569
  inflight.set(id, { out: output, keep: null, resolve, reject });
@@ -10249,6 +10585,7 @@ function createTaskRuntime(options = {}) {
10249
10585
  });
10250
10586
  }
10251
10587
  function submitSlice(op, hdr, data, runOptions) {
10588
+ ensureDoorbell();
10252
10589
  const id = nextId++;
10253
10590
  return new Promise((resolve, reject) => {
10254
10591
  inflight.set(id, { out: null, keep: data, resolve, reject });
@@ -10270,11 +10607,16 @@ function createTaskRuntime(options = {}) {
10270
10607
  });
10271
10608
  }
10272
10609
  const { JSCallback } = __require("bun:ffi");
10273
- const cb = new JSCallback(() => {
10274
- drainNow();
10275
- }, { args: [], returns: "void", threadsafe: true });
10276
- doorbell = cb;
10277
- 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();
10278
10620
  schedule();
10279
10621
  const runtime = {
10280
10622
  gzipDecompress(data, runOptions) {
@@ -10318,6 +10660,7 @@ function createTaskRuntime(options = {}) {
10318
10660
  deferred.reject(cancelledError());
10319
10661
  }
10320
10662
  inflight.clear();
10663
+ ffi.taskSetDoorbell(0);
10321
10664
  ffi.taskShutdown();
10322
10665
  release();
10323
10666
  doorbell?.close();
@@ -10454,6 +10797,7 @@ export {
10454
10797
  errorMessage,
10455
10798
  fallbackHandler,
10456
10799
  generateRequestId,
10800
+ getIngressBinding,
10457
10801
  gracefulShutdown,
10458
10802
  gzipCompressUpperBound,
10459
10803
  headHandler,