@hyperspan/framework 0.4.4 → 0.5.0

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.
@@ -78,7 +78,7 @@ var mergeBuffers = (buffer1, buffer2) => {
78
78
  if (!buffer1) {
79
79
  return buffer2;
80
80
  }
81
- const merged = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
81
+ const merged = new Uint8Array(new ArrayBuffer(buffer1.byteLength + buffer2.byteLength));
82
82
  merged.set(new Uint8Array(buffer1), 0);
83
83
  merged.set(buffer2, buffer1.byteLength);
84
84
  return merged;
@@ -111,8 +111,9 @@ var RETAINED_304_HEADERS = [
111
111
  "expires",
112
112
  "vary"
113
113
  ];
114
+ var stripWeak = (tag) => tag.replace(/^W\//, "");
114
115
  function etagMatches(etag2, ifNoneMatch) {
115
- return ifNoneMatch != null && ifNoneMatch.split(/,\s*/).indexOf(etag2) > -1;
116
+ return ifNoneMatch != null && ifNoneMatch.split(/,\s*/).some((t) => stripWeak(t) === stripWeak(etag2));
116
117
  }
117
118
  function initializeGenerator(generator) {
118
119
  if (!generator) {
package/dist/server.js CHANGED
@@ -74,10 +74,10 @@ export const __CLIENT_JS = {
74
74
  // src/server.ts
75
75
  import { html, isHSHtml, renderStream, renderAsync, render } from "@hyperspan/html";
76
76
  import { readdir } from "node:fs/promises";
77
- import { basename, extname, join } from "node:path";
77
+ import { basename, extname, join as join2 } from "node:path";
78
78
 
79
79
  // ../../node_modules/isbot/index.mjs
80
- var fullPattern = " daum[ /]| deusu/| yadirectfetcher|(?:^|[^g])news(?!sapphire)|(?<! (?:channel/|google/))google(?!(app|/google| pixel))|(?<! cu)bots?(?:\\b|_)|(?<!(?:lib))http|(?<![hg]m)score|(?<!cam)scan|@[a-z][\\w-]+\\.|\\(\\)|\\.com\\b|\\btime/|\\||^<|^[\\w \\.\\-\\(?:\\):%]+(?:/v?\\d+(?:\\.\\d+)?(?:\\.\\d{1,10})*?)?(?:,|$)|^[^ ]{50,}$|^\\d+\\b|^\\w*search\\b|^\\w+/[\\w\\(\\)]*$|^active|^ad muncher|^amaya|^avsdevicesdk/|^biglotron|^bot|^bw/|^clamav[ /]|^client/|^cobweb/|^custom|^ddg[_-]android|^discourse|^dispatch/\\d|^downcast/|^duckduckgo|^email|^facebook|^getright/|^gozilla/|^hobbit|^hotzonu|^hwcdn/|^igetter/|^jeode/|^jetty/|^jigsaw|^microsoft bits|^movabletype|^mozilla/\\d\\.\\d\\s[\\w\\.-]+$|^mozilla/\\d\\.\\d\\s\\(compatible;?(?:\\s\\w+\\/\\d+\\.\\d+)?\\)$|^navermailapp|^netsurf|^offline|^openai/|^owler|^php|^postman|^python|^rank|^read|^reed|^rest|^rss|^snapchat|^space bison|^svn|^swcd |^taringa|^thumbor/|^track|^w3c|^webbandit/|^webcopier|^wget|^whatsapp|^wordpress|^xenu link sleuth|^yahoo|^yandex|^zdm/\\d|^zoom marketplace/|^{{.*}}$|analyzer|archive|ask jeeves/teoma|audit|bit\\.ly/|bluecoat drtr|browsex|burpcollaborator|capture|catch|check\\b|checker|chrome-lighthouse|chromeframe|classifier|cloudflare|convertify|crawl|cypress/|dareboost|datanyze|dejaclick|detect|dmbrowser|download|evc-batch/|exaleadcloudview|feed|firephp|functionize|gomezagent|grab|headless|httrack|hubspot marketing grader|hydra|ibisbrowser|infrawatch|insight|inspect|iplabel|ips-agent|java(?!;)|library|linkcheck|mail\\.ru/|manager|measure|neustar wpm|node|nutch|offbyone|onetrust|optimize|pageburst|pagespeed|parser|perl|phantomjs|pingdom|powermarks|preview|proxy|ptst[ /]\\d|retriever|rexx;|rigor|rss\\b|scrape|server|sogou|sparkler/|speedcurve|spider|splash|statuscake|supercleaner|synapse|synthetic|tools|torrent|transcoder|url|validator|virtuoso|wappalyzer|webglance|webkit2png|whatcms/|xtate/";
80
+ var fullPattern = " daum[ /]| deusu/|(?:^|[^g])news(?!sapphire)|(?<! (?:channel/|google/))google(?!(app|/google| pixel))|(?<! cu)bots?(?:\\b|_)|(?<!(?:lib))http|(?<![hg]m)score|(?<!cam)scan|@[a-z][\\w-]+\\.|\\(\\)|\\.com\\b|\\btime/|\\||^<|^[\\w \\.\\-\\(?:\\):%]+(?:/v?\\d+(?:\\.\\d+)?(?:\\.\\d{1,10})*?)?(?:,|$)|^[^ ]{50,}$|^\\d+\\b|^\\w*search\\b|^\\w+/[\\w\\(\\)]*$|^active|^ad muncher|^amaya|^avsdevicesdk/|^azure|^biglotron|^bot|^bw/|^clamav[ /]|^client/|^cobweb/|^custom|^ddg[_-]android|^discourse|^dispatch/\\d|^downcast/|^duckduckgo|^email|^facebook|^getright/|^gozilla/|^hobbit|^hotzonu|^hwcdn/|^igetter/|^jeode/|^jetty/|^jigsaw|^microsoft bits|^movabletype|^mozilla/\\d\\.\\d\\s[\\w\\.-]+$|^mozilla/\\d\\.\\d\\s\\(compatible;?(?:\\s\\w+\\/\\d+\\.\\d+)?\\)$|^navermailapp|^netsurf|^offline|^openai/|^owler|^php|^postman|^python|^rank|^read|^reed|^rest|^rss|^snapchat|^space bison|^svn|^swcd |^taringa|^thumbor/|^track|^w3c|^webbandit/|^webcopier|^wget|^whatsapp|^wordpress|^xenu link sleuth|^yahoo|^yandex|^zdm/\\d|^zoom marketplace/|agent|analyzer|archive|ask jeeves/teoma|audit|bit\\.ly/|bluecoat drtr|browsex|burpcollaborator|capture|catch|check\\b|checker|chrome-lighthouse|chromeframe|classifier|cloudflare|convertify|crawl|cypress/|dareboost|datanyze|dejaclick|detect|dmbrowser|download|evc-batch/|exaleadcloudview|feed|fetcher|firephp|functionize|grab|headless|httrack|hubspot marketing grader|hydra|ibisbrowser|infrawatch|insight|inspect|iplabel|java(?!;)|library|linkcheck|mail\\.ru/|manager|measure|neustar wpm|node|nutch|offbyone|onetrust|optimize|pageburst|pagespeed|parser|perl|phantomjs|pingdom|powermarks|preview|proxy|ptst[ /]\\d|retriever|rexx;|rigor|rss\\b|scrape|server|sogou|sparkler/|speedcurve|spider|splash|statuscake|supercleaner|synapse|synthetic|tools|torrent|transcoder|url|validator|virtuoso|wappalyzer|webglance|webkit2png|whatcms/|xtate/";
81
81
  var naivePattern = /bot|crawl|http|lighthouse|scan|search|spider/i;
82
82
  var pattern;
83
83
  function getPattern() {
@@ -139,6 +139,9 @@ var compose = (middleware, onError, onNotFound) => {
139
139
  };
140
140
  };
141
141
 
142
+ // ../../node_modules/hono/dist/request/constants.js
143
+ var GET_MATCH_RESULT = Symbol();
144
+
142
145
  // ../../node_modules/hono/dist/utils/body.js
143
146
  var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
144
147
  const { all = false, dot = false } = options;
@@ -185,7 +188,11 @@ var handleParsingAllValues = (form, key, value) => {
185
188
  form[key] = [form[key], value];
186
189
  }
187
190
  } else {
188
- form[key] = value;
191
+ if (!key.endsWith("[]")) {
192
+ form[key] = value;
193
+ } else {
194
+ form[key] = [value];
195
+ }
189
196
  }
190
197
  };
191
198
  var handleParsingNestedValues = (form, key, value) => {
@@ -272,7 +279,7 @@ var tryDecode = (str, decoder) => {
272
279
  var tryDecodeURI = (str) => tryDecode(str, decodeURI);
273
280
  var getPath = (request) => {
274
281
  const url = request.url;
275
- const start = url.indexOf("/", 8);
282
+ const start = url.indexOf("/", url.charCodeAt(9) === 58 ? 13 : 8);
276
283
  let i = start;
277
284
  for (;i < url.length; i++) {
278
285
  const charCode = url.charCodeAt(i);
@@ -330,7 +337,7 @@ var _decodeURI = (value) => {
330
337
  if (value.indexOf("+") !== -1) {
331
338
  value = value.replace(/\+/g, " ");
332
339
  }
333
- return value.indexOf("%") !== -1 ? decodeURIComponent_(value) : value;
340
+ return value.indexOf("%") !== -1 ? tryDecode(value, decodeURIComponent_) : value;
334
341
  };
335
342
  var _getQueryParam = (url, key, multiple) => {
336
343
  let encoded;
@@ -472,7 +479,7 @@ var HonoRequest = class {
472
479
  return bodyCache[key] = raw[key]();
473
480
  };
474
481
  json() {
475
- return this.#cachedBody("json");
482
+ return this.#cachedBody("text").then((text) => JSON.parse(text));
476
483
  }
477
484
  text() {
478
485
  return this.#cachedBody("text");
@@ -498,6 +505,9 @@ var HonoRequest = class {
498
505
  get method() {
499
506
  return this.raw.method;
500
507
  }
508
+ get [GET_MATCH_RESULT]() {
509
+ return this.#matchResult;
510
+ }
501
511
  get matchedRoutes() {
502
512
  return this.#matchResult[0].map(([[, route]]) => route);
503
513
  }
@@ -546,11 +556,11 @@ var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) =>
546
556
 
547
557
  // ../../node_modules/hono/dist/context.js
548
558
  var TEXT_PLAIN = "text/plain; charset=UTF-8";
549
- var setHeaders = (headers, map = {}) => {
550
- for (const key of Object.keys(map)) {
551
- headers.set(key, map[key]);
552
- }
553
- return headers;
559
+ var setDefaultContentType = (contentType, headers) => {
560
+ return {
561
+ "Content-Type": contentType,
562
+ ...headers
563
+ };
554
564
  };
555
565
  var Context = class {
556
566
  #rawRequest;
@@ -559,15 +569,13 @@ var Context = class {
559
569
  #var;
560
570
  finalized = false;
561
571
  error;
562
- #status = 200;
572
+ #status;
563
573
  #executionCtx;
564
- #headers;
565
- #preparedHeaders;
566
574
  #res;
567
- #isFresh = true;
568
575
  #layout;
569
576
  #renderer;
570
577
  #notFoundHandler;
578
+ #preparedHeaders;
571
579
  #matchResult;
572
580
  #path;
573
581
  constructor(req, options) {
@@ -599,11 +607,11 @@ var Context = class {
599
607
  }
600
608
  }
601
609
  get res() {
602
- this.#isFresh = false;
603
- return this.#res ||= new Response("404 Not Found", { status: 404 });
610
+ return this.#res ||= new Response(null, {
611
+ headers: this.#preparedHeaders ??= new Headers
612
+ });
604
613
  }
605
614
  set res(_res) {
606
- this.#isFresh = false;
607
615
  if (this.#res && _res) {
608
616
  _res = new Response(_res.body, _res);
609
617
  for (const [k, v] of this.#res.headers.entries()) {
@@ -637,42 +645,16 @@ var Context = class {
637
645
  if (this.finalized) {
638
646
  this.#res = new Response(this.#res.body, this.#res);
639
647
  }
648
+ const headers = this.#res ? this.#res.headers : this.#preparedHeaders ??= new Headers;
640
649
  if (value === undefined) {
641
- if (this.#headers) {
642
- this.#headers.delete(name);
643
- } else if (this.#preparedHeaders) {
644
- delete this.#preparedHeaders[name.toLocaleLowerCase()];
645
- }
646
- if (this.finalized) {
647
- this.res.headers.delete(name);
648
- }
649
- return;
650
- }
651
- if (options?.append) {
652
- if (!this.#headers) {
653
- this.#isFresh = false;
654
- this.#headers = new Headers(this.#preparedHeaders);
655
- this.#preparedHeaders = {};
656
- }
657
- this.#headers.append(name, value);
650
+ headers.delete(name);
651
+ } else if (options?.append) {
652
+ headers.append(name, value);
658
653
  } else {
659
- if (this.#headers) {
660
- this.#headers.set(name, value);
661
- } else {
662
- this.#preparedHeaders ??= {};
663
- this.#preparedHeaders[name.toLowerCase()] = value;
664
- }
665
- }
666
- if (this.finalized) {
667
- if (options?.append) {
668
- this.res.headers.append(name, value);
669
- } else {
670
- this.res.headers.set(name, value);
671
- }
654
+ headers.set(name, value);
672
655
  }
673
656
  };
674
657
  status = (status) => {
675
- this.#isFresh = false;
676
658
  this.#status = status;
677
659
  };
678
660
  set = (key, value) => {
@@ -689,94 +671,47 @@ var Context = class {
689
671
  return Object.fromEntries(this.#var);
690
672
  }
691
673
  #newResponse(data, arg, headers) {
692
- if (this.#isFresh && !headers && !arg && this.#status === 200) {
693
- return new Response(data, {
694
- headers: this.#preparedHeaders
695
- });
696
- }
697
- if (arg && typeof arg !== "number") {
698
- const header = new Headers(arg.headers);
699
- if (this.#headers) {
700
- this.#headers.forEach((v, k) => {
701
- if (k === "set-cookie") {
702
- header.append(k, v);
703
- } else {
704
- header.set(k, v);
705
- }
706
- });
707
- }
708
- const headers2 = setHeaders(header, this.#preparedHeaders);
709
- return new Response(data, {
710
- headers: headers2,
711
- status: arg.status ?? this.#status
712
- });
713
- }
714
- const status = typeof arg === "number" ? arg : this.#status;
715
- this.#preparedHeaders ??= {};
716
- this.#headers ??= new Headers;
717
- setHeaders(this.#headers, this.#preparedHeaders);
718
- if (this.#res) {
719
- this.#res.headers.forEach((v, k) => {
720
- if (k === "set-cookie") {
721
- this.#headers?.append(k, v);
674
+ const responseHeaders = this.#res ? new Headers(this.#res.headers) : this.#preparedHeaders ?? new Headers;
675
+ if (typeof arg === "object" && "headers" in arg) {
676
+ const argHeaders = arg.headers instanceof Headers ? arg.headers : new Headers(arg.headers);
677
+ for (const [key, value] of argHeaders) {
678
+ if (key.toLowerCase() === "set-cookie") {
679
+ responseHeaders.append(key, value);
722
680
  } else {
723
- this.#headers?.set(k, v);
681
+ responseHeaders.set(key, value);
724
682
  }
725
- });
726
- setHeaders(this.#headers, this.#preparedHeaders);
683
+ }
727
684
  }
728
- headers ??= {};
729
- for (const [k, v] of Object.entries(headers)) {
730
- if (typeof v === "string") {
731
- this.#headers.set(k, v);
732
- } else {
733
- this.#headers.delete(k);
734
- for (const v2 of v) {
735
- this.#headers.append(k, v2);
685
+ if (headers) {
686
+ for (const [k, v] of Object.entries(headers)) {
687
+ if (typeof v === "string") {
688
+ responseHeaders.set(k, v);
689
+ } else {
690
+ responseHeaders.delete(k);
691
+ for (const v2 of v) {
692
+ responseHeaders.append(k, v2);
693
+ }
736
694
  }
737
695
  }
738
696
  }
739
- return new Response(data, {
740
- status,
741
- headers: this.#headers
742
- });
697
+ const status = typeof arg === "number" ? arg : arg?.status ?? this.#status;
698
+ return new Response(data, { status, headers: responseHeaders });
743
699
  }
744
700
  newResponse = (...args) => this.#newResponse(...args);
745
- body = (data, arg, headers) => {
746
- return typeof arg === "number" ? this.#newResponse(data, arg, headers) : this.#newResponse(data, arg);
747
- };
701
+ body = (data, arg, headers) => this.#newResponse(data, arg, headers);
748
702
  text = (text, arg, headers) => {
749
- if (!this.#preparedHeaders) {
750
- if (this.#isFresh && !headers && !arg) {
751
- return new Response(text);
752
- }
753
- this.#preparedHeaders = {};
754
- }
755
- this.#preparedHeaders["content-type"] = TEXT_PLAIN;
756
- if (typeof arg === "number") {
757
- return this.#newResponse(text, arg, headers);
758
- }
759
- return this.#newResponse(text, arg);
703
+ return !this.#preparedHeaders && !this.#status && !arg && !headers && !this.finalized ? new Response(text) : this.#newResponse(text, arg, setDefaultContentType(TEXT_PLAIN, headers));
760
704
  };
761
705
  json = (object, arg, headers) => {
762
- const body = JSON.stringify(object);
763
- this.#preparedHeaders ??= {};
764
- this.#preparedHeaders["content-type"] = "application/json";
765
- return typeof arg === "number" ? this.#newResponse(body, arg, headers) : this.#newResponse(body, arg);
706
+ return this.#newResponse(JSON.stringify(object), arg, setDefaultContentType("application/json", headers));
766
707
  };
767
708
  html = (html, arg, headers) => {
768
- this.#preparedHeaders ??= {};
769
- this.#preparedHeaders["content-type"] = "text/html; charset=UTF-8";
770
- if (typeof html === "object") {
771
- return resolveCallback(html, HtmlEscapedCallbackPhase.Stringify, false, {}).then((html2) => {
772
- return typeof arg === "number" ? this.#newResponse(html2, arg, headers) : this.#newResponse(html2, arg);
773
- });
774
- }
775
- return typeof arg === "number" ? this.#newResponse(html, arg, headers) : this.#newResponse(html, arg);
709
+ const res = (html2) => this.#newResponse(html2, arg, setDefaultContentType("text/html; charset=UTF-8", headers));
710
+ return typeof html === "object" ? resolveCallback(html, HtmlEscapedCallbackPhase.Stringify, false, {}).then(res) : res(html);
776
711
  };
777
712
  redirect = (location, status) => {
778
- this.#headers ??= new Headers;
779
- this.#headers.set("Location", String(location));
713
+ const locationString = String(location);
714
+ this.header("Location", !/[^\x00-\xFF]/.test(locationString) ? locationString : encodeURI(locationString));
780
715
  return this.newResponse(null, status ?? 302);
781
716
  };
782
717
  notFound = () => {
@@ -802,7 +737,8 @@ var notFoundHandler = (c) => {
802
737
  };
803
738
  var errorHandler = (err, c) => {
804
739
  if ("getResponse" in err) {
805
- return err.getResponse();
740
+ const res = err.getResponse();
741
+ return c.newResponse(res.body, res);
806
742
  }
807
743
  console.error(err);
808
744
  return c.text("Internal Server Error", 500);
@@ -950,7 +886,7 @@ var Hono = class {
950
886
  #addRoute(method, path, handler) {
951
887
  method = method.toUpperCase();
952
888
  path = mergePath(this._basePath, path);
953
- const r = { path, method, handler };
889
+ const r = { basePath: this._basePath, path, method, handler };
954
890
  this.router.add(method, path, [handler, r]);
955
891
  this.routes.push(r);
956
892
  }
@@ -1061,6 +997,9 @@ var Node = class {
1061
997
  const name = pattern2[1];
1062
998
  let regexpStr = pattern2[2] || LABEL_REG_EXP_STR;
1063
999
  if (name && pattern2[2]) {
1000
+ if (regexpStr === ".*") {
1001
+ throw PATH_ERROR;
1002
+ }
1064
1003
  regexpStr = regexpStr.replace(/^\((?!\?:)(?=[^)]+\)$)/, "(?:");
1065
1004
  if (/\((?!\?:)/.test(regexpStr)) {
1066
1005
  throw PATH_ERROR;
@@ -1436,11 +1375,10 @@ var Node2 = class {
1436
1375
  const nextP = parts[i + 1];
1437
1376
  const pattern2 = getPattern2(p, nextP);
1438
1377
  const key = Array.isArray(pattern2) ? pattern2[0] : p;
1439
- if (Object.keys(curNode.#children).includes(key)) {
1378
+ if (key in curNode.#children) {
1440
1379
  curNode = curNode.#children[key];
1441
- const pattern22 = getPattern2(p, nextP);
1442
- if (pattern22) {
1443
- possibleKeys.push(pattern22[1]);
1380
+ if (pattern2) {
1381
+ possibleKeys.push(pattern2[1]);
1444
1382
  }
1445
1383
  continue;
1446
1384
  }
@@ -1451,14 +1389,13 @@ var Node2 = class {
1451
1389
  }
1452
1390
  curNode = curNode.#children[key];
1453
1391
  }
1454
- const m = /* @__PURE__ */ Object.create(null);
1455
- const handlerSet = {
1456
- handler,
1457
- possibleKeys: possibleKeys.filter((v, i, a) => a.indexOf(v) === i),
1458
- score: this.#order
1459
- };
1460
- m[method] = handlerSet;
1461
- curNode.#methods.push(m);
1392
+ curNode.#methods.push({
1393
+ [method]: {
1394
+ handler,
1395
+ possibleKeys: possibleKeys.filter((v, i, a) => a.indexOf(v) === i),
1396
+ score: this.#order
1397
+ }
1398
+ });
1462
1399
  return curNode;
1463
1400
  }
1464
1401
  #getHandlerSets(node, method, nodeParams, params) {
@@ -1519,10 +1456,10 @@ var Node2 = class {
1519
1456
  }
1520
1457
  continue;
1521
1458
  }
1522
- if (part === "") {
1459
+ const [key, name, matcher] = pattern2;
1460
+ if (!part && !(matcher instanceof RegExp)) {
1523
1461
  continue;
1524
1462
  }
1525
- const [key, name, matcher] = pattern2;
1526
1463
  const child = node.#children[key];
1527
1464
  const restPathString = parts.slice(i).join("/");
1528
1465
  if (matcher instanceof RegExp) {
@@ -1598,42 +1535,11 @@ var Hono2 = class extends Hono {
1598
1535
 
1599
1536
  // ../../node_modules/hono/dist/adapter/bun/serve-static.js
1600
1537
  import { stat } from "node:fs/promises";
1538
+ import { join } from "node:path";
1601
1539
 
1602
1540
  // ../../node_modules/hono/dist/utils/compress.js
1603
1541
  var COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/(?!event-stream(?:[;\s]|$))[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i;
1604
1542
 
1605
- // ../../node_modules/hono/dist/utils/filepath.js
1606
- var getFilePath = (options) => {
1607
- let filename = options.filename;
1608
- const defaultDocument = options.defaultDocument || "index.html";
1609
- if (filename.endsWith("/")) {
1610
- filename = filename.concat(defaultDocument);
1611
- } else if (!filename.match(/\.[a-zA-Z0-9_-]+$/)) {
1612
- filename = filename.concat("/" + defaultDocument);
1613
- }
1614
- const path = getFilePathWithoutDefaultDocument({
1615
- root: options.root,
1616
- filename
1617
- });
1618
- return path;
1619
- };
1620
- var getFilePathWithoutDefaultDocument = (options) => {
1621
- let root = options.root || "";
1622
- let filename = options.filename;
1623
- if (/(?:^|[\/\\])\.\.(?:$|[\/\\])/.test(filename)) {
1624
- return;
1625
- }
1626
- filename = filename.replace(/^\.?[\/\\]/, "");
1627
- filename = filename.replace(/\\/, "/");
1628
- root = root.replace(/\/$/, "");
1629
- let path = root ? root + "/" + filename : filename;
1630
- path = path.replace(/^\.?\//, "");
1631
- if (root[0] !== "/" && path[0] === "/") {
1632
- return;
1633
- }
1634
- return path;
1635
- };
1636
-
1637
1543
  // ../../node_modules/hono/dist/utils/mime.js
1638
1544
  var getMimeType = (filename, mimes = baseMimes) => {
1639
1545
  const regexp = /\.([a-zA-Z0-9]+?)$/;
@@ -1693,6 +1599,7 @@ var _baseMimes = {
1693
1599
  wasm: "application/wasm",
1694
1600
  webm: "video/webm",
1695
1601
  weba: "audio/webm",
1602
+ webmanifest: "application/manifest+json",
1696
1603
  webp: "image/webp",
1697
1604
  woff: "font/woff",
1698
1605
  woff2: "font/woff2",
@@ -1706,6 +1613,22 @@ var _baseMimes = {
1706
1613
  };
1707
1614
  var baseMimes = _baseMimes;
1708
1615
 
1616
+ // ../../node_modules/hono/dist/middleware/serve-static/path.js
1617
+ var defaultJoin = (...paths) => {
1618
+ let result = paths.filter((p) => p !== "").join("/");
1619
+ result = result.replace(/(?<=\/)\/+/g, "");
1620
+ const segments = result.split("/");
1621
+ const resolved = [];
1622
+ for (const segment of segments) {
1623
+ if (segment === ".." && resolved.length > 0 && resolved.at(-1) !== "..") {
1624
+ resolved.pop();
1625
+ } else if (segment !== ".") {
1626
+ resolved.push(segment);
1627
+ }
1628
+ }
1629
+ return resolved.join("/") || ".";
1630
+ };
1631
+
1709
1632
  // ../../node_modules/hono/dist/middleware/serve-static/index.js
1710
1633
  var ENCODINGS = {
1711
1634
  br: ".br",
@@ -1714,65 +1637,34 @@ var ENCODINGS = {
1714
1637
  };
1715
1638
  var ENCODINGS_ORDERED_KEYS = Object.keys(ENCODINGS);
1716
1639
  var DEFAULT_DOCUMENT = "index.html";
1717
- var defaultPathResolve = (path) => path;
1718
1640
  var serveStatic = (options) => {
1719
- let isAbsoluteRoot = false;
1720
- let root;
1721
- if (options.root) {
1722
- if (options.root.startsWith("/")) {
1723
- isAbsoluteRoot = true;
1724
- root = new URL(`file://${options.root}`).pathname;
1725
- } else {
1726
- root = options.root;
1727
- }
1728
- }
1641
+ const root = options.root ?? "./";
1642
+ const optionPath = options.path;
1643
+ const join = options.join ?? defaultJoin;
1729
1644
  return async (c, next) => {
1730
1645
  if (c.finalized) {
1731
- await next();
1732
- return;
1646
+ return next();
1733
1647
  }
1734
- let filename = options.path ?? decodeURI(c.req.path);
1735
- filename = options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename;
1736
- if (!filename.endsWith("/") && options.isDir) {
1737
- const path2 = getFilePathWithoutDefaultDocument({
1738
- filename,
1739
- root
1740
- });
1741
- if (path2 && await options.isDir(path2)) {
1742
- filename += "/";
1648
+ let filename;
1649
+ if (options.path) {
1650
+ filename = options.path;
1651
+ } else {
1652
+ try {
1653
+ filename = decodeURIComponent(c.req.path);
1654
+ if (/(?:^|[\/\\])\.\.(?:$|[\/\\])/.test(filename)) {
1655
+ throw new Error;
1656
+ }
1657
+ } catch {
1658
+ await options.onNotFound?.(c.req.path, c);
1659
+ return next();
1743
1660
  }
1744
1661
  }
1745
- let path = getFilePath({
1746
- filename,
1747
- root,
1748
- defaultDocument: DEFAULT_DOCUMENT
1749
- });
1750
- if (!path) {
1751
- return await next();
1752
- }
1753
- if (isAbsoluteRoot) {
1754
- path = "/" + path;
1662
+ let path = join(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename);
1663
+ if (options.isDir && await options.isDir(path)) {
1664
+ path = join(path, DEFAULT_DOCUMENT);
1755
1665
  }
1756
1666
  const getContent = options.getContent;
1757
- const pathResolve = options.pathResolve ?? defaultPathResolve;
1758
- path = pathResolve(path);
1759
1667
  let content = await getContent(path, c);
1760
- if (!content) {
1761
- let pathWithoutDefaultDocument = getFilePathWithoutDefaultDocument({
1762
- filename,
1763
- root
1764
- });
1765
- if (!pathWithoutDefaultDocument) {
1766
- return await next();
1767
- }
1768
- pathWithoutDefaultDocument = pathResolve(pathWithoutDefaultDocument);
1769
- if (pathWithoutDefaultDocument !== path) {
1770
- content = await getContent(pathWithoutDefaultDocument, c);
1771
- if (content) {
1772
- path = pathWithoutDefaultDocument;
1773
- }
1774
- }
1775
- }
1776
1668
  if (content instanceof Response) {
1777
1669
  return c.newResponse(content.body, content);
1778
1670
  }
@@ -1807,13 +1699,9 @@ var serveStatic = (options) => {
1807
1699
  var serveStatic2 = (options) => {
1808
1700
  return async function serveStatic2(c, next) {
1809
1701
  const getContent = async (path) => {
1810
- path = path.startsWith("/") ? path : `./${path}`;
1811
1702
  const file = Bun.file(path);
1812
1703
  return await file.exists() ? file : null;
1813
1704
  };
1814
- const pathResolve = (path) => {
1815
- return path.startsWith("/") ? path : `./${path}`;
1816
- };
1817
1705
  const isDir = async (path) => {
1818
1706
  let isDir2;
1819
1707
  try {
@@ -1825,7 +1713,7 @@ var serveStatic2 = (options) => {
1825
1713
  return serveStatic({
1826
1714
  ...options,
1827
1715
  getContent,
1828
- pathResolve,
1716
+ join,
1829
1717
  isDir
1830
1718
  })(c, next);
1831
1719
  };
@@ -1869,6 +1757,52 @@ var WSContext = class {
1869
1757
  this.#init.close(code, reason);
1870
1758
  }
1871
1759
  };
1760
+ var defineWebSocketHelper = (handler) => {
1761
+ return (...args) => {
1762
+ if (typeof args[0] === "function") {
1763
+ const [createEvents, options] = args;
1764
+ return async function upgradeWebSocket(c, next) {
1765
+ const events = await createEvents(c);
1766
+ const result = await handler(c, events, options);
1767
+ if (result) {
1768
+ return result;
1769
+ }
1770
+ await next();
1771
+ };
1772
+ } else {
1773
+ const [c, events, options] = args;
1774
+ return (async () => {
1775
+ const upgraded = await handler(c, events, options);
1776
+ if (!upgraded) {
1777
+ throw new Error("Failed to upgrade WebSocket");
1778
+ }
1779
+ return upgraded;
1780
+ })();
1781
+ }
1782
+ };
1783
+ };
1784
+
1785
+ // ../../node_modules/hono/dist/adapter/bun/server.js
1786
+ var getBunServer = (c) => ("server" in c.env) ? c.env.server : c.env;
1787
+
1788
+ // ../../node_modules/hono/dist/adapter/bun/websocket.js
1789
+ var upgradeWebSocket = defineWebSocketHelper((c, events) => {
1790
+ const server = getBunServer(c);
1791
+ if (!server) {
1792
+ throw new TypeError("env has to include the 2nd argument of fetch.");
1793
+ }
1794
+ const upgradeResult = server.upgrade(c.req.raw, {
1795
+ data: {
1796
+ events,
1797
+ url: new URL(c.req.url),
1798
+ protocol: c.req.url
1799
+ }
1800
+ });
1801
+ if (upgradeResult) {
1802
+ return new Response(null);
1803
+ }
1804
+ return;
1805
+ });
1872
1806
 
1873
1807
  // ../../node_modules/hono/dist/http-exception.js
1874
1808
  var HTTPException = class extends Error {
@@ -1894,10 +1828,12 @@ var HTTPException = class extends Error {
1894
1828
  };
1895
1829
 
1896
1830
  // ../../node_modules/hono/dist/middleware/csrf/index.js
1831
+ var secFetchSiteValues = ["same-origin", "same-site", "none", "cross-site"];
1832
+ var isSecFetchSite = (value) => secFetchSiteValues.includes(value);
1897
1833
  var isSafeMethodRe = /^(GET|HEAD)$/;
1898
1834
  var isRequestedByFormElementRe = /^\b(application\/x-www-form-urlencoded|multipart\/form-data|text\/plain)\b/i;
1899
1835
  var csrf = (options) => {
1900
- const handler = ((optsOrigin) => {
1836
+ const originHandler = ((optsOrigin) => {
1901
1837
  if (!optsOrigin) {
1902
1838
  return (origin, c) => origin === new URL(c.req.url).origin;
1903
1839
  } else if (typeof optsOrigin === "string") {
@@ -1912,13 +1848,31 @@ var csrf = (options) => {
1912
1848
  if (origin === undefined) {
1913
1849
  return false;
1914
1850
  }
1915
- return handler(origin, c);
1851
+ return originHandler(origin, c);
1852
+ };
1853
+ const secFetchSiteHandler = ((optsSecFetchSite) => {
1854
+ if (!optsSecFetchSite) {
1855
+ return (secFetchSite) => secFetchSite === "same-origin";
1856
+ } else if (typeof optsSecFetchSite === "string") {
1857
+ return (secFetchSite) => secFetchSite === optsSecFetchSite;
1858
+ } else if (typeof optsSecFetchSite === "function") {
1859
+ return optsSecFetchSite;
1860
+ } else {
1861
+ return (secFetchSite) => optsSecFetchSite.includes(secFetchSite);
1862
+ }
1863
+ })(options?.secFetchSite);
1864
+ const isAllowedSecFetchSite = (secFetchSite, c) => {
1865
+ if (secFetchSite === undefined) {
1866
+ return false;
1867
+ }
1868
+ if (!isSecFetchSite(secFetchSite)) {
1869
+ return false;
1870
+ }
1871
+ return secFetchSiteHandler(secFetchSite, c);
1916
1872
  };
1917
1873
  return async function csrf2(c, next) {
1918
- if (!isSafeMethodRe.test(c.req.method) && isRequestedByFormElementRe.test(c.req.header("content-type") || "text/plain") && !isAllowedOrigin(c.req.header("origin"), c)) {
1919
- const res = new Response("Forbidden", {
1920
- status: 403
1921
- });
1874
+ if (!isSafeMethodRe.test(c.req.method) && isRequestedByFormElementRe.test(c.req.header("content-type") || "text/plain") && !isAllowedSecFetchSite(c.req.header("sec-fetch-site"), c) && !isAllowedOrigin(c.req.header("origin"), c)) {
1875
+ const res = new Response("Forbidden", { status: 403 });
1922
1876
  throw new HTTPException(403, { res });
1923
1877
  }
1924
1878
  await next();
@@ -2159,11 +2113,11 @@ async function showErrorReponse(context, err, responseOptions) {
2159
2113
  }
2160
2114
  var ROUTE_SEGMENT = /(\[[a-zA-Z_\.]+\])/g;
2161
2115
  async function buildRoutes(config) {
2162
- const routesDir = join(config.appDir, "routes");
2116
+ const routesDir = join2(config.appDir, "routes");
2163
2117
  const files = await readdir(routesDir, { recursive: true });
2164
2118
  const routes = [];
2165
2119
  for (const file of files) {
2166
- if (!file.includes(".") || basename(file).startsWith(".")) {
2120
+ if (!file.includes(".") || basename(file).startsWith(".") || file.includes(".test.") || file.includes(".spec.")) {
2167
2121
  continue;
2168
2122
  }
2169
2123
  let route = "/" + file.replace(extname(file), "");
@@ -2186,33 +2140,33 @@ async function buildRoutes(config) {
2186
2140
  });
2187
2141
  }
2188
2142
  routes.push({
2189
- file: join("./", routesDir, file),
2143
+ file: join2("./", routesDir, file),
2190
2144
  route: normalizePath(route || "/"),
2191
2145
  params
2192
2146
  });
2193
2147
  }
2194
2148
  return await Promise.all(routes.map(async (route) => {
2195
- route.module = (await import(join(CWD, route.file))).default;
2149
+ route.module = (await import(join2(CWD, route.file))).default;
2196
2150
  return route;
2197
2151
  }));
2198
2152
  }
2199
2153
  async function buildActions(config) {
2200
- const routesDir = join(config.appDir, "actions");
2154
+ const routesDir = join2(config.appDir, "actions");
2201
2155
  const files = await readdir(routesDir, { recursive: true });
2202
2156
  const routes = [];
2203
2157
  for (const file of files) {
2204
- if (!file.includes(".") || basename(file).startsWith(".")) {
2158
+ if (!file.includes(".") || basename(file).startsWith(".") || file.includes(".test.") || file.includes(".spec.")) {
2205
2159
  continue;
2206
2160
  }
2207
2161
  let route = assetHash("/" + file.replace(extname(file), ""));
2208
2162
  routes.push({
2209
- file: join("./", routesDir, file),
2163
+ file: join2("./", routesDir, file),
2210
2164
  route: `/__actions/${route}`,
2211
2165
  params: []
2212
2166
  });
2213
2167
  }
2214
2168
  return await Promise.all(routes.map(async (route) => {
2215
- route.module = (await import(join(CWD, route.file))).default;
2169
+ route.module = (await import(join2(CWD, route.file))).default;
2216
2170
  route.route = route.module._route;
2217
2171
  return route;
2218
2172
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperspan/framework",
3
- "version": "0.4.4",
3
+ "version": "0.5.0",
4
4
  "description": "Hyperspan Web Framework",
5
5
  "main": "dist/server.ts",
6
6
  "types": "src/server.ts",
@@ -65,10 +65,10 @@
65
65
  "typescript": "^5.8.3"
66
66
  },
67
67
  "dependencies": {
68
- "@hyperspan/html": "workspace:^",
69
- "hono": "^4.7.10",
70
- "isbot": "^5.1.28",
68
+ "@hyperspan/html": "0.1.7",
69
+ "hono": "^4.9.4",
70
+ "isbot": "^5.1.30",
71
71
  "timestring": "^7.0.0",
72
- "zod": "^3.25.67"
72
+ "zod": "^4.1.5"
73
73
  }
74
74
  }
@@ -1,11 +1,11 @@
1
- import { z } from 'zod';
1
+ import { z } from 'zod/v4';
2
2
  import { unstable__createAction } from './actions';
3
3
  import { describe, it, expect } from 'bun:test';
4
4
  import { html, render, type HSHtml } from '@hyperspan/html';
5
- import type { Context } from 'hono';
5
+ import type { THSContext } from './server';
6
6
 
7
7
  describe('createAction', () => {
8
- const formWithNameOnly = ({ data }: { data?: { name: string } }) => {
8
+ const formWithNameOnly = (c: THSContext, { data }: { data?: { name: string } }) => {
9
9
  return html`
10
10
  <form>
11
11
  <p>
@@ -23,8 +23,18 @@ describe('createAction', () => {
23
23
  name: z.string(),
24
24
  });
25
25
  const action = unstable__createAction(schema, formWithNameOnly);
26
+ const mockContext = {
27
+ req: {
28
+ method: 'POST',
29
+ formData: async () => {
30
+ const formData = new FormData();
31
+ formData.append('name', 'John');
32
+ return formData;
33
+ },
34
+ },
35
+ } as THSContext;
26
36
 
27
- const formResponse = render(action.render({ data: { name: 'John' } }) as HSHtml);
37
+ const formResponse = render(action.render(mockContext, { data: { name: 'John' } }) as HSHtml);
28
38
  expect(formResponse).toContain('value="John"');
29
39
  });
30
40
  });
@@ -52,7 +62,7 @@ describe('createAction', () => {
52
62
  return formData;
53
63
  },
54
64
  },
55
- } as Context;
65
+ } as THSContext;
56
66
 
57
67
  const response = await action.run(mockContext);
58
68
 
@@ -85,7 +95,7 @@ describe('createAction', () => {
85
95
  return formData;
86
96
  },
87
97
  },
88
- } as Context;
98
+ } as THSContext;
89
99
 
90
100
  const response = await action.run(mockContext);
91
101
 
package/src/actions.ts CHANGED
@@ -2,8 +2,8 @@ import { html, HSHtml } from '@hyperspan/html';
2
2
  import * as z from 'zod/v4';
3
3
  import { HTTPException } from 'hono/http-exception';
4
4
  import { assetHash } from './assets';
5
- import { IS_PROD, returnHTMLResponse, type THSResponseTypes } from './server';
6
- import type { Context, MiddlewareHandler } from 'hono';
5
+ import { IS_PROD, returnHTMLResponse, type THSContext, type THSResponseTypes } from './server';
6
+ import type { MiddlewareHandler } from 'hono';
7
7
  import type { HandlerResponse, Next, TypedResponse } from 'hono/types';
8
8
 
9
9
  /**
@@ -29,39 +29,34 @@ export interface HSAction<T extends z.ZodTypeAny> {
29
29
  _form: Parameters<HSAction<T>['form']>[0];
30
30
  form(
31
31
  renderForm: (
32
- c: Context<any, any, {}>,
32
+ c: THSContext,
33
33
  { data, error }: { data?: z.infer<T>; error?: z.ZodError | Error }
34
34
  ) => HSHtml | void | null | Promise<HSHtml | void | null>
35
35
  ): HSAction<T>;
36
36
  post(
37
37
  handler: (
38
- c: Context<any, any, {}>,
38
+ c: THSContext,
39
39
  { data }: { data?: z.infer<T> }
40
40
  ) => TActionResponse | Promise<TActionResponse>
41
41
  ): HSAction<T>;
42
42
  error(
43
43
  handler: (
44
- c: Context<any, any, {}>,
44
+ c: THSContext,
45
45
  { data, error }: { data?: z.infer<T>; error?: z.ZodError | Error }
46
46
  ) => TActionResponse
47
47
  ): HSAction<T>;
48
- render(
49
- c: Context<any, any, {}>,
50
- props?: { data?: z.infer<T>; error?: z.ZodError | Error }
51
- ): TActionResponse;
52
- run(c: Context<any, any, {}>): TActionResponse | Promise<TActionResponse>;
48
+ render(c: THSContext, props?: { data?: z.infer<T>; error?: z.ZodError | Error }): TActionResponse;
49
+ run(c: THSContext): TActionResponse | Promise<TActionResponse>;
53
50
  middleware: (
54
51
  middleware: Array<
55
52
  | MiddlewareHandler
56
- | ((
57
- context: Context<any, string, {}>
58
- ) => TActionResponse | Promise<TActionResponse> | void | Promise<void>)
53
+ | ((context: THSContext) => TActionResponse | Promise<TActionResponse> | void | Promise<void>)
59
54
  >
60
55
  ) => HSAction<T>;
61
56
  _getRouteHandlers: () => Array<
62
57
  | MiddlewareHandler
63
- | ((context: Context, next: Next) => TActionResponse | Promise<TActionResponse>)
64
- | ((context: Context) => TActionResponse | Promise<TActionResponse>)
58
+ | ((context: THSContext, next: Next) => TActionResponse | Promise<TActionResponse>)
59
+ | ((context: THSContext) => TActionResponse | Promise<TActionResponse>)
65
60
  >;
66
61
  }
67
62
 
@@ -74,8 +69,8 @@ export function unstable__createAction<T extends z.ZodTypeAny>(
74
69
  _errorHandler: Parameters<HSAction<T>['error']>[0] | null = null,
75
70
  _middleware: Array<
76
71
  | MiddlewareHandler
77
- | ((context: Context, next: Next) => TActionResponse | Promise<TActionResponse>)
78
- | ((context: Context) => TActionResponse | Promise<TActionResponse>)
72
+ | ((context: THSContext, next: Next) => TActionResponse | Promise<TActionResponse>)
73
+ | ((context: THSContext) => TActionResponse | Promise<TActionResponse>)
79
74
  > = [];
80
75
 
81
76
  const api: HSAction<T> = {
@@ -113,10 +108,7 @@ export function unstable__createAction<T extends z.ZodTypeAny>(
113
108
  /**
114
109
  * Get form renderer method
115
110
  */
116
- render(
117
- c: Context<any, any, {}>,
118
- formState?: { data?: z.infer<T>; error?: z.ZodError | Error }
119
- ) {
111
+ render(c: THSContext, formState?: { data?: z.infer<T>; error?: z.ZodError | Error }) {
120
112
  const form = _form ? _form(c, formState || {}) : null;
121
113
  return form ? html`<hs-action url="${this._route}">${form}</hs-action>` : null;
122
114
  },
@@ -124,7 +116,7 @@ export function unstable__createAction<T extends z.ZodTypeAny>(
124
116
  _getRouteHandlers() {
125
117
  return [
126
118
  ..._middleware,
127
- async (c: Context) => {
119
+ async (c: THSContext) => {
128
120
  const response = await returnHTMLResponse(c, () => api.run(c));
129
121
 
130
122
  // Replace redirects with special header because fetch() automatically follows redirects
package/src/server.ts CHANGED
@@ -18,16 +18,19 @@ const CWD = process.cwd();
18
18
  /**
19
19
  * Types
20
20
  */
21
+ export type THSContext = Context<any, any, {}>;
21
22
  export type THSResponseTypes = HSHtml | Response | string | null;
22
- export type THSRouteHandler = (context: Context) => THSResponseTypes | Promise<THSResponseTypes>;
23
- export type THSAPIRouteHandler = (context: Context) => Promise<any> | any;
23
+ export type THSRouteHandler = (context: THSContext) => THSResponseTypes | Promise<THSResponseTypes>;
24
+ export type THSAPIRouteHandler = (context: THSContext) => Promise<any> | any;
24
25
 
25
26
  export type THSRoute = {
26
27
  _kind: 'hsRoute';
27
28
  get: (handler: THSRouteHandler) => THSRoute;
28
29
  post: (handler: THSRouteHandler) => THSRoute;
29
30
  middleware: (middleware: Array<MiddlewareHandler>) => THSRoute;
30
- _getRouteHandlers: () => Array<MiddlewareHandler | ((context: Context) => HandlerResponse<any>)>;
31
+ _getRouteHandlers: () => Array<
32
+ MiddlewareHandler | ((context: THSContext) => HandlerResponse<any>)
33
+ >;
31
34
  };
32
35
  export type THSAPIRoute = {
33
36
  _kind: 'hsAPIRoute';
@@ -37,7 +40,9 @@ export type THSAPIRoute = {
37
40
  delete: (handler: THSAPIRouteHandler) => THSAPIRoute;
38
41
  patch: (handler: THSAPIRouteHandler) => THSAPIRoute;
39
42
  middleware: (middleware: Array<MiddlewareHandler>) => THSAPIRoute;
40
- _getRouteHandlers: () => Array<MiddlewareHandler | ((context: Context) => HandlerResponse<any>)>;
43
+ _getRouteHandlers: () => Array<
44
+ MiddlewareHandler | ((context: THSContext) => HandlerResponse<any>)
45
+ >;
41
46
  };
42
47
 
43
48
  export function createConfig(config: THSServerConfig): THSServerConfig {
@@ -82,7 +87,7 @@ export function createRoute(handler?: THSRouteHandler): THSRoute {
82
87
  _getRouteHandlers() {
83
88
  return [
84
89
  ..._middleware,
85
- async (context: Context) => {
90
+ async (context: THSContext) => {
86
91
  const method = context.req.method.toUpperCase();
87
92
 
88
93
  // Handle CORS preflight requests
@@ -166,7 +171,7 @@ export function createAPIRoute(handler?: THSAPIRouteHandler): THSAPIRoute {
166
171
  _getRouteHandlers() {
167
172
  return [
168
173
  ..._middleware,
169
- async (context: Context) => {
174
+ async (context: THSContext) => {
170
175
  const method = context.req.method.toUpperCase();
171
176
 
172
177
  // Handle CORS preflight requests
@@ -246,7 +251,7 @@ export function createAPIRoute(handler?: THSAPIRouteHandler): THSAPIRoute {
246
251
  * Return HTML response from userland route handler
247
252
  */
248
253
  export async function returnHTMLResponse(
249
- context: Context,
254
+ context: THSContext,
250
255
  handlerFn: () => unknown,
251
256
  responseOptions?: { status?: ContentfulStatusCode; headers?: Headers | Record<string, string> }
252
257
  ): Promise<Response> {
@@ -333,7 +338,7 @@ export function isRunnableRoute(route: unknown): boolean {
333
338
  * @TODO: Should check for and load user-customizeable template with special name (app/__error.ts ?)
334
339
  */
335
340
  async function showErrorReponse(
336
- context: Context,
341
+ context: THSContext,
337
342
  err: Error,
338
343
  responseOptions?: { status?: ContentfulStatusCode; headers?: Headers | Record<string, string> }
339
344
  ) {
@@ -408,8 +413,13 @@ export async function buildRoutes(config: THSServerConfig): Promise<THSRouteMap[
408
413
  const routes: THSRouteMap[] = [];
409
414
 
410
415
  for (const file of files) {
411
- // No directories
412
- if (!file.includes('.') || basename(file).startsWith('.')) {
416
+ // No directories or test files
417
+ if (
418
+ !file.includes('.') ||
419
+ basename(file).startsWith('.') ||
420
+ file.includes('.test.') ||
421
+ file.includes('.spec.')
422
+ ) {
413
423
  continue;
414
424
  }
415
425
 
@@ -466,8 +476,13 @@ export async function buildActions(config: THSServerConfig): Promise<THSRouteMap
466
476
  const routes: THSRouteMap[] = [];
467
477
 
468
478
  for (const file of files) {
469
- // No directories
470
- if (!file.includes('.') || basename(file).startsWith('.')) {
479
+ // No directories or test files
480
+ if (
481
+ !file.includes('.') ||
482
+ basename(file).startsWith('.') ||
483
+ file.includes('.test.') ||
484
+ file.includes('.spec.')
485
+ ) {
471
486
  continue;
472
487
  }
473
488
 
@@ -496,7 +511,7 @@ export async function buildActions(config: THSServerConfig): Promise<THSRouteMap
496
511
  */
497
512
  export function createRouteFromModule(
498
513
  RouteModule: any
499
- ): Array<MiddlewareHandler | ((context: Context) => HandlerResponse<any>)> {
514
+ ): Array<MiddlewareHandler | ((context: THSContext) => HandlerResponse<any>)> {
500
515
  const route = getRunnableRoute(RouteModule);
501
516
  return route._getRouteHandlers();
502
517
  }