@barqel/barq 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,10 @@
1
1
  // src/internal/node-compat.ts
2
+ import cluster from "node:cluster";
2
3
  import crypto from "node:crypto";
4
+ import fs from "node:fs";
3
5
  import http from "node:http";
6
+ import https from "node:https";
7
+ import { cpus as cpus2 } from "node:os";
4
8
  import { Readable } from "node:stream";
5
9
 
6
10
  // src/app.ts
@@ -495,6 +499,86 @@ function parseIpAddress(value) {
495
499
  return bytes;
496
500
  }
497
501
 
502
+ // src/errors.ts
503
+ class HttpError extends Error {
504
+ status;
505
+ body;
506
+ headers;
507
+ constructor(status, body, headers) {
508
+ if (!Number.isInteger(status) || status < 100 || status > 599) {
509
+ throw new Error(`Invalid HTTP status code: ${status}. Must be an integer between 100 and 599.`);
510
+ }
511
+ super(typeof body === "string" ? body : `HTTP ${status}`);
512
+ this.name = "HttpError";
513
+ this.status = status;
514
+ this.body = body ?? { error: defaultStatusText(status) };
515
+ if (headers !== undefined)
516
+ this.headers = headers;
517
+ }
518
+ }
519
+ function defaultStatusText(status) {
520
+ switch (status) {
521
+ case 400:
522
+ return "Bad Request";
523
+ case 401:
524
+ return "Unauthorized";
525
+ case 403:
526
+ return "Forbidden";
527
+ case 404:
528
+ return "Not Found";
529
+ case 405:
530
+ return "Method Not Allowed";
531
+ case 408:
532
+ return "Request Timeout";
533
+ case 409:
534
+ return "Conflict";
535
+ case 413:
536
+ return "Payload Too Large";
537
+ case 418:
538
+ return "I'm a teapot";
539
+ case 422:
540
+ return "Unprocessable Entity";
541
+ case 429:
542
+ return "Too Many Requests";
543
+ case 500:
544
+ return "Internal Server Error";
545
+ case 502:
546
+ return "Bad Gateway";
547
+ case 503:
548
+ return "Service Unavailable";
549
+ case 504:
550
+ return "Gateway Timeout";
551
+ default:
552
+ return status >= 500 ? "Internal Server Error" : "Error";
553
+ }
554
+ }
555
+ function safeJson(status, body, headers) {
556
+ try {
557
+ const init = headers !== undefined ? { status, headers } : { status };
558
+ return json(body, init);
559
+ } catch {
560
+ return new Response("Internal Server Error", {
561
+ status: 500,
562
+ headers: { "content-type": "text/plain; charset=utf-8" }
563
+ });
564
+ }
565
+ }
566
+ function errorToResponse(err, development) {
567
+ if (err instanceof Response) {
568
+ return err;
569
+ }
570
+ if (err instanceof HttpError) {
571
+ return safeJson(err.status, err.body, err.headers);
572
+ }
573
+ if (development) {
574
+ const message = err instanceof Error ? err.message : String(err);
575
+ const stack = err instanceof Error ? err.stack : undefined;
576
+ return safeJson(500, { error: "Internal Server Error", message, stack });
577
+ }
578
+ return safeJson(500, { error: "Internal Server Error" });
579
+ }
580
+ var createErrorResponse = errorToResponse;
581
+
498
582
  // src/internal/pathname.ts
499
583
  function decodeSegment(seg) {
500
584
  if (seg.indexOf("%") === -1)
@@ -541,9 +625,6 @@ function hasDotSegment(path) {
541
625
  }
542
626
  return false;
543
627
  }
544
- function hasEncodedSlash(path) {
545
- return path.includes("%2f") || path.includes("%2F");
546
- }
547
628
  function targetStart(url) {
548
629
  if (url.charCodeAt(0) === 47)
549
630
  return 0;
@@ -596,11 +677,11 @@ function scanPathname(url) {
596
677
  end = hash;
597
678
  }
598
679
  const raw = url.slice(slash, end);
680
+ if (raw.indexOf("%00") !== -1 || raw.indexOf("\x00") !== -1) {
681
+ throw new HttpError(400, { error: "Bad Request" });
682
+ }
599
683
  if (raw.indexOf(".") === -1 && raw.indexOf("%") === -1)
600
684
  return raw;
601
- if (hasEncodedSlash(raw)) {
602
- return raw;
603
- }
604
685
  return hasDotSegment(raw) ? normalizeDotSegments(raw) : raw;
605
686
  }
606
687
  function parseRequestTarget(url) {
@@ -626,11 +707,10 @@ function parseRequestTarget(url) {
626
707
  query = hash !== -1 && hash > end ? url.slice(end + 1, hash) : url.slice(end + 1);
627
708
  }
628
709
  const raw = url.slice(slash, end);
629
- if (hasEncodedSlash(raw)) {
630
- pathname = raw;
631
- } else {
632
- pathname = hasDotSegment(raw) ? normalizeDotSegments(raw) : raw;
710
+ if (raw.indexOf("%00") !== -1 || raw.indexOf("\x00") !== -1) {
711
+ throw new HttpError(400, { error: "Bad Request" });
633
712
  }
713
+ pathname = hasDotSegment(raw) ? normalizeDotSegments(raw) : raw;
634
714
  }
635
715
  return { pathname, query };
636
716
  }
