@hyperspan/framework 0.1.7 → 0.2.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.
package/LICENSE.txt ADDED
@@ -0,0 +1,30 @@
1
+ BSD 3-Clause License
2
+
3
+ Hyperspan copyright (c) 2025, Vance Lucas.
4
+ LINK: https://www.hyperspan.dev
5
+ All rights reserved.
6
+
7
+ Redistribution and use in source and binary forms, with or without
8
+ modification, are permitted provided that the following conditions are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright notice, this
11
+ list of conditions and the following disclaimer.
12
+
13
+ 2. Redistributions in binary form must reproduce the above copyright notice,
14
+ this list of conditions and the following disclaimer in the documentation
15
+ and/or other materials provided with the distribution.
16
+
17
+ 3. Neither the name of the copyright holder nor the names of its
18
+ contributors may be used to endorse or promote products derived from
19
+ this software without specific prior written permission.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @hyperspan/framework
2
2
 
3
- > [!NOTE] > **The Hyperspan framework package is still in the heavy development. APIs may change without notice.**
3
+ ![License](https://img.shields.io/npm/l/%40hyperspan%2Fframework?style=for-the-badge&labelColor=2e3440&color=6f4fbe)
4
+ ![Version](https://img.shields.io/npm/v/%40hyperspan%2Fframework.svg?label=Version&style=for-the-badge&labelColor=2e3440&color=eea837)
5
+ ![Downloads](https://img.shields.io/npm/dw/%40hyperspan%2Fframework?style=for-the-badge&labelColor=2e3440&color=50b6a9)
6
+ ![Bun Badge](https://img.shields.io/badge/Bun-000?logo=bun&logoColor=fff&style=for-the-badge&color=2e3440)
7
+ ![TypeScript Badge](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=fff&style=for-the-badge)
4
8
 
5
- Full Hyperspan framework docs coming soon...
9
+ Hyperspan web framework!
10
+
11
+ Information and docs are at [Hyperspan.dev](https://www.hyperspan.dev)
package/build.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { build } from 'bun';
2
2
 
3
3
  const entrypoints = ['./src/server.ts', './src/assets.ts'];
4
- const external = ['@hyperspan/html'];
4
+ const external = ['@hyperspan/html', 'preact', 'preact-render-to-string'];
5
5
  const outdir = './dist';
6
6
  const target = 'node';
7
7
  const splitting = true;
package/dist/assets.js CHANGED
@@ -55,67 +55,36 @@ function hyperspanScriptTags() {
55
55
  ></script>`)}
56
56
  `;
57
57
  }
58
- var PREACT_PUBLIC_FILE_PATH = "/_hs/js/preact.js";
59
- function md5(content) {
58
+ function assetHash(content) {
60
59
  return createHash("md5").update(content).digest("hex");
61
60
  }
62
- async function copyPreactToPublicFolder() {
63
- const sourceFile = resolve(PWD, "../", "./src/clientjs/preact.ts");
64
- const preactClient = Bun.build({
65
- entrypoints: [sourceFile],
66
- outdir: "./public/_hs/js",
67
- minify: true,
68
- format: "esm",
69
- target: "browser"
70
- });
71
- }
72
- async function createPreactIsland(file) {
73
- let filePath = file.replace("file://", "");
74
- const jsId = md5(filePath);
75
- if (!clientImportMap.has("preact")) {
76
- await copyPreactToPublicFolder();
77
- clientImportMap.set("preact", "" + PREACT_PUBLIC_FILE_PATH);
78
- clientImportMap.set("preact/compat", "" + PREACT_PUBLIC_FILE_PATH);
79
- clientImportMap.set("preact/hooks", "" + PREACT_PUBLIC_FILE_PATH);
80
- clientImportMap.set("preact/jsx-runtime", "" + PREACT_PUBLIC_FILE_PATH);
61
+ var ISLAND_PUBLIC_PATH = "/_hs/js/islands";
62
+ var ISLAND_DEFAULTS = () => ({
63
+ ssr: true
64
+ });
65
+ function renderIsland(Component, props, options = ISLAND_DEFAULTS()) {
66
+ if (Component.__HS_ISLAND?.render) {
67
+ return Component.__HS_ISLAND.render(props, options);
81
68
  }
82
- if (!clientImportMap.has("react")) {
83
- clientImportMap.set("react", "." + PREACT_PUBLIC_FILE_PATH);
84
- clientImportMap.set("react-dom", "." + PREACT_PUBLIC_FILE_PATH);
69
+ if (Component.__HS_ISLAND?.ssr && options.ssr) {
70
+ return Component.__HS_ISLAND.ssr(props);
85
71
  }
86
- let resultStr = 'import{h,render}from"preact";';
87
- const buildResult = await Bun.build({
88
- entrypoints: [filePath],
89
- minify: true,
90
- external: ["react", "preact"],
91
- env: "APP_PUBLIC_*"
92
- });
93
- for (const output of buildResult.outputs) {
94
- resultStr += await output.text();
95
- }
96
- const r = /export\{([a-zA-Z]+) as default\}/g;
97
- const matchExport = r.exec(resultStr);
98
- if (!matchExport) {
99
- throw new Error("File does not have a default export! Ensure a function has export default to use this.");
72
+ if (Component.__HS_ISLAND?.clientOnly) {
73
+ return Component.__HS_ISLAND.clientOnly(props);
100
74
  }
101
- const fn = matchExport[1];
102
- let _mounted = false;
103
- return (props) => {
104
- if (!_mounted) {
105
- _mounted = true;
106
- resultStr += `render(h(${fn}, ${JSON.stringify(props)}), document.getElementById("${jsId}"));`;
107
- }
108
- return html.raw(`<div id="${jsId}"></div><script type="module" data-source-id="${jsId}">${resultStr}</script>`);
109
- };
75
+ throw new Error(`Module ${Component.name} was not loaded with an island plugin! Did you forget to install an island plugin and add it to the createServer() 'islandPlugins' config?`);
110
76
  }
111
77
  export {
78
+ renderIsland,
112
79
  hyperspanStyleTags,
113
80
  hyperspanScriptTags,
114
- createPreactIsland,
115
81
  clientJSFiles,
116
82
  clientImportMap,
117
83
  clientCSSFiles,
118
84
  buildClientJS,
119
- buildClientCSS
85
+ buildClientCSS,
86
+ assetHash,
87
+ ISLAND_PUBLIC_PATH,
88
+ ISLAND_DEFAULTS
120
89
  };
121
90
 
package/dist/server.js CHANGED
@@ -8,8 +8,8 @@ import { readdir } from "node:fs/promises";
8
8
  import { basename, extname, join } from "node:path";
9
9
  import { html, isHSHtml, renderStream, renderAsync, render } from "@hyperspan/html";
10
10
 
11
- // node_modules/isbot/index.mjs
12
- 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/";
11
+ // ../../node_modules/isbot/index.mjs
12
+ 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/";
13
13
  var naivePattern = /bot|crawl|http|lighthouse|scan|search|spider/i;
14
14
  var pattern;
15
15
  function getPattern() {
@@ -27,7 +27,7 @@ function isbot(userAgent) {
27
27
  return Boolean(userAgent) && getPattern().test(userAgent);
28
28
  }
29
29
 
30
- // node_modules/hono/dist/compose.js
30
+ // ../../node_modules/hono/dist/compose.js
31
31
  var compose = (middleware, onError, onNotFound) => {
32
32
  return (context, next) => {
33
33
  let index = -1;
@@ -71,7 +71,7 @@ var compose = (middleware, onError, onNotFound) => {
71
71
  };
72
72
  };
73
73
 
74
- // node_modules/hono/dist/utils/body.js
74
+ // ../../node_modules/hono/dist/utils/body.js
75
75
  var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
76
76
  const { all = false, dot = false } = options;
77
77
  const headers = request instanceof HonoRequest ? request.raw.headers : request.headers;
@@ -135,7 +135,7 @@ var handleParsingNestedValues = (form, key, value) => {
135
135
  });
136
136
  };
137
137
 
138
- // node_modules/hono/dist/utils/url.js
138
+ // ../../node_modules/hono/dist/utils/url.js
139
139
  var splitPath = (path) => {
140
140
  const paths = path.split("/");
141
141
  if (paths[0] === "") {
@@ -330,7 +330,7 @@ var getQueryParams = (url, key) => {
330
330
  };
331
331
  var decodeURIComponent_ = decodeURIComponent;
332
332
 
333
- // node_modules/hono/dist/request.js
333
+ // ../../node_modules/hono/dist/request.js
334
334
  var tryDecodeURIComponent = (str) => tryDecode(str, decodeURIComponent_);
335
335
  var HonoRequest = class {
336
336
  raw;
@@ -438,7 +438,7 @@ var HonoRequest = class {
438
438
  }
439
439
  };
440
440
 
441
- // node_modules/hono/dist/utils/html.js
441
+ // ../../node_modules/hono/dist/utils/html.js
442
442
  var HtmlEscapedCallbackPhase = {
443
443
  Stringify: 1,
444
444
  BeforeStream: 2,
@@ -476,7 +476,7 @@ var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) =>
476
476
  }
477
477
  };
478
478
 
479
- // node_modules/hono/dist/context.js
479
+ // ../../node_modules/hono/dist/context.js
480
480
  var TEXT_PLAIN = "text/plain; charset=UTF-8";
481
481
  var setHeaders = (headers, map = {}) => {
482
482
  for (const key of Object.keys(map)) {
@@ -717,7 +717,7 @@ var Context = class {
717
717
  };
718
718
  };
719
719
 
720
- // node_modules/hono/dist/router.js
720
+ // ../../node_modules/hono/dist/router.js
721
721
  var METHOD_NAME_ALL = "ALL";
722
722
  var METHOD_NAME_ALL_LOWERCASE = "all";
723
723
  var METHODS = ["get", "post", "put", "delete", "options", "patch"];
@@ -725,10 +725,10 @@ var MESSAGE_MATCHER_IS_ALREADY_BUILT = "Can not add a route since the matcher is
725
725
  var UnsupportedPathError = class extends Error {
726
726
  };
727
727
 
728
- // node_modules/hono/dist/utils/constants.js
728
+ // ../../node_modules/hono/dist/utils/constants.js
729
729
  var COMPOSED_HANDLER = "__COMPOSED_HANDLER";
730
730
 
731
- // node_modules/hono/dist/hono-base.js
731
+ // ../../node_modules/hono/dist/hono-base.js
732
732
  var notFoundHandler = (c) => {
733
733
  return c.text("404 Not Found", 404);
734
734
  };
@@ -801,6 +801,8 @@ var Hono = class {
801
801
  router: this.router,
802
802
  getPath: this.getPath
803
803
  });
804
+ clone.errorHandler = this.errorHandler;
805
+ clone.#notFoundHandler = this.#notFoundHandler;
804
806
  clone.routes = this.routes;
805
807
  return clone;
806
808
  }
@@ -855,8 +857,7 @@ var Hono = class {
855
857
  let executionContext = undefined;
856
858
  try {
857
859
  executionContext = c.executionCtx;
858
- } catch {
859
- }
860
+ } catch {}
860
861
  return [c.env, executionContext];
861
862
  };
862
863
  replaceRequest ||= (() => {
@@ -945,7 +946,7 @@ var Hono = class {
945
946
  };
946
947
  };
947
948
 
948
- // node_modules/hono/dist/router/reg-exp-router/node.js
949
+ // ../../node_modules/hono/dist/router/reg-exp-router/node.js
949
950
  var LABEL_REG_EXP_STR = "[^/]+";
950
951
  var ONLY_WILDCARD_REG_EXP_STR = ".*";
951
952
  var TAIL_WILDCARD_REG_EXP_STR = "(?:|/.*)";
@@ -1046,7 +1047,7 @@ var Node = class {
1046
1047
  }
1047
1048
  };
1048
1049
 
1049
- // node_modules/hono/dist/router/reg-exp-router/trie.js
1050
+ // ../../node_modules/hono/dist/router/reg-exp-router/trie.js
1050
1051
  var Trie = class {
1051
1052
  #context = { varIndex: 0 };
1052
1053
  #root = new Node;
@@ -1102,7 +1103,7 @@ var Trie = class {
1102
1103
  }
1103
1104
  };
1104
1105
 
1105
- // node_modules/hono/dist/router/reg-exp-router/router.js
1106
+ // ../../node_modules/hono/dist/router/reg-exp-router/router.js
1106
1107
  var emptyParam = [];
1107
1108
  var nullMatcher = [/^$/, [], /* @__PURE__ */ Object.create(null)];
1108
1109
  var wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
@@ -1284,7 +1285,7 @@ var RegExpRouter = class {
1284
1285
  }
1285
1286
  };
1286
1287
 
1287
- // node_modules/hono/dist/router/smart-router/router.js
1288
+ // ../../node_modules/hono/dist/router/smart-router/router.js
1288
1289
  var SmartRouter = class {
1289
1290
  name = "SmartRouter";
1290
1291
  #routers = [];
@@ -1339,7 +1340,7 @@ var SmartRouter = class {
1339
1340
  }
1340
1341
  };
1341
1342
 
1342
- // node_modules/hono/dist/router/trie-router/node.js
1343
+ // ../../node_modules/hono/dist/router/trie-router/node.js
1343
1344
  var emptyParams = /* @__PURE__ */ Object.create(null);
1344
1345
  var Node2 = class {
1345
1346
  #methods;
@@ -1495,7 +1496,7 @@ var Node2 = class {
1495
1496
  }
1496
1497
  };
1497
1498
 
1498
- // node_modules/hono/dist/router/trie-router/router.js
1499
+ // ../../node_modules/hono/dist/router/trie-router/router.js
1499
1500
  var TrieRouter = class {
1500
1501
  name = "TrieRouter";
1501
1502
  #node;
@@ -1517,7 +1518,7 @@ var TrieRouter = class {
1517
1518
  }
1518
1519
  };
1519
1520
 
1520
- // node_modules/hono/dist/hono.js
1521
+ // ../../node_modules/hono/dist/hono.js
1521
1522
  var Hono2 = class extends Hono {
1522
1523
  constructor(options = {}) {
1523
1524
  super(options);
@@ -1527,13 +1528,13 @@ var Hono2 = class extends Hono {
1527
1528
  }
1528
1529
  };
1529
1530
 
1530
- // node_modules/hono/dist/adapter/bun/serve-static.js
1531
+ // ../../node_modules/hono/dist/adapter/bun/serve-static.js
1531
1532
  import { stat } from "node:fs/promises";
1532
1533
 
1533
- // node_modules/hono/dist/utils/compress.js
1534
+ // ../../node_modules/hono/dist/utils/compress.js
1534
1535
  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;
1535
1536
 
1536
- // node_modules/hono/dist/utils/filepath.js
1537
+ // ../../node_modules/hono/dist/utils/filepath.js
1537
1538
  var getFilePath = (options) => {
1538
1539
  let filename = options.filename;
1539
1540
  const defaultDocument = options.defaultDocument || "index.html";
@@ -1565,7 +1566,7 @@ var getFilePathWithoutDefaultDocument = (options) => {
1565
1566
  return path;
1566
1567
  };
1567
1568
 
1568
- // node_modules/hono/dist/utils/mime.js
1569
+ // ../../node_modules/hono/dist/utils/mime.js
1569
1570
  var getMimeType = (filename, mimes = baseMimes) => {
1570
1571
  const regexp = /\.([a-zA-Z0-9]+?)$/;
1571
1572
  const match = filename.match(regexp);
@@ -1637,7 +1638,7 @@ var _baseMimes = {
1637
1638
  };
1638
1639
  var baseMimes = _baseMimes;
1639
1640
 
1640
- // node_modules/hono/dist/middleware/serve-static/index.js
1641
+ // ../../node_modules/hono/dist/middleware/serve-static/index.js
1641
1642
  var ENCODINGS = {
1642
1643
  br: ".br",
1643
1644
  zstd: ".zst",
@@ -1734,7 +1735,7 @@ var serveStatic = (options) => {
1734
1735
  };
1735
1736
  };
1736
1737
 
1737
- // node_modules/hono/dist/adapter/bun/serve-static.js
1738
+ // ../../node_modules/hono/dist/adapter/bun/serve-static.js
1738
1739
  var serveStatic2 = (options) => {
1739
1740
  return async function serveStatic2(c, next) {
1740
1741
  const getContent = async (path) => {
@@ -1750,8 +1751,7 @@ var serveStatic2 = (options) => {
1750
1751
  try {
1751
1752
  const stats = await stat(path);
1752
1753
  isDir2 = stats.isDirectory();
1753
- } catch {
1754
- }
1754
+ } catch {}
1755
1755
  return isDir2;
1756
1756
  };
1757
1757
  return serveStatic({
@@ -1763,7 +1763,7 @@ var serveStatic2 = (options) => {
1763
1763
  };
1764
1764
  };
1765
1765
 
1766
- // node_modules/hono/dist/helper/ssg/middleware.js
1766
+ // ../../node_modules/hono/dist/helper/ssg/middleware.js
1767
1767
  var X_HONO_DISABLE_SSG_HEADER_KEY = "x-hono-disable-ssg";
1768
1768
  var SSG_DISABLED_RESPONSE = (() => {
1769
1769
  try {
@@ -1775,10 +1775,10 @@ var SSG_DISABLED_RESPONSE = (() => {
1775
1775
  return null;
1776
1776
  }
1777
1777
  })();
1778
- // node_modules/hono/dist/adapter/bun/ssg.js
1778
+ // ../../node_modules/hono/dist/adapter/bun/ssg.js
1779
1779
  var { write } = Bun;
1780
1780
 
1781
- // node_modules/hono/dist/helper/websocket/index.js
1781
+ // ../../node_modules/hono/dist/helper/websocket/index.js
1782
1782
  var WSContext = class {
1783
1783
  #init;
1784
1784
  constructor(init) {
@@ -1802,7 +1802,7 @@ var WSContext = class {
1802
1802
  }
1803
1803
  };
1804
1804
 
1805
- // node_modules/hono/dist/http-exception.js
1805
+ // ../../node_modules/hono/dist/http-exception.js
1806
1806
  var HTTPException = class extends Error {
1807
1807
  res;
1808
1808
  status;
@@ -1828,6 +1828,9 @@ var HTTPException = class extends Error {
1828
1828
  // src/server.ts
1829
1829
  var IS_PROD = false;
1830
1830
  var CWD = process.cwd();
1831
+ function createConfig(config) {
1832
+ return config;
1833
+ }
1831
1834
  function createRoute(handler) {
1832
1835
  let _handlers = {};
1833
1836
  if (handler) {
@@ -1952,6 +1955,9 @@ function getRunnableRoute(route) {
1952
1955
  function isRunnableRoute(route) {
1953
1956
  return typeof route === "object" && "run" in route;
1954
1957
  }
1958
+ function createLayout(layout) {
1959
+ return layout;
1960
+ }
1955
1961
  async function showErrorReponse(context, err) {
1956
1962
  const output = render(html`
1957
1963
  <main>
@@ -1969,7 +1975,6 @@ async function showErrorReponse(context, err) {
1969
1975
  var ROUTE_SEGMENT = /(\[[a-zA-Z_\.]+\])/g;
1970
1976
  async function buildRoutes(config) {
1971
1977
  const routesDir = join(config.appDir, "routes");
1972
- console.log(routesDir);
1973
1978
  const files = await readdir(routesDir, { recursive: true });
1974
1979
  const routes = [];
1975
1980
  for (const file of files) {
@@ -2096,6 +2101,8 @@ export {
2096
2101
  createRouteFromModule,
2097
2102
  createRoute,
2098
2103
  createReadableStreamFromAsyncGenerator,
2104
+ createLayout,
2105
+ createConfig,
2099
2106
  createAPIRoute,
2100
2107
  buildRoutes,
2101
2108
  StreamResponse,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperspan/framework",
3
- "version": "0.1.7",
3
+ "version": "0.2.0",
4
4
  "description": "Hyperspan Web Framework",
5
5
  "main": "dist/server.js",
6
6
  "types": "src/server.ts",
@@ -53,21 +53,16 @@
53
53
  "prepack": "bun run clean && bun run build"
54
54
  },
55
55
  "devDependencies": {
56
- "@types/bun": "^1.1.9",
57
- "@types/node": "^22.5.5",
58
- "@types/react": "^19.1.0",
59
- "bun-plugin-dts": "^0.3.0",
60
- "bun-types": "latest",
61
- "prettier": "^3.2.5"
62
- },
63
- "peerDependencies": {
64
- "typescript": "^5.0.0"
56
+ "@types/bun": "^1.2.14",
57
+ "@types/node": "^22.15.20",
58
+ "@types/react": "^19.1.5",
59
+ "prettier": "^3.5.3",
60
+ "typescript": "^5.8.3"
65
61
  },
66
62
  "dependencies": {
67
- "@hyperspan/html": "^0.1.6",
68
- "hono": "^4.7.4",
69
- "isbot": "^5.1.25",
70
- "preact": "^10.26.5",
71
- "zod": "^4.0.0-beta.20250415T232143"
63
+ "@hyperspan/html": "^0.1.7",
64
+ "hono": "^4.7.10",
65
+ "isbot": "^5.1.28",
66
+ "zod": "^3.25.28"
72
67
  }
73
68
  }
@@ -1,4 +1,4 @@
1
- import z from 'zod';
1
+ import z from 'zod/v4';
2
2
  import { createAction } from './actions';
3
3
  import { describe, it, expect } from 'bun:test';
4
4
  import { html, render, type HSHtml } from '@hyperspan/html';
package/src/actions.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { html, HSHtml } from '@hyperspan/html';
2
- import * as z from 'zod';
2
+ import * as z from 'zod/v4';
3
3
  import { HTTPException } from 'hono/http-exception';
4
4
 
5
5
  import type { THSResponseTypes } from './server';
package/src/assets.ts CHANGED
@@ -89,82 +89,35 @@ export function hyperspanScriptTags() {
89
89
  `;
90
90
  }
91
91
 
92
- // External ESM = https://esm.sh/preact@10.26.4/compat
93
- const PREACT_PUBLIC_FILE_PATH = '/_hs/js/preact.js';
94
-
95
- function md5(content: string): string {
92
+ export function assetHash(content: string): string {
96
93
  return createHash('md5').update(content).digest('hex');
97
94
  }
98
95
 
99
96
  /**
100
- * Build Preact client JS and copy to public folder
101
- */
102
- async function copyPreactToPublicFolder() {
103
- const sourceFile = resolve(PWD, '../', './src/clientjs/preact.ts');
104
- const preactClient = Bun.build({
105
- entrypoints: [sourceFile],
106
- outdir: './public/_hs/js',
107
- minify: true,
108
- format: 'esm',
109
- target: 'browser',
110
- });
111
- }
112
-
113
- /**
114
- * Return a Preact component, mounted as an island in a <script> tag so it can be embedded into the page response.
97
+ * Island defaults
115
98
  */
116
- export async function createPreactIsland(file: string) {
117
- let filePath = file.replace('file://', '');
118
- const jsId = md5(filePath);
119
-
120
- // Add Preact to client import map if not already present
121
- if (!clientImportMap.has('preact')) {
122
- await copyPreactToPublicFolder();
123
- clientImportMap.set('preact', '' + PREACT_PUBLIC_FILE_PATH);
124
- clientImportMap.set('preact/compat', '' + PREACT_PUBLIC_FILE_PATH);
125
- clientImportMap.set('preact/hooks', '' + PREACT_PUBLIC_FILE_PATH);
126
- clientImportMap.set('preact/jsx-runtime', '' + PREACT_PUBLIC_FILE_PATH);
99
+ export const ISLAND_PUBLIC_PATH = '/_hs/js/islands';
100
+ export const ISLAND_DEFAULTS = () => ({
101
+ ssr: true,
102
+ });
103
+
104
+ export function renderIsland(Component: any, props: any, options = ISLAND_DEFAULTS()) {
105
+ // Render is an OPTIONAL override that allows you to render the island with your own logic
106
+ if (Component.__HS_ISLAND?.render) {
107
+ return Component.__HS_ISLAND.render(props, options);
127
108
  }
128
- if (!clientImportMap.has('react')) {
129
- clientImportMap.set('react', '.' + PREACT_PUBLIC_FILE_PATH);
130
- clientImportMap.set('react-dom', '.' + PREACT_PUBLIC_FILE_PATH);
131
- }
132
-
133
- let resultStr = 'import{h,render}from"preact";';
134
- const buildResult = await Bun.build({
135
- entrypoints: [filePath],
136
- minify: true,
137
- external: ['react', 'preact'],
138
- // @ts-ignore
139
- env: 'APP_PUBLIC_*', // Inlines any ENV that starts with 'APP_PUBLIC_'
140
- });
141
109
 
142
- for (const output of buildResult.outputs) {
143
- resultStr += await output.text(); // string
110
+ // If ssr is true, render the island with the ssr function
111
+ if (Component.__HS_ISLAND?.ssr && options.ssr) {
112
+ return Component.__HS_ISLAND.ssr(props);
144
113
  }
145
114
 
146
- // Find default export - this is our component
147
- const r = /export\{([a-zA-Z]+) as default\}/g;
148
- const matchExport = r.exec(resultStr);
149
-
150
- if (!matchExport) {
151
- throw new Error(
152
- 'File does not have a default export! Ensure a function has export default to use this.'
153
- );
115
+ // If ssr is false, render the island with the clientOnly function
116
+ if (Component.__HS_ISLAND?.clientOnly) {
117
+ return Component.__HS_ISLAND.clientOnly(props);
154
118
  }
155
119
 
156
- // Preact render/mount component
157
- const fn = matchExport[1];
158
- let _mounted = false;
159
-
160
- // Return HTML that will embed this component
161
- return (props: any) => {
162
- if (!_mounted) {
163
- _mounted = true;
164
- resultStr += `render(h(${fn}, ${JSON.stringify(props)}), document.getElementById("${jsId}"));`;
165
- }
166
- return html.raw(
167
- `<div id="${jsId}"></div><script type="module" data-source-id="${jsId}">${resultStr}</script>`
168
- );
169
- };
120
+ throw new Error(
121
+ `Module ${Component.name} was not loaded with an island plugin! Did you forget to install an island plugin and add it to the createServer() 'islandPlugins' config?`
122
+ );
170
123
  }
@@ -1,2 +1,3 @@
1
1
  export * from 'preact/compat';
2
- export { h, render } from 'preact';
2
+ export * from 'preact/hooks';
3
+ export { h, render, hydrate } from 'preact';
package/src/server.ts CHANGED
@@ -15,6 +15,8 @@ const CWD = process.cwd();
15
15
  */
16
16
  export type THSResponseTypes = HSHtml | Response | string | null;
17
17
  export type THSRouteHandler = (context: Context) => THSResponseTypes | Promise<THSResponseTypes>;
18
+ export type THSAPIResponseTypes = Response | Record<any, any> | void;
19
+ export type THSAPIRouteHandler = (context: Context) => THSResponseTypes | Promise<THSResponseTypes>;
18
20
 
19
21
  export type THSRoute = {
20
22
  _kind: 'hsRoute';
@@ -26,6 +28,10 @@ export type THSRoute = {
26
28
  run: (method: string, context: Context) => Promise<Response>;
27
29
  };
28
30
 
31
+ export function createConfig(config: THSServerConfig): THSServerConfig {
32
+ return config;
33
+ }
34
+
29
35
  /**
30
36
  * Define a route that can handle a direct HTTP request.
31
37
  * Route handlers should return a HSHtml or Response object
@@ -100,8 +106,8 @@ export function createRoute(handler?: THSRouteHandler): THSRoute {
100
106
  * Create new API Route
101
107
  * API Route handlers should return a JSON object or a Response
102
108
  */
103
- export function createAPIRoute(handler?: THSRouteHandler): THSRoute {
104
- let _handlers: Record<string, THSRouteHandler> = {};
109
+ export function createAPIRoute(handler?: THSAPIRouteHandler): THSRoute {
110
+ let _handlers: Record<string, THSAPIRouteHandler> = {};
105
111
 
106
112
  if (handler) {
107
113
  _handlers['GET'] = handler;
@@ -109,23 +115,23 @@ export function createAPIRoute(handler?: THSRouteHandler): THSRoute {
109
115
 
110
116
  const api: THSRoute = {
111
117
  _kind: 'hsRoute',
112
- get(handler: THSRouteHandler) {
118
+ get(handler: THSAPIRouteHandler) {
113
119
  _handlers['GET'] = handler;
114
120
  return api;
115
121
  },
116
- post(handler: THSRouteHandler) {
122
+ post(handler: THSAPIRouteHandler) {
117
123
  _handlers['POST'] = handler;
118
124
  return api;
119
125
  },
120
- put(handler: THSRouteHandler) {
126
+ put(handler: THSAPIRouteHandler) {
121
127
  _handlers['PUT'] = handler;
122
128
  return api;
123
129
  },
124
- delete(handler: THSRouteHandler) {
130
+ delete(handler: THSAPIRouteHandler) {
125
131
  _handlers['DELETE'] = handler;
126
132
  return api;
127
133
  },
128
- patch(handler: THSRouteHandler) {
134
+ patch(handler: THSAPIRouteHandler) {
129
135
  _handlers['PATCH'] = handler;
130
136
  return api;
131
137
  },
@@ -202,6 +208,14 @@ export function isRunnableRoute(route: unknown): boolean {
202
208
  return typeof route === 'object' && 'run' in route;
203
209
  }
204
210
 
211
+ /**
212
+ * Create a layout for a Hyperspan app. Passthrough for now.
213
+ * Future intent is to be able to conditionally render a layout for full page content vs. partial content.
214
+ */
215
+ export function createLayout<T>(layout: (props: T) => HSHtml | Promise<HSHtml>) {
216
+ return layout;
217
+ }
218
+
205
219
  /**
206
220
  * Basic error handling
207
221
  * @TODO: Should check for and load user-customizeable template with special name (app/__error.ts ?)
@@ -224,6 +238,7 @@ export type THSServerConfig = {
224
238
  appDir: string;
225
239
  staticFileRoot: string;
226
240
  rewrites?: Array<{ source: string; destination: string }>;
241
+ islandPlugins?: Array<any>; // Loaders for client islands
227
242
  // For customizing the routes and adding your own...
228
243
  beforeRoutesAdded?: (app: Hono) => void;
229
244
  afterRoutesAdded?: (app: Hono) => void;
@@ -242,7 +257,6 @@ const ROUTE_SEGMENT = /(\[[a-zA-Z_\.]+\])/g;
242
257
  export async function buildRoutes(config: THSServerConfig): Promise<THSRouteMap[]> {
243
258
  // Walk all pages and add them as routes
244
259
  const routesDir = join(config.appDir, 'routes');
245
- console.log(routesDir);
246
260
  const files = await readdir(routesDir, { recursive: true });
247
261
  const routes: THSRouteMap[] = [];
248
262