@@ -654,17 +734,20 @@ function findQueryParam(url, name, startPos) {
654
734
  const queryStart = startPos !== undefined && startPos >= 0 ? startPos : url.indexOf("?");
655
735
  if (queryStart === -1)
656
736
  return null;
737
+ const hashPos = url.indexOf("#", queryStart);
738
+ const len = hashPos === -1 ? url.length : hashPos;
657
739
  let s = queryStart + 1;
658
- const len = url.length;
659
740
  const nameLen = name.length;
660
741
  while (s < len) {
661
742
  if (url.startsWith(name, s)) {
662
743
  const eqPos = s + nameLen;
744
+ if (eqPos >= len)
745
+ return "";
663
746
  const nextChar = url.charCodeAt(eqPos);
664
747
  if (nextChar === 61) {
665
748
  const valStart = eqPos + 1;
666
749
  let amp = url.indexOf("&", valStart);
667
- if (amp === -1)
750
+ if (amp === -1 || amp > len)
668
751
  amp = len;
669
752
  const val = url.slice(valStart, amp);
670
753
  if (val.indexOf("+") === -1 && val.indexOf("%") === -1) {
@@ -672,12 +755,12 @@ function findQueryParam(url, name, startPos) {
672
755
  }
673
756
  return decodeQueryComponent(val);
674
757
  }
675
- if (nextChar === 38 || eqPos === len) {
758
+ if (nextChar === 38) {
676
759
  return "";
677
760
  }
678
761
  }
679
762
  const amp = url.indexOf("&", s);
680
- if (amp === -1)
763
+ if (amp === -1 || amp >= len)
681
764
  break;
682
765
  s = amp + 1;
683
766
  }
@@ -688,12 +771,13 @@ function findQueryParamAll(url, name) {
688
771
  const queryStart = url.indexOf("?");
689
772
  if (queryStart === -1)
690
773
  return res;
691
- const len = url.length;
774
+ const hashPos = url.indexOf("#", queryStart);
775
+ const len = hashPos === -1 ? url.length : hashPos;
692
776
  let s = queryStart + 1;
693
777
  const nLen = name.length;
694
778
  while (s < len) {
695
779
  let amp = url.indexOf("&", s);
696
- if (amp === -1)
780
+ if (amp === -1 || amp > len)
697
781
  amp = len;
698
782
  let eq = url.indexOf("=", s);
699
783
  if (eq === -1 || eq > amp)
@@ -718,12 +802,13 @@ function parseAllQueryParams(url) {
718
802
  const qIdx = url.indexOf("?");
719
803
  if (qIdx === -1)
720
804
  return Object.create(null);
721
- const len = url.length;
805
+ const hashPos = url.indexOf("#", qIdx);
806
+ const len = hashPos === -1 ? url.length : hashPos;
722
807
  const res = Object.create(null);
723
808
  let s = qIdx + 1;
724
809
  while (s < len) {
725
810
  let amp = url.indexOf("&", s);
726
- if (amp === -1)
811
+ if (amp === -1 || amp > len)
727
812
  amp = len;
728
813
  let eq = url.indexOf("=", s);
729
814
  if (eq === -1 || eq > amp)
@@ -866,12 +951,10 @@ class Context {
866
951
  this._getLazy().ip = directIp;
867
952
  return directIp;
868
953
  }
869
- for (const name of ["cf-connecting-ip", "x-real-ip"]) {
870
- const candidate = this.req.headers.get(name)?.trim();
871
- if (candidate && isValidIpAddress(candidate)) {
872
- this._getLazy().ip = candidate;
873
- return candidate;
874
- }
954
+ const realIp = this.req.headers.get("x-real-ip")?.trim();
955
+ if (realIp && isValidIpAddress(realIp)) {
956
+ this._getLazy().ip = realIp;
957
+ return realIp;
875
958
  }
876
959
  this._getLazy().ip = directIp;
877
960
  return directIp;
@@ -985,86 +1068,6 @@ class Context {
985
1068
  }
986
1069
  }
987
1070
 
988
- // src/errors.ts
989
- class HttpError extends Error {
990
- status;
991
- body;
992
- headers;
993
- constructor(status, body, headers) {
994
- if (!Number.isInteger(status) || status < 100 || status > 599) {
995
- throw new Error(`Invalid HTTP status code: ${status}. Must be an integer between 100 and 599.`);
996
- }
997
- super(typeof body === "string" ? body : `HTTP ${status}`);
998
- this.name = "HttpError";
999
- this.status = status;
1000
- this.body = body ?? { error: defaultStatusText(status) };
1001
- if (headers !== undefined)
1002
- this.headers = headers;
1003
- }
1004
- }
1005
- function defaultStatusText(status) {
1006
- switch (status) {
1007
- case 400:
1008
- return "Bad Request";
1009
- case 401:
1010
- return "Unauthorized";
1011
- case 403:
1012
- return "Forbidden";
1013
- case 404:
1014
- return "Not Found";
1015
- case 405:
1016
- return "Method Not Allowed";
1017
- case 408:
1018
- return "Request Timeout";
1019
- case 409:
1020
- return "Conflict";
1021
- case 413:
1022
- return "Payload Too Large";
1023
- case 418:
1024
- return "I'm a teapot";
1025
- case 422:
1026
- return "Unprocessable Entity";
1027
- case 429:
1028
- return "Too Many Requests";
1029
- case 500:
1030
- return "Internal Server Error";
1031
- case 502:
1032
- return "Bad Gateway";
1033
- case 503:
1034
- return "Service Unavailable";
1035
- case 504:
1036
- return "Gateway Timeout";
1037
- default:
1038
- return status >= 500 ? "Internal Server Error" : "Error";
1039
- }
1040
- }
1041
- function safeJson(status, body, headers) {
1042
- try {
1043
- const init = headers !== undefined ? { status, headers } : { status };
1044
- return json(body, init);
1045
- } catch {
1046
- return new Response("Internal Server Error", {
1047
- status: 500,
1048
- headers: { "content-type": "text/plain; charset=utf-8" }
1049
- });
1050
- }
1051
- }
1052
- function errorToResponse(err, development) {
1053
- if (err instanceof Response) {
1054
- return err;
1055
- }
1056
- if (err instanceof HttpError) {
1057
- return safeJson(err.status, err.body, err.headers);
1058
- }
1059
- if (development) {
1060
- const message = err instanceof Error ? err.message : String(err);
1061
- const stack = err instanceof Error ? err.stack : undefined;
1062
- return safeJson(500, { error: "Internal Server Error", message, stack });
1063
- }
1064
- return safeJson(500, { error: "Internal Server Error" });
1065
- }
1066
- var createErrorResponse = errorToResponse;
1067
-
1068
1071
  // src/internal/scan.ts
1069
1072
  var ESCAPE_JSON_CHARS_RE = /[\u0000-\u001f"\\]/;
1070
1073
  function escapeJsonString(value) {
@@ -2071,13 +2074,13 @@ async function stopServer(server, hardTimeoutMs) {
2071
2074
  let forced = false;
2072
2075
  let stopPromise = null;
2073
2076
  try {
2074
- stopPromise = server.stop().then(() => {});
2077
+ stopPromise = Promise.resolve(server.stop()).then(() => {});
2075
2078
  await Promise.race([
2076
2079
  stopPromise,
2077
2080
  new Promise((resolve) => {
2078
2081
  timer = setTimeout(() => {
2079
2082
  forced = true;
2080
- stopPromise = server.stop(true).then(() => {});
2083
+ stopPromise = Promise.resolve(server.stop(true)).then(() => {});
2081
2084
  resolve();
2082
2085
  }, hardTimeoutMs);
2083
2086
  timer?.unref?.();
@@ -2107,7 +2110,6 @@ var METHOD_BITS = {
2107
2110
  HEAD: 1 << 5,
2108
2111
  OPTIONS: 1 << 6
2109
2112
  };
2110
- var _ALL_METHODS_BITMASK = (1 << 7) - 1;
2111
2113
  var STATIC_CHILD_SCAN_THRESHOLD = 4;
2112
2114
  function methodBit(method) {
2113
2115
  return METHOD_BITS[method] ?? 0;
@@ -2159,10 +2161,10 @@ function cloneNode(node) {
2159
2161
  var MAX_OPTIONAL_PARAMS = 8;
2160
2162
  function parsePattern(pattern) {
2161
2163
  if (pattern.length === 0 || pattern.charCodeAt(0) !== 47) {
2162
- throw new Error(`Invalid route pattern "${pattern}": must start with '/'`);
2164
+ throw new Error(`Invalid route pattern "${pattern}": must start with /`);
2163
2165
  }
2164
2166
  if (pattern.indexOf("#") !== -1) {
2165
- throw new Error(`Invalid route pattern "${pattern}": must not contain '#'`);
2167
+ throw new Error(`Invalid route pattern "${pattern}": must not contain #`);
2166
2168
  }
2167
2169
  let p = pattern;
2168
2170
  if (p.length > 1 && p.charCodeAt(p.length - 1) === 47) {
@@ -2176,7 +2178,7 @@ function parsePattern(pattern) {
2176
2178
  const end = slash === -1 ? p.length : slash;
2177
2179
  const seg = p.slice(i, end);
2178
2180
  if (seg.length === 0) {
2179
- throw new Error(`Invalid route pattern "${pattern}": empty segment ('//')`);
2181
+ throw new Error(`Invalid route pattern "${pattern}": empty segment (//)`);
2180
2182
  }
2181
2183
  const c0 = seg.charCodeAt(0);
2182
2184
  if (seg.includes("?") && !(c0 === 58 && seg.endsWith("?") && seg.indexOf("?") === seg.length - 1)) {
@@ -2198,7 +2200,7 @@ function parsePattern(pattern) {
2198
2200
  segments.push(optional ? { kind: "param", name, optional: true } : { kind: "param", name });
2199
2201
  } else if (c0 === 42) {
2200
2202
  if (end !== p.length) {
2201
- throw new Error(`Invalid route pattern "${pattern}": wildcard '*' must be the final segment`);
2203
+ throw new Error(`Invalid route pattern "${pattern}": wildcard * must be the final segment`);
2202
2204
  }
2203
2205
  const name = seg.length > 1 ? seg.slice(1) : null;
2204
2206
  if (name !== null) {
@@ -2290,7 +2292,7 @@ function matchNode(rootNode, pathname, startI, method, paramNames, paramValues,
2290
2292
  child = staticChildren.get(pathname.slice(i, segEnd));
2291
2293
  }
2292
2294
  if (child !== undefined) {
2293
- const hasParamChildren = node.paramChildren !== null && node.paramChildren.size > 0;
2295
+ const hasParamChildren = node.paramChild !== null || node.paramChildren !== null && node.paramChildren.size > 0;
2294
2296
  if (!hasParamChildren && node.wildcardChild === null) {
2295
2297
  node = child;
2296
2298
  i = nextI;
@@ -2306,9 +2308,39 @@ function matchNode(rootNode, pathname, startI, method, paramNames, paramValues,
2306
2308
  }
2307
2309
  }
2308
2310
  }
2309
- const paramChildren = node.paramChildren ?? (node.paramChild !== null && node.paramName !== null ? new Map([[node.paramName, node.paramChild]]) : null);
2310
- if (paramChildren !== null && paramChildren.size > 0) {
2311
- for (const [paramName, child] of paramChildren) {
2311
+ if (node.paramChild !== null && node.paramName !== null) {
2312
+ const child = node.paramChild;
2313
+ const paramName = node.paramName;
2314
+ if (nextI >= len && child.handlers !== null) {
2315
+ let leafH = child.handlers[method];
2316
+ if (leafH === undefined && method === "HEAD")
2317
+ leafH = child.handlers.GET;
2318
+ if (leafH !== undefined) {
2319
+ const paramVal = pathname.indexOf("%", i) !== -1 ? decodeParam(pathname.slice(i, segEnd)) : pathname.slice(i, segEnd);
2320
+ const p = {
2321
+ [paramName]: paramVal,
2322
+ [MATCH_HANDLER]: leafH
2323
+ };
2324
+ if (depth === 0)
2325
+ return p;
2326
+ for (let k = 0;k < depth; k++) {
2327
+ p[paramNames[k]] = paramValues[k];
2328
+ }
2329
+ return p;
2330
+ }
2331
+ }
2332
+ paramNames[depth] = paramName;
2333
+ paramValues[depth] = decodeParam(pathname.slice(i, segEnd));
2334
+ const res = matchNode(child, pathname, nextI, method, paramNames, paramValues, depth + 1);
2335
+ if (res !== null) {
2336
+ if (MATCH_NOT_ALLOWED in res) {
2337
+ notAllowedCandidate ??= res;
2338
+ } else {
2339
+ return res;
2340
+ }
2341
+ }
2342
+ } else if (node.paramChildren !== null && node.paramChildren.size > 0) {
2343
+ for (const [paramName, child] of node.paramChildren) {
2312
2344
  if (nextI >= len && child.handlers !== null) {
2313
2345
  let leafH = child.handlers[method];
2314
2346
  if (leafH === undefined && method === "HEAD")
@@ -2475,17 +2507,6 @@ class SegmentTrie {
2475
2507
  this.maxStaticLength = cleanPattern.length;
2476
2508
  }
2477
2509
  }
2478
- for (const seg of segments) {
2479
- if (seg.kind === "static") {} else if (seg.kind === "param") {
2480
- if (isPureStatic) {
2481
- throw new Error(`Internal error: param segment in pure static route`);
2482
- }
2483
- } else {
2484
- if (isPureStatic) {
2485
- throw new Error(`Internal error: wildcard segment in pure static route`);
2486
- }
2487
- }
2488
- }
2489
2510
  let node = this.root;
2490
2511
  for (const seg of segments) {
2491
2512
  if (seg.kind === "static") {
@@ -2868,12 +2889,6 @@ var FAST_JSON_HEADERS = new Headers({
2868
2889
  var FAST_TEXT_HEADERS = new Headers({
2869
2890
  "content-type": "text/plain; charset=utf-8"
2870
2891
  });
2871
- var _FAST_JSON_INIT = Object.freeze({
2872
- headers: FAST_JSON_HEADERS
2873
- });
2874
- var _FAST_TEXT_INIT = Object.freeze({
2875
- headers: FAST_TEXT_HEADERS
2876
- });
2877
2892
 
2878
2893
  class ReadonlySetView {
2879
2894
  source;
@@ -3054,12 +3069,16 @@ class FastLightContext {
3054
3069
  return this;
3055
3070
  }
3056
3071
  get url() {
3057
- return this._url ?? (this._url = this.req.url);
3072
+ if (this._url === undefined) {
3073
+ this._url = this.req.url;
3074
+ }
3075
+ return this._url;
3058
3076
  }
3059
3077
  get pathname() {
3060
- if (this._pathname !== undefined)
3061
- return this._pathname;
3062
- return this._pathname = scanPathname(this.url);
3078
+ if (this._pathname === undefined) {
3079
+ this._pathname = scanPathname(this.url);
3080
+ }
3081
+ return this._pathname;
3063
3082
  }
3064
3083
  queryParam(name) {
3065
3084
  let u = this._url;
@@ -3093,6 +3112,19 @@ class FastLightContext {
3093
3112
  return this.req.body;
3094
3113
  }
3095
3114
  }
3115
+ var lightContextPool = [];
3116
+ function acquireLightContext(req, params) {
3117
+ const popped = lightContextPool.pop();
3118
+ if (popped !== undefined) {
3119
+ return popped.init(req, params);
3120
+ }
3121
+ return new FastLightContext(req, params);
3122
+ }
3123
+ function releaseLightContext(ctx) {
3124
+ if (lightContextPool.length < 128) {
3125
+ lightContextPool.push(ctx);
3126
+ }
3127
+ }
3096
3128
  function reportWebSocketError(handler, ws, error) {
3097
3129
  if (!handler) {
3098
3130
  console.error("Unhandled WebSocket callback error:", error);
@@ -3193,12 +3225,12 @@ function canonicalizeRoutePattern(pattern) {
3193
3225
  }
3194
3226
  function validateRoutePattern(pattern) {
3195
3227
  if (pattern.length === 0 || pattern.charCodeAt(0) !== 47) {
3196
- throw new Error(`Invalid route pattern "${pattern}": must start with '/'`);
3228
+ throw new Error(`Invalid route pattern "${pattern}": must start with /`);
3197
3229
  }
3198
3230
  if (pattern === "/")
3199
3231
  return;
3200
3232
  if (pattern.indexOf("#") !== -1) {
3201
- throw new Error(`Invalid route pattern "${pattern}": must not contain '#'`);
3233
+ throw new Error(`Invalid route pattern "${pattern}": must not contain #`);
3202
3234
  }
3203
3235
  const segments = pattern.split("/");
3204
3236
  if (segments.length - 1 > MAX_ROUTE_SEGMENTS) {
@@ -3207,11 +3239,11 @@ function validateRoutePattern(pattern) {
3207
3239
  for (let i = 1;i < segments.length; i++) {
3208
3240
  const seg = segments[i];
3209
3241
  if (seg.length === 0) {
3210
- throw new Error(`Invalid route pattern "${pattern}": empty segment ('//')`);
3242
+ throw new Error(`Invalid route pattern "${pattern}": empty segment (//)`);
3211
3243
  }
3212
3244
  const decoded = decodeSegment2(seg);
3213
3245
  if (decoded === "." || decoded === "..") {
3214
- throw new Error(`Invalid route pattern "${pattern}": dot segments '.', '..' not allowed in route patterns`);
3246
+ throw new Error(`Invalid route pattern "${pattern}": dot segments ., .. not allowed in route patterns`);
3215
3247
  }
3216
3248
  }
3217
3249
  }
@@ -3685,11 +3717,15 @@ class App {
3685
3717
  }
3686
3718
  } else if (nr.isParamsOnly && raw) {
3687
3719
  const analysis = analyzeHandler(raw);
3688
- if (analysis.paramSerializer && raw.constructor.name !== "AsyncFunction") {
3720
+ if (analysis.paramSerializer) {
3689
3721
  const serializer = analysis.paramSerializer;
3690
3722
  const responseInit = { headers: FAST_JSON_HEADERS };
3691
3723
  methodMap[nr.method] = (req) => {
3692
3724
  const params = req.params ?? EMPTY_PARAMS;
3725
+ const syncRes = raw({ params });
3726
+ if (isThenable(syncRes)) {
3727
+ return Promise.resolve(syncRes).then((val) => val instanceof Response ? val : typeof val === "object" && val !== null ? Response.json(val) : new Response(String(val)));
3728
+ }
3693
3729
  return new Response(serializer(params), responseInit);
3694
3730
  };
3695
3731
  } else {
@@ -3724,48 +3760,36 @@ class App {
3724
3760
  methodMap.HEAD = () => new Response(null, { headers: FAST_JSON_HEADERS });
3725
3761
  }
3726
3762
  } else if (nr.isLightweight && raw) {
3727
- const isAsync = raw.constructor.name === "AsyncFunction";
3728
- const reusedLightCtx = new FastLightContext(null, EMPTY_PARAMS);
3729
- let lightCtxInUse = false;
3730
3763
  methodMap[nr.method] = (req) => {
3731
3764
  const params = isDyn ? req.params ?? EMPTY_PARAMS : EMPTY_PARAMS;
3732
- const useFresh = isAsync || lightCtxInUse;
3733
- const ctx = useFresh ? new FastLightContext(req, params) : reusedLightCtx.init(req, params);
3734
- if (!useFresh)
3735
- lightCtxInUse = true;
3765
+ const ctx = acquireLightContext(req, params);
3736
3766
  let res;
3737
3767
  try {
3738
3768
  res = raw(ctx);
3739
3769
  } catch (err) {
3740
- if (!useFresh)
3741
- lightCtxInUse = false;
3770
+ releaseLightContext(ctx);
3742
3771
  return errorToResponse(err, this.development);
3743
3772
  }
3744
3773
  if (typeof res === "object" && res !== null) {
3745
3774
  if (res instanceof Response) {
3746
- if (!useFresh)
3747
- lightCtxInUse = false;
3775
+ releaseLightContext(ctx);
3748
3776
  return res;
3749
3777
  }
3750
3778
  if (!isThenable(res)) {
3751
- if (!useFresh)
3752
- lightCtxInUse = false;
3779
+ releaseLightContext(ctx);
3753
3780
  return Response.json(res);
3754
3781
  }
3755
3782
  } else if (typeof res === "string") {
3756
- if (!useFresh)
3757
- lightCtxInUse = false;
3783
+ releaseLightContext(ctx);
3758
3784
  return new Response(res, { headers: FAST_TEXT_HEADERS });
3759
3785
  } else if (!isThenable(res)) {
3760
- if (!useFresh)
3761
- lightCtxInUse = false;
3786
+ releaseLightContext(ctx);
3762
3787
  return new Response(JSON.stringify(res), {
3763
3788
  headers: FAST_JSON_HEADERS
3764
3789
  });
3765
3790
  }
3766
3791
  return Promise.resolve(res).then((r) => {
3767
- if (!useFresh)
3768
- lightCtxInUse = false;
3792
+ releaseLightContext(ctx);
3769
3793
  if (r instanceof Response)
3770
3794
  return r;
3771
3795
  if (typeof r === "object" && r !== null)
@@ -3776,8 +3800,7 @@ class App {
3776
3800
  return new Response(r, { headers: FAST_TEXT_HEADERS });
3777
3801
  return finalizeValue(r);
3778
3802
  }).catch((err) => {
3779
- if (!useFresh)
3780
- lightCtxInUse = false;
3803
+ releaseLightContext(ctx);
3781
3804
  return errorToResponse(err, this.development);
3782
3805
  });
3783
3806
  };
@@ -3824,7 +3847,7 @@ class App {
3824
3847
  return isThenable(res) ? res.then(stripBody) : stripBody(res);
3825
3848
  }
3826
3849
  }
3827
- return new Response(`{"error":"Method Not Allowed"}`, {
3850
+ return new Response('{"error":"Method Not Allowed"}', {
3828
3851
  status: 405,
3829
3852
  headers: {
3830
3853
  "content-type": "application/json; charset=utf-8",
@@ -3973,6 +3996,8 @@ class App {
3973
3996
  throw new Error("Cannot start cluster without an application entry file");
3974
3997
  }
3975
3998
  const processes = [];
3999
+ let crashCount = 0;
4000
+ let lastReset = Date.now();
3976
4001
  const spawnWorker = () => {
3977
4002
  const proc = Bun.spawn([process.execPath, "run", entry], {
3978
4003
  env: {
@@ -3984,10 +4009,25 @@ class App {
3984
4009
  });
3985
4010
  proc.exited.then((_code) => {
3986
4011
  if (this.stopPromise === null && this.clusterProcesses !== null) {
4012
+ const now = Date.now();
4013
+ if (now - lastReset > 5000) {
4014
+ crashCount = 0;
4015
+ lastReset = now;
4016
+ }
4017
+ crashCount++;
3987
4018
  const idx = processes.indexOf(proc);
3988
4019
  if (idx !== -1) {
3989
4020
  processes.splice(idx, 1);
3990
4021
  }
4022
+ if (crashCount > 15) {
4023
+ console.error("[Barq Cluster] Workers restarting too fast. Throttling respawn.");
4024
+ setTimeout(() => {
4025
+ if (this.stopPromise === null && this.clusterProcesses !== null) {
4026
+ processes.push(spawnWorker());
4027
+ }
4028
+ }, 2000);
4029
+ return;
4030
+ }
3991
4031
  processes.push(spawnWorker());
3992
4032
  }
3993
4033
  });
@@ -4096,6 +4136,300 @@ function stripBody(res) {
4096
4136
 
4097
4137
  // src/internal/node-compat.ts
4098
4138
  var isNode = typeof Bun === "undefined";
4139
+ var WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
4140
+ var nodeTopics = new Map;
4141
+
4142
+ class NodeServerWebSocket {
4143
+ socket;
4144
+ data;
4145
+ readyState = 1;
4146
+ remoteAddress;
4147
+ topics = new Set;
4148
+ buffer = Buffer.alloc(0);
4149
+ fragments = [];
4150
+ fragmentOpcode = 0;
4151
+ maxPayloadLength;
4152
+ handlers;
4153
+ appHandlers;
4154
+ constructor(socket, data, wsOptions) {
4155
+ this.socket = socket;
4156
+ this.data = data;
4157
+ this.remoteAddress = ("remoteAddress" in socket ? socket.remoteAddress : "") || "";
4158
+ this.maxPayloadLength = wsOptions?.maxPayloadLength ?? 16 * 1024 * 1024;
4159
+ this.appHandlers = wsOptions;
4160
+ const internal = data?._barqInternal ? data.handlers : undefined;
4161
+ this.handlers = internal;
4162
+ socket.on("data", (chunk) => this.onData(chunk));
4163
+ socket.on("close", () => this.onClose(1006, ""));
4164
+ socket.on("error", (err) => this.onError(err));
4165
+ socket.on("drain", () => this.onDrain());
4166
+ }
4167
+ send(data) {
4168
+ if (this.readyState !== 1)
4169
+ return -1;
4170
+ let payload;
4171
+ let isText = true;
4172
+ if (typeof data === "string") {
4173
+ payload = Buffer.from(data, "utf8");
4174
+ isText = true;
4175
+ } else if (Buffer.isBuffer(data)) {
4176
+ payload = data;
4177
+ isText = false;
4178
+ } else if (ArrayBuffer.isView(data)) {
4179
+ payload = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
4180
+ isText = false;
4181
+ } else {
4182
+ payload = Buffer.from(String(data), "utf8");
4183
+ isText = true;
4184
+ }
4185
+ this.sendFrame(isText ? 1 : 2, payload);
4186
+ return payload.length;
4187
+ }
4188
+ subscribe(room) {
4189
+ let set = nodeTopics.get(room);
4190
+ if (!set) {
4191
+ set = new Set;
4192
+ nodeTopics.set(room, set);
4193
+ }
4194
+ set.add(this);
4195
+ this.topics.add(room);
4196
+ }
4197
+ unsubscribe(room) {
4198
+ const set = nodeTopics.get(room);
4199
+ if (set) {
4200
+ set.delete(this);
4201
+ if (set.size === 0)
4202
+ nodeTopics.delete(room);
4203
+ }
4204
+ this.topics.delete(room);
4205
+ }
4206
+ isSubscribed(room) {
4207
+ return this.topics.has(room);
4208
+ }
4209
+ publish(room, message, _compress) {
4210
+ const set = nodeTopics.get(room);
4211
+ if (!set || set.size === 0)
4212
+ return 0;
4213
+ let sent = 0;
4214
+ for (const client of set) {
4215
+ if (client !== this && client.readyState === 1) {
4216
+ client.send(message);
4217
+ sent++;
4218
+ }
4219
+ }
4220
+ return sent;
4221
+ }
4222
+ ping(data = "") {
4223
+ if (this.readyState !== 1)
4224
+ return;
4225
+ const buf = typeof data === "string" ? Buffer.from(data, "utf8") : data;
4226
+ this.sendFrame(9, buf);
4227
+ }
4228
+ pong(data = "") {
4229
+ if (this.readyState !== 1)
4230
+ return;
4231
+ const buf = typeof data === "string" ? Buffer.from(data, "utf8") : data;
4232
+ this.sendFrame(10, buf);
4233
+ }
4234
+ cork(callback) {
4235
+ return callback(this);
4236
+ }
4237
+ close(code = 1000, reason = "") {
4238
+ if (this.readyState >= 2)
4239
+ return;
4240
+ this.readyState = 2;
4241
+ const reasonBuf = Buffer.from(reason, "utf8");
4242
+ const payload = Buffer.allocUnsafe(2 + reasonBuf.length);
4243
+ payload.writeUInt16BE(code, 0);
4244
+ reasonBuf.copy(payload, 2);
4245
+ this.sendFrame(8, payload);
4246
+ this.socket.end();
4247
+ }
4248
+ terminate() {
4249
+ this.readyState = 3;
4250
+ this.socket.destroy();
4251
+ }
4252
+ sendFrame(opcode, payload) {
4253
+ if (this.socket.destroyed)
4254
+ return;
4255
+ const len = payload.length;
4256
+ let header;
4257
+ if (len <= 125) {
4258
+ header = Buffer.allocUnsafe(2);
4259
+ header[0] = 128 | opcode & 15;
4260
+ header[1] = len;
4261
+ } else if (len <= 65535) {
4262
+ header = Buffer.allocUnsafe(4);
4263
+ header[0] = 128 | opcode & 15;
4264
+ header[1] = 126;
4265
+ header.writeUInt16BE(len, 2);
4266
+ } else {
4267
+ header = Buffer.allocUnsafe(10);
4268
+ header[0] = 128 | opcode & 15;
4269
+ header[1] = 127;
4270
+ header.writeBigUInt64BE(BigInt(len), 2);
4271
+ }
4272
+ this.socket.write(Buffer.concat([header, payload]));
4273
+ }
4274
+ onData(chunk) {
4275
+ this.buffer = this.buffer.length === 0 ? chunk : Buffer.concat([this.buffer, chunk]);
4276
+ while (this.buffer.length >= 2) {
4277
+ const byte0 = this.buffer[0];
4278
+ const byte1 = this.buffer[1];
4279
+ const fin = (byte0 & 128) !== 0;
4280
+ const opcode = byte0 & 15;
4281
+ const masked = (byte1 & 128) !== 0;
4282
+ if (!masked) {
4283
+ this.close(1002, "Protocol error: unmasked client frame");
4284
+ this.onClose(1002, "Protocol error: unmasked client frame");
4285
+ return;
4286
+ }
4287
+ let payloadLen = byte1 & 127;
4288
+ let offset = 2;
4289
+ if (payloadLen === 126) {
4290
+ if (this.buffer.length < 4)
4291
+ return;
4292
+ payloadLen = this.buffer.readUInt16BE(2);
4293
+ offset = 4;
4294
+ } else if (payloadLen === 127) {
4295
+ if (this.buffer.length < 10)
4296
+ return;
4297
+ const high = this.buffer.readUInt32BE(2);
4298
+ const low = this.buffer.readUInt32BE(6);
4299
+ payloadLen = high * 4294967296 + low;
4300
+ offset = 10;
4301
+ }
4302
+ if (payloadLen > this.maxPayloadLength) {
4303
+ this.close(1009, "Message Too Big");
4304
+ return;
4305
+ }
4306
+ const maskLen = 4;
4307
+ const totalLen = offset + maskLen + payloadLen;
4308
+ if (this.buffer.length < totalLen)
4309
+ return;
4310
+ const maskKey = this.buffer.subarray(offset, offset + 4);
4311
+ const rawPayload = this.buffer.subarray(offset + maskLen, totalLen);
4312
+ const payload = Buffer.allocUnsafe(payloadLen);
4313
+ for (let i = 0;i < payloadLen; i++) {
4314
+ payload[i] = rawPayload[i] ^ maskKey[i & 3];
4315
+ }
4316
+ this.buffer = this.buffer.subarray(totalLen);
4317
+ if (opcode === 8) {
4318
+ let code = 1000;
4319
+ let reason = "";
4320
+ if (payload.length >= 2) {
4321
+ code = payload.readUInt16BE(0);
4322
+ if (payload.length > 2) {
4323
+ reason = payload.subarray(2).toString("utf8");
4324
+ }
4325
+ }
4326
+ if (this.readyState === 1) {
4327
+ this.close(code, reason);
4328
+ }
4329
+ this.onClose(code, reason);
4330
+ return;
4331
+ }
4332
+ if (opcode === 9) {
4333
+ this.sendFrame(10, payload);
4334
+ continue;
4335
+ }
4336
+ if (opcode === 10) {
4337
+ continue;
4338
+ }
4339
+ if (!fin) {
4340
+ if (opcode !== 0) {
4341
+ this.fragmentOpcode = opcode;
4342
+ this.fragments = [payload];
4343
+ } else {
4344
+ this.fragments.push(payload);
4345
+ }
4346
+ continue;
4347
+ }
4348
+ let fullPayload = payload;
4349
+ let effectiveOpcode = opcode;
4350
+ if (opcode === 0) {
4351
+ this.fragments.push(payload);
4352
+ fullPayload = Buffer.concat(this.fragments);
4353
+ effectiveOpcode = this.fragmentOpcode;
4354
+ this.fragments = [];
4355
+ this.fragmentOpcode = 0;
4356
+ }
4357
+ const message = effectiveOpcode === 1 ? fullPayload.toString("utf8") : fullPayload;
4358
+ this.onMessage(message);
4359
+ }
4360
+ }
4361
+ dispatchOpen() {
4362
+ try {
4363
+ this.handlers?.open?.(this);
4364
+ this.appHandlers?.open?.(this);
4365
+ } catch (err) {
4366
+ this.onError(err);
4367
+ }
4368
+ }
4369
+ onMessage(msg) {
4370
+ try {
4371
+ this.handlers?.message?.(this, msg);
4372
+ this.appHandlers?.message?.(this, msg);
4373
+ } catch (err) {
4374
+ this.onError(err);
4375
+ }
4376
+ }
4377
+ onClose(code, reason) {
4378
+ if (this.readyState === 3)
4379
+ return;
4380
+ this.readyState = 3;
4381
+ for (const room of this.topics) {
4382
+ const set = nodeTopics.get(room);
4383
+ if (set) {
4384
+ set.delete(this);
4385
+ if (set.size === 0)
4386
+ nodeTopics.delete(room);
4387
+ }
4388
+ }
4389
+ this.topics.clear();
4390
+ try {
4391
+ this.handlers?.close?.(this, code, reason);
4392
+ this.appHandlers?.close?.(this, code, reason);
4393
+ } catch (err) {
4394
+ this.onError(err);
4395
+ }
4396
+ }
4397
+ onError(err) {
4398
+ try {
4399
+ if (this.handlers?.error) {
4400
+ this.handlers.error(this, err);
4401
+ } else if (this.appHandlers?.error) {
4402
+ this.appHandlers.error(this, err);
4403
+ }
4404
+ } catch (e) {
4405
+ console.error("Unhandled WebSocket error:", e);
4406
+ }
4407
+ }
4408
+ onDrain() {
4409
+ try {
4410
+ this.handlers?.drain?.(this);
4411
+ this.appHandlers?.drain?.(this);
4412
+ } catch (err) {
4413
+ this.onError(err);
4414
+ }
4415
+ }
4416
+ }
4417
+ function resolveTlsData(input) {
4418
+ if (!input)
4419
+ return;
4420
+ if (Buffer.isBuffer(input) || typeof input === "string") {
4421
+ if (typeof input === "string" && !input.includes(`
4422
+ `) && (input.startsWith("./") || input.startsWith("/") || input.includes("."))) {
4423
+ try {
4424
+ if (fs.existsSync(input)) {
4425
+ return fs.readFileSync(input);
4426
+ }
4427
+ } catch {}
4428
+ }
4429
+ return input;
4430
+ }
4431
+ return;
4432
+ }
4099
4433
  if (isNode) {
4100
4434
  if (typeof globalThis.Bun === "undefined") {
4101
4435
  Object.defineProperty(globalThis, "Bun", {
@@ -4119,15 +4453,59 @@ if (isNode) {
4119
4453
  });
4120
4454
  }
4121
4455
  App.prototype.listen = function(port) {
4122
- const rawPort = port ?? this["_options"]?.port ?? 3000;
4123
- const hostname = this["_options"]?.hostname ?? "0.0.0.0";
4124
- const listenPort = rawPort === 0 ? Math.floor(Math.random() * 20000) + 30000 : rawPort;
4125
- const server = http.createServer(async (req, res) => {
4456
+ const app = this;
4457
+ const rawPort = port ?? app._options?.port ?? 3000;
4458
+ const hostname = app._options?.hostname ?? "0.0.0.0";
4459
+ const activeSockets = new Set;
4460
+ let inFlightRequests = 0;
4461
+ let isDraining = false;
4462
+ const fastRoutes = new Map;
4463
+ if (app.nativeRoutes && !app.hasMiddlewares && app.errorResolver === undefined) {
4464
+ for (const nr of app.nativeRoutes) {
4465
+ if (!nr.isDynamic && nr.isContextFree && nr.rawHandler) {
4466
+ fastRoutes.set(`${nr.method}:${nr.pattern}`, nr.rawHandler);
4467
+ }
4468
+ }
4469
+ }
4470
+ const requestListener = async (req, res) => {
4471
+ inFlightRequests++;
4472
+ if (isDraining) {
4473
+ res.setHeader("Connection", "close");
4474
+ }
4475
+ const onFinished = () => {
4476
+ inFlightRequests--;
4477
+ res.removeListener("finish", onFinished);
4478
+ res.removeListener("close", onFinished);
4479
+ };
4480
+ res.on("finish", onFinished);
4481
+ res.on("close", onFinished);
4482
+ const method = req.method || "GET";
4483
+ const rawUrl = req.url || "/";
4484
+ const pathname = scanPathname(rawUrl);
4485
+ const fastHandler = fastRoutes.get(`${method}:${pathname}`);
4486
+ if (fastHandler) {
4487
+ try {
4488
+ const val = fastHandler();
4489
+ if (typeof val === "string") {
4490
+ res.writeHead(200, {
4491
+ "content-type": "text/plain; charset=utf-8"
4492
+ });
4493
+ res.end(val);
4494
+ return;
4495
+ }
4496
+ if (typeof val === "object" && val !== null && !(val instanceof Response)) {
4497
+ res.writeHead(200, {
4498
+ "content-type": "application/json; charset=utf-8"
4499
+ });
4500
+ res.end(JSON.stringify(val));
4501
+ return;
4502
+ }
4503
+ } catch {}
4504
+ }
4126
4505
  try {
4127
4506
  const protocol = req.socket?.encrypted ? "https" : "http";
4128
4507
  const host = req.headers.host || "localhost";
4129
- const url = `${protocol}://${host}${req.url || "/"}`;
4130
- const method = req.method || "GET";
4508
+ const url = `${protocol}://${host}${rawUrl}`;
4131
4509
  let reqBody;
4132
4510
  if (method !== "GET" && method !== "HEAD") {
4133
4511
  reqBody = Readable.toWeb(req);
@@ -4139,40 +4517,228 @@ if (isNode) {
4139
4517
  duplex: "half"
4140
4518
  });
4141
4519
  const remoteIp = req.socket.remoteAddress || "";
4142
- const webRes = await this["handle"](webReq, {
4143
- requestIP: () => remoteIp ? { address: remoteIp } : null
4144
- });
4145
- res.statusCode = webRes.status;
4146
- res.statusMessage = webRes.statusText;
4520
+ let upgraded = false;
4521
+ const serverShim = {
4522
+ requestIP: () => remoteIp ? { address: remoteIp } : null,
4523
+ upgrade: (_request, options) => {
4524
+ const secKey = req.headers["sec-websocket-key"];
4525
+ if (!secKey || typeof secKey !== "string")
4526
+ return false;
4527
+ const acceptKey = crypto.createHash("sha1").update(secKey + WS_GUID).digest("base64");
4528
+ req.socket.write(`HTTP/1.1 101 Switching Protocols\r
4529
+ ` + `Upgrade: websocket\r
4530
+ ` + `Connection: Upgrade\r
4531
+ ` + `Sec-WebSocket-Accept: ${acceptKey}\r
4532
+ \r
4533
+ `);
4534
+ const ws = new NodeServerWebSocket(req.socket, options?.data, app._options?.websocket);
4535
+ upgraded = true;
4536
+ ws.dispatchOpen();
4537
+ return true;
4538
+ }
4539
+ };
4540
+ const webRes = await app.handle(webReq, serverShim);
4541
+ if (upgraded)
4542
+ return;
4543
+ const resHeaders = {};
4147
4544
  webRes.headers.forEach((val, key) => {
4148
- res.setHeader(key, val);
4545
+ resHeaders[key] = val;
4149
4546
  });
4150
- if (!webRes.body) {
4547
+ const getSetCookie = webRes.headers.getSetCookie;
4548
+ if (typeof getSetCookie === "function") {
4549
+ const cookies = getSetCookie.call(webRes.headers);
4550
+ if (cookies && cookies.length > 0) {
4551
+ resHeaders["set-cookie"] = cookies;
4552
+ }
4553
+ }
4554
+ res.writeHead(webRes.status, webRes.statusText || undefined, resHeaders);
4555
+ if (webRes.body) {
4556
+ const nodeStream = Readable.fromWeb(webRes.body);
4557
+ nodeStream.pipe(res);
4558
+ } else {
4151
4559
  res.end();
4152
- return;
4153
4560
  }
4154
- const nodeStream = Readable.fromWeb(webRes.body);
4155
- nodeStream.pipe(res);
4156
4561
  } catch (err) {
4157
4562
  console.error("Barq Node Adapter Error:", err);
4158
4563
  if (!res.headersSent) {
4159
- res.statusCode = 500;
4160
- res.setHeader("Content-Type", "application/json");
4564
+ res.writeHead(500, {
4565
+ "content-type": "application/json; charset=utf-8"
4566
+ });
4161
4567
  res.end('{"error":"Internal Server Error"}');
4162
4568
  }
4163
4569
  }
4570
+ };
4571
+ const tlsConfig = app._options?.tls;
4572
+ const hasTls = Boolean(tlsConfig && (tlsConfig.key || tlsConfig.cert));
4573
+ const server = hasTls ? https.createServer({
4574
+ key: resolveTlsData(tlsConfig?.key),
4575
+ cert: resolveTlsData(tlsConfig?.cert),
4576
+ ca: resolveTlsData(tlsConfig?.ca),
4577
+ passphrase: tlsConfig?.passphrase
4578
+ }, requestListener) : http.createServer(requestListener);
4579
+ server.on("connection", (socket) => {
4580
+ activeSockets.add(socket);
4581
+ socket.on("close", () => activeSockets.delete(socket));
4164
4582
  });
4165
- server.listen(listenPort, hostname);
4166
- return {
4167
- port: listenPort,
4583
+ server.on("upgrade", async (req, socket, head) => {
4584
+ activeSockets.add(socket);
4585
+ socket.on("close", () => activeSockets.delete(socket));
4586
+ const protocol = socket?.encrypted ? "https" : "http";
4587
+ const host = req.headers.host || "localhost";
4588
+ const fullUrl = `${protocol}://${host}${req.url || "/"}`;
4589
+ let upgradedSocket = null;
4590
+ const serverShim = {
4591
+ requestIP: () => ({
4592
+ address: ("remoteAddress" in socket ? socket.remoteAddress : "") || ""
4593
+ }),
4594
+ upgrade: (_request, options) => {
4595
+ const secKey = req.headers["sec-websocket-key"];
4596
+ if (!secKey || typeof secKey !== "string")
4597
+ return false;
4598
+ const acceptKey = crypto.createHash("sha1").update(secKey + WS_GUID).digest("base64");
4599
+ socket.write(`HTTP/1.1 101 Switching Protocols\r
4600
+ ` + `Upgrade: websocket\r
4601
+ ` + `Connection: Upgrade\r
4602
+ ` + `Sec-WebSocket-Accept: ${acceptKey}\r
4603
+ \r
4604
+ `);
4605
+ const ws = new NodeServerWebSocket(socket, options?.data, app._options?.websocket);
4606
+ upgradedSocket = ws;
4607
+ if (head && head.length > 0) {
4608
+ socket.unshift(head);
4609
+ }
4610
+ ws.dispatchOpen();
4611
+ return true;
4612
+ }
4613
+ };
4614
+ const webReq = new Request(fullUrl, {
4615
+ method: "GET",
4616
+ headers: req.headers
4617
+ });
4618
+ try {
4619
+ const webRes = await app.handle(webReq, serverShim);
4620
+ if (!upgradedSocket) {
4621
+ const status = webRes.status || 400;
4622
+ const statusText = webRes.statusText || "Bad Request";
4623
+ socket.write(`HTTP/1.1 ${status} ${statusText}\r
4624
+ Connection: close\r
4625
+ \r
4626
+ `);
4627
+ socket.destroy();
4628
+ }
4629
+ } catch {
4630
+ if (!upgradedSocket) {
4631
+ socket.write(`HTTP/1.1 500 Internal Server Error\r
4632
+ Connection: close\r
4633
+ \r
4634
+ `);
4635
+ socket.destroy();
4636
+ }
4637
+ }
4638
+ });
4639
+ server.listen(rawPort, hostname);
4640
+ const serverHandle = {
4641
+ get port() {
4642
+ const addr = server.address();
4643
+ return addr && typeof addr === "object" ? addr.port : rawPort;
4644
+ },
4168
4645
  hostname,
4169
- stop: () => new Promise((resolve) => {
4170
- server.close(() => resolve());
4171
- if (typeof server.closeAllConnections === "function") {
4172
- server.closeAllConnections();
4646
+ stop: (hardTimeoutMs) => new Promise((resolve) => {
4647
+ if (hardTimeoutMs === true) {
4648
+ for (const socket of activeSockets) {
4649
+ socket.destroy();
4650
+ }
4651
+ activeSockets.clear();
4652
+ server.close(() => resolve({ forced: true, pending: 0 }));
4653
+ return;
4654
+ }
4655
+ isDraining = true;
4656
+ const timeout = typeof hardTimeoutMs === "number" ? hardTimeoutMs : 5000;
4657
+ let timer;
4658
+ let resolved = false;
4659
+ const done = (forced) => {
4660
+ if (resolved)
4661
+ return;
4662
+ resolved = true;
4663
+ if (timer !== undefined)
4664
+ clearTimeout(timer);
4665
+ const pending = activeSockets.size;
4666
+ if (forced) {
4667
+ for (const socket of activeSockets) {
4668
+ socket.destroy();
4669
+ }
4670
+ activeSockets.clear();
4671
+ }
4672
+ server.close(() => resolve({ forced, pending: forced ? pending : 0 }));
4673
+ };
4674
+ timer = setTimeout(() => done(true), timeout);
4675
+ if (typeof timer.unref === "function")
4676
+ timer.unref();
4677
+ server.close(() => {
4678
+ if (!resolved) {
4679
+ resolved = true;
4680
+ if (timer !== undefined)
4681
+ clearTimeout(timer);
4682
+ resolve({ forced: false, pending: 0 });
4683
+ }
4684
+ });
4685
+ if (inFlightRequests === 0) {
4686
+ done(false);
4173
4687
  }
4174
4688
  })
4175
4689
  };
4690
+ return serverHandle;
4691
+ };
4692
+ App.prototype.cluster = function(port) {
4693
+ const app = this;
4694
+ const rawPort = port ?? app._options?.port ?? 3000;
4695
+ const hostname = app._options?.hostname ?? "0.0.0.0";
4696
+ const listenPort = rawPort === 0 ? 3000 : rawPort;
4697
+ if (cluster.isPrimary) {
4698
+ const numCPUs = cpus2().length;
4699
+ const workers = [];
4700
+ let crashCount = 0;
4701
+ let lastReset = Date.now();
4702
+ const spawnWorker = () => {
4703
+ const worker = cluster.fork({
4704
+ ...process.env,
4705
+ PORT: String(listenPort)
4706
+ });
4707
+ worker.on("exit", () => {
4708
+ const now = Date.now();
4709
+ if (now - lastReset > 5000) {
4710
+ crashCount = 0;
4711
+ lastReset = now;
4712
+ }
4713
+ crashCount++;
4714
+ const idx = workers.indexOf(worker);
4715
+ if (idx !== -1)
4716
+ workers.splice(idx, 1);
4717
+ if (crashCount > 15) {
4718
+ console.error("[Barq Cluster] Node workers restarting too fast. Throttling respawn.");
4719
+ setTimeout(() => {
4720
+ workers.push(spawnWorker());
4721
+ }, 2000);
4722
+ return;
4723
+ }
4724
+ workers.push(spawnWorker());
4725
+ });
4726
+ return worker;
4727
+ };
4728
+ for (let i = 0;i < numCPUs; i++) {
4729
+ workers.push(spawnWorker());
4730
+ }
4731
+ return {
4732
+ port: listenPort,
4733
+ hostname,
4734
+ stop: async () => {
4735
+ for (const w of workers) {
4736
+ w.kill("SIGTERM");
4737
+ }
4738
+ }
4739
+ };
4740
+ }
4741
+ return this.listen(listenPort);
4176
4742
  };
4177
4743
  }
4178
4744
  // src/cors.ts
@@ -4301,4 +4867,4 @@ export {
4301
4867
  validateJson
4302
4868
  };
4303
4869
 
4304
- //# debugId=488AD569EC96D0E864756E2164756E21
4870
+ //# debugId=883AC01877895D9164756E2164756E21