@hyperspan/framework 0.1.4 → 0.1.6
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/build.ts +8 -21
- package/dist/assets.js +2 -165
- package/dist/server.js +4 -5
- package/package.json +3 -2
- package/src/actions.test.ts +9 -10
- package/src/actions.ts +16 -13
- package/src/assets.ts +2 -47
- package/src/server.ts +20 -17
- package/dist/assets.d.ts +0 -46
- package/dist/server.d.ts +0 -98
package/build.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {build} from 'bun';
|
|
2
|
-
import dts from 'bun-plugin-dts';
|
|
3
2
|
|
|
4
3
|
const entrypoints = ['./src/server.ts', './src/assets.ts'];
|
|
5
4
|
const external = ['@hyperspan/html'];
|
|
@@ -7,23 +6,11 @@ const outdir = './dist';
|
|
|
7
6
|
const target = 'node';
|
|
8
7
|
const splitting = true;
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}),
|
|
19
|
-
|
|
20
|
-
// Build type files for TypeScript
|
|
21
|
-
build({
|
|
22
|
-
entrypoints,
|
|
23
|
-
external,
|
|
24
|
-
outdir,
|
|
25
|
-
target,
|
|
26
|
-
splitting,
|
|
27
|
-
plugins: [dts()],
|
|
28
|
-
}),
|
|
29
|
-
]);
|
|
9
|
+
// Build JS
|
|
10
|
+
await build({
|
|
11
|
+
entrypoints,
|
|
12
|
+
external,
|
|
13
|
+
outdir,
|
|
14
|
+
target,
|
|
15
|
+
splitting,
|
|
16
|
+
});
|
package/dist/assets.js
CHANGED
|
@@ -1,145 +1,10 @@
|
|
|
1
1
|
// src/assets.ts
|
|
2
2
|
import { html } from "@hyperspan/html";
|
|
3
|
-
|
|
4
|
-
// src/clientjs/md5.js
|
|
5
|
-
function md5cycle(x, k) {
|
|
6
|
-
var a = x[0], b = x[1], c = x[2], d = x[3];
|
|
7
|
-
a = ff(a, b, c, d, k[0], 7, -680876936);
|
|
8
|
-
d = ff(d, a, b, c, k[1], 12, -389564586);
|
|
9
|
-
c = ff(c, d, a, b, k[2], 17, 606105819);
|
|
10
|
-
b = ff(b, c, d, a, k[3], 22, -1044525330);
|
|
11
|
-
a = ff(a, b, c, d, k[4], 7, -176418897);
|
|
12
|
-
d = ff(d, a, b, c, k[5], 12, 1200080426);
|
|
13
|
-
c = ff(c, d, a, b, k[6], 17, -1473231341);
|
|
14
|
-
b = ff(b, c, d, a, k[7], 22, -45705983);
|
|
15
|
-
a = ff(a, b, c, d, k[8], 7, 1770035416);
|
|
16
|
-
d = ff(d, a, b, c, k[9], 12, -1958414417);
|
|
17
|
-
c = ff(c, d, a, b, k[10], 17, -42063);
|
|
18
|
-
b = ff(b, c, d, a, k[11], 22, -1990404162);
|
|
19
|
-
a = ff(a, b, c, d, k[12], 7, 1804603682);
|
|
20
|
-
d = ff(d, a, b, c, k[13], 12, -40341101);
|
|
21
|
-
c = ff(c, d, a, b, k[14], 17, -1502002290);
|
|
22
|
-
b = ff(b, c, d, a, k[15], 22, 1236535329);
|
|
23
|
-
a = gg(a, b, c, d, k[1], 5, -165796510);
|
|
24
|
-
d = gg(d, a, b, c, k[6], 9, -1069501632);
|
|
25
|
-
c = gg(c, d, a, b, k[11], 14, 643717713);
|
|
26
|
-
b = gg(b, c, d, a, k[0], 20, -373897302);
|
|
27
|
-
a = gg(a, b, c, d, k[5], 5, -701558691);
|
|
28
|
-
d = gg(d, a, b, c, k[10], 9, 38016083);
|
|
29
|
-
c = gg(c, d, a, b, k[15], 14, -660478335);
|
|
30
|
-
b = gg(b, c, d, a, k[4], 20, -405537848);
|
|
31
|
-
a = gg(a, b, c, d, k[9], 5, 568446438);
|
|
32
|
-
d = gg(d, a, b, c, k[14], 9, -1019803690);
|
|
33
|
-
c = gg(c, d, a, b, k[3], 14, -187363961);
|
|
34
|
-
b = gg(b, c, d, a, k[8], 20, 1163531501);
|
|
35
|
-
a = gg(a, b, c, d, k[13], 5, -1444681467);
|
|
36
|
-
d = gg(d, a, b, c, k[2], 9, -51403784);
|
|
37
|
-
c = gg(c, d, a, b, k[7], 14, 1735328473);
|
|
38
|
-
b = gg(b, c, d, a, k[12], 20, -1926607734);
|
|
39
|
-
a = hh(a, b, c, d, k[5], 4, -378558);
|
|
40
|
-
d = hh(d, a, b, c, k[8], 11, -2022574463);
|
|
41
|
-
c = hh(c, d, a, b, k[11], 16, 1839030562);
|
|
42
|
-
b = hh(b, c, d, a, k[14], 23, -35309556);
|
|
43
|
-
a = hh(a, b, c, d, k[1], 4, -1530992060);
|
|
44
|
-
d = hh(d, a, b, c, k[4], 11, 1272893353);
|
|
45
|
-
c = hh(c, d, a, b, k[7], 16, -155497632);
|
|
46
|
-
b = hh(b, c, d, a, k[10], 23, -1094730640);
|
|
47
|
-
a = hh(a, b, c, d, k[13], 4, 681279174);
|
|
48
|
-
d = hh(d, a, b, c, k[0], 11, -358537222);
|
|
49
|
-
c = hh(c, d, a, b, k[3], 16, -722521979);
|
|
50
|
-
b = hh(b, c, d, a, k[6], 23, 76029189);
|
|
51
|
-
a = hh(a, b, c, d, k[9], 4, -640364487);
|
|
52
|
-
d = hh(d, a, b, c, k[12], 11, -421815835);
|
|
53
|
-
c = hh(c, d, a, b, k[15], 16, 530742520);
|
|
54
|
-
b = hh(b, c, d, a, k[2], 23, -995338651);
|
|
55
|
-
a = ii(a, b, c, d, k[0], 6, -198630844);
|
|
56
|
-
d = ii(d, a, b, c, k[7], 10, 1126891415);
|
|
57
|
-
c = ii(c, d, a, b, k[14], 15, -1416354905);
|
|
58
|
-
b = ii(b, c, d, a, k[5], 21, -57434055);
|
|
59
|
-
a = ii(a, b, c, d, k[12], 6, 1700485571);
|
|
60
|
-
d = ii(d, a, b, c, k[3], 10, -1894986606);
|
|
61
|
-
c = ii(c, d, a, b, k[10], 15, -1051523);
|
|
62
|
-
b = ii(b, c, d, a, k[1], 21, -2054922799);
|
|
63
|
-
a = ii(a, b, c, d, k[8], 6, 1873313359);
|
|
64
|
-
d = ii(d, a, b, c, k[15], 10, -30611744);
|
|
65
|
-
c = ii(c, d, a, b, k[6], 15, -1560198380);
|
|
66
|
-
b = ii(b, c, d, a, k[13], 21, 1309151649);
|
|
67
|
-
a = ii(a, b, c, d, k[4], 6, -145523070);
|
|
68
|
-
d = ii(d, a, b, c, k[11], 10, -1120210379);
|
|
69
|
-
c = ii(c, d, a, b, k[2], 15, 718787259);
|
|
70
|
-
b = ii(b, c, d, a, k[9], 21, -343485551);
|
|
71
|
-
x[0] = add32(a, x[0]);
|
|
72
|
-
x[1] = add32(b, x[1]);
|
|
73
|
-
x[2] = add32(c, x[2]);
|
|
74
|
-
x[3] = add32(d, x[3]);
|
|
75
|
-
}
|
|
76
|
-
function cmn(q, a, b, x, s, t) {
|
|
77
|
-
a = add32(add32(a, q), add32(x, t));
|
|
78
|
-
return add32(a << s | a >>> 32 - s, b);
|
|
79
|
-
}
|
|
80
|
-
function ff(a, b, c, d, x, s, t) {
|
|
81
|
-
return cmn(b & c | ~b & d, a, b, x, s, t);
|
|
82
|
-
}
|
|
83
|
-
function gg(a, b, c, d, x, s, t) {
|
|
84
|
-
return cmn(b & d | c & ~d, a, b, x, s, t);
|
|
85
|
-
}
|
|
86
|
-
function hh(a, b, c, d, x, s, t) {
|
|
87
|
-
return cmn(b ^ c ^ d, a, b, x, s, t);
|
|
88
|
-
}
|
|
89
|
-
function ii(a, b, c, d, x, s, t) {
|
|
90
|
-
return cmn(c ^ (b | ~d), a, b, x, s, t);
|
|
91
|
-
}
|
|
92
|
-
function md51(s) {
|
|
93
|
-
var txt = "";
|
|
94
|
-
var n = s.length, state = [1732584193, -271733879, -1732584194, 271733878], i;
|
|
95
|
-
for (i = 64;i <= s.length; i += 64) {
|
|
96
|
-
md5cycle(state, md5blk(s.substring(i - 64, i)));
|
|
97
|
-
}
|
|
98
|
-
s = s.substring(i - 64);
|
|
99
|
-
var tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
100
|
-
for (i = 0;i < s.length; i++)
|
|
101
|
-
tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3);
|
|
102
|
-
tail[i >> 2] |= 128 << (i % 4 << 3);
|
|
103
|
-
if (i > 55) {
|
|
104
|
-
md5cycle(state, tail);
|
|
105
|
-
for (i = 0;i < 16; i++)
|
|
106
|
-
tail[i] = 0;
|
|
107
|
-
}
|
|
108
|
-
tail[14] = n * 8;
|
|
109
|
-
md5cycle(state, tail);
|
|
110
|
-
return state;
|
|
111
|
-
}
|
|
112
|
-
function md5blk(s) {
|
|
113
|
-
var md5blks = [], i;
|
|
114
|
-
for (i = 0;i < 64; i += 4) {
|
|
115
|
-
md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24);
|
|
116
|
-
}
|
|
117
|
-
return md5blks;
|
|
118
|
-
}
|
|
119
|
-
var hex_chr = "0123456789abcdef".split("");
|
|
120
|
-
function rhex(n) {
|
|
121
|
-
var s = "", j = 0;
|
|
122
|
-
for (;j < 4; j++)
|
|
123
|
-
s += hex_chr[n >> j * 8 + 4 & 15] + hex_chr[n >> j * 8 & 15];
|
|
124
|
-
return s;
|
|
125
|
-
}
|
|
126
|
-
function hex(x) {
|
|
127
|
-
for (var i = 0;i < x.length; i++)
|
|
128
|
-
x[i] = rhex(x[i]);
|
|
129
|
-
return x.join("");
|
|
130
|
-
}
|
|
131
|
-
function add32(a, b) {
|
|
132
|
-
return a + b & 4294967295;
|
|
133
|
-
}
|
|
134
|
-
function md5(s) {
|
|
135
|
-
return hex(md51(s));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// src/assets.ts
|
|
139
3
|
import { readdir } from "node:fs/promises";
|
|
140
4
|
import { resolve } from "node:path";
|
|
141
5
|
var IS_PROD = false;
|
|
142
6
|
var PWD = import.meta.dir;
|
|
7
|
+
var clientImportMap = new Map;
|
|
143
8
|
var clientJSFiles = new Map;
|
|
144
9
|
async function buildClientJS() {
|
|
145
10
|
const sourceFile = resolve(PWD, "../", "./src/clientjs/hyperspan-client.ts");
|
|
@@ -197,39 +62,11 @@ function hyperspanScriptTags() {
|
|
|
197
62
|
></script>`)}
|
|
198
63
|
`;
|
|
199
64
|
}
|
|
200
|
-
async function createPreactIsland(file) {
|
|
201
|
-
let filePath = file.replace("file://", "");
|
|
202
|
-
let resultStr = 'import{h,render}from"preact";';
|
|
203
|
-
const build = await Bun.build({
|
|
204
|
-
entrypoints: [filePath],
|
|
205
|
-
minify: true,
|
|
206
|
-
external: ["react", "preact"],
|
|
207
|
-
env: "APP_PUBLIC_*"
|
|
208
|
-
});
|
|
209
|
-
for (const output of build.outputs) {
|
|
210
|
-
resultStr += await output.text();
|
|
211
|
-
}
|
|
212
|
-
const r = /export\{([a-zA-Z]+) as default\}/g;
|
|
213
|
-
const matchExport = r.exec(resultStr);
|
|
214
|
-
const jsId = md5(resultStr);
|
|
215
|
-
if (!matchExport) {
|
|
216
|
-
throw new Error("File does not have a default export! Ensure a function has export default to use this.");
|
|
217
|
-
}
|
|
218
|
-
const fn = matchExport[1];
|
|
219
|
-
let _mounted = false;
|
|
220
|
-
return (props) => {
|
|
221
|
-
if (!_mounted) {
|
|
222
|
-
_mounted = true;
|
|
223
|
-
resultStr += `render(h(${fn}, ${JSON.stringify(props)}), document.getElementById("${jsId}"));`;
|
|
224
|
-
}
|
|
225
|
-
return html.raw(`<div id="${jsId}"></div><script type="module" data-source-id="${jsId}">${resultStr}</script>`);
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
65
|
export {
|
|
229
66
|
hyperspanStyleTags,
|
|
230
67
|
hyperspanScriptTags,
|
|
231
|
-
createPreactIsland,
|
|
232
68
|
clientJSFiles,
|
|
69
|
+
clientImportMap,
|
|
233
70
|
clientCSSFiles,
|
|
234
71
|
buildClientJS,
|
|
235
72
|
buildClientCSS
|
package/dist/server.js
CHANGED
|
@@ -1931,7 +1931,7 @@ function createAPIRoute(handler) {
|
|
|
1931
1931
|
return api;
|
|
1932
1932
|
}
|
|
1933
1933
|
function getRunnableRoute(route) {
|
|
1934
|
-
if (
|
|
1934
|
+
if (isRunnableRoute(route)) {
|
|
1935
1935
|
return route;
|
|
1936
1936
|
}
|
|
1937
1937
|
const kind = typeof route;
|
|
@@ -1943,7 +1943,7 @@ function getRunnableRoute(route) {
|
|
|
1943
1943
|
}
|
|
1944
1944
|
throw new Error('Route not runnable. Use "export default createRoute()" to create a Hyperspan route.');
|
|
1945
1945
|
}
|
|
1946
|
-
function
|
|
1946
|
+
function isRunnableRoute(route) {
|
|
1947
1947
|
return typeof route === "object" && "run" in route;
|
|
1948
1948
|
}
|
|
1949
1949
|
async function showErrorReponse(context, err) {
|
|
@@ -2024,8 +2024,7 @@ async function createServer(config) {
|
|
|
2024
2024
|
const fullRouteFile = join(CWD, route.file);
|
|
2025
2025
|
const routePattern = normalizePath(route.route);
|
|
2026
2026
|
routeMap.push({ route: routePattern, file: route.file });
|
|
2027
|
-
|
|
2028
|
-
app.all(routePattern, createRouteFromModule(routeModule));
|
|
2027
|
+
app.all(routePattern, createRouteFromModule(await import(fullRouteFile)));
|
|
2029
2028
|
}
|
|
2030
2029
|
if (routeMap.length === 0) {
|
|
2031
2030
|
app.get("/", (context) => {
|
|
@@ -2085,7 +2084,7 @@ function normalizePath(urlPath) {
|
|
|
2085
2084
|
}
|
|
2086
2085
|
export {
|
|
2087
2086
|
normalizePath,
|
|
2088
|
-
|
|
2087
|
+
isRunnableRoute,
|
|
2089
2088
|
getRunnableRoute,
|
|
2090
2089
|
createServer,
|
|
2091
2090
|
createRouteFromModule,
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperspan/framework",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Hyperspan Web Framework",
|
|
5
5
|
"main": "dist/server.js",
|
|
6
|
+
"types": "src/server.ts",
|
|
6
7
|
"public": true,
|
|
7
8
|
"publishConfig": {
|
|
8
9
|
"access": "public"
|
|
@@ -63,7 +64,7 @@
|
|
|
63
64
|
"typescript": "^5.0.0"
|
|
64
65
|
},
|
|
65
66
|
"dependencies": {
|
|
66
|
-
"@hyperspan/html": "^0.1.
|
|
67
|
+
"@hyperspan/html": "^0.1.5",
|
|
67
68
|
"@preact/compat": "^18.3.1",
|
|
68
69
|
"hono": "^4.7.4",
|
|
69
70
|
"isbot": "^5.1.25",
|
package/src/actions.test.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import z from 'zod';
|
|
2
|
-
import { createAction
|
|
2
|
+
import { createAction } from './actions';
|
|
3
3
|
import { describe, it, expect } from 'bun:test';
|
|
4
4
|
import { html, render, type TmplHtml } from '@hyperspan/html';
|
|
5
5
|
import type { Context } from 'hono';
|
|
6
6
|
|
|
7
7
|
describe('createAction', () => {
|
|
8
|
-
const formWithNameOnly = ({ data }:
|
|
8
|
+
const formWithNameOnly = ({ data }: { data?: { name: string } }) => {
|
|
9
9
|
return html`
|
|
10
10
|
<form>
|
|
11
11
|
<p>
|
|
12
12
|
Name:
|
|
13
|
-
<input type="text" name="name" value="${data
|
|
13
|
+
<input type="text" name="name" value="${data?.name || ''}" />
|
|
14
14
|
</p>
|
|
15
15
|
<button type="submit">Submit</button>
|
|
16
16
|
</form>
|
|
@@ -22,7 +22,7 @@ describe('createAction', () => {
|
|
|
22
22
|
const schema = z.object({
|
|
23
23
|
name: z.string(),
|
|
24
24
|
});
|
|
25
|
-
const action = createAction(schema
|
|
25
|
+
const action = createAction(schema, formWithNameOnly);
|
|
26
26
|
|
|
27
27
|
const formResponse = render(action.render({ data: { name: 'John' } }) as TmplHtml);
|
|
28
28
|
expect(formResponse).toContain('value="John"');
|
|
@@ -34,10 +34,9 @@ describe('createAction', () => {
|
|
|
34
34
|
const schema = z.object({
|
|
35
35
|
name: z.string().nonempty(),
|
|
36
36
|
});
|
|
37
|
-
const action = createAction(schema)
|
|
38
|
-
.
|
|
39
|
-
|
|
40
|
-
return html`<div>Thanks for submitting the form, ${data.name}!</div>`;
|
|
37
|
+
const action = createAction(schema, formWithNameOnly)
|
|
38
|
+
.post((c, { data }) => {
|
|
39
|
+
return html`<div>Thanks for submitting the form, ${data?.name}!</div>`;
|
|
41
40
|
})
|
|
42
41
|
.error((c, { error }) => {
|
|
43
42
|
return html`<div>There was an error! ${error?.message}</div>`;
|
|
@@ -68,8 +67,8 @@ describe('createAction', () => {
|
|
|
68
67
|
});
|
|
69
68
|
const action = createAction(schema)
|
|
70
69
|
.form(formWithNameOnly)
|
|
71
|
-
.
|
|
72
|
-
return html`<div>Thanks for submitting the form, ${data
|
|
70
|
+
.post((c, { data }) => {
|
|
71
|
+
return html`<div>Thanks for submitting the form, ${data?.name}!</div>`;
|
|
73
72
|
})
|
|
74
73
|
.error((c, { error }) => {
|
|
75
74
|
return html`<div>There was an error! ${error?.message}</div>`;
|
package/src/actions.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { html } from '@hyperspan/html';
|
|
1
|
+
import { html, TmplHtml } from '@hyperspan/html';
|
|
2
2
|
import * as z from 'zod';
|
|
3
3
|
import { HTTPException } from 'hono/http-exception';
|
|
4
4
|
|
|
@@ -20,21 +20,24 @@ import type { Context } from 'hono';
|
|
|
20
20
|
*/
|
|
21
21
|
export interface HSAction<T extends z.ZodTypeAny> {
|
|
22
22
|
_kind: string;
|
|
23
|
-
form(renderForm: (data: z.infer<T>) =>
|
|
24
|
-
|
|
23
|
+
form(renderForm: ({ data }: { data?: z.infer<T> }) => TmplHtml): HSAction<T>;
|
|
24
|
+
post(handler: (c: Context, { data }: { data?: z.infer<T> }) => THSResponseTypes): HSAction<T>;
|
|
25
25
|
error(
|
|
26
26
|
handler: (
|
|
27
27
|
c: Context,
|
|
28
|
-
{ data, error }: { data
|
|
28
|
+
{ data, error }: { data?: z.infer<T>; error?: z.ZodError | Error }
|
|
29
29
|
) => THSResponseTypes
|
|
30
30
|
): HSAction<T>;
|
|
31
|
-
render(props?: { data
|
|
31
|
+
render(props?: { data?: z.infer<T>; error?: z.ZodError | Error }): THSResponseTypes;
|
|
32
32
|
run(method: 'GET' | 'POST', c: Context): Promise<THSResponseTypes>;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export function createAction<T extends z.ZodTypeAny>(
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
export function createAction<T extends z.ZodTypeAny>(
|
|
36
|
+
schema: T | null = null,
|
|
37
|
+
form: Parameters<HSAction<T>['form']>[0] | null = null
|
|
38
|
+
) {
|
|
39
|
+
let _handler: Parameters<HSAction<T>['post']>[0] | null = null,
|
|
40
|
+
_form: Parameters<HSAction<T>['form']>[0] | null = form,
|
|
38
41
|
_errorHandler: Parameters<HSAction<T>['error']>[0] | null = null;
|
|
39
42
|
|
|
40
43
|
const api: HSAction<T> = {
|
|
@@ -50,7 +53,7 @@ export function createAction<T extends z.ZodTypeAny>(schema: T | null = null) {
|
|
|
50
53
|
* Returns result from form processing if successful
|
|
51
54
|
* Re-renders form with data and error information otherwise
|
|
52
55
|
*/
|
|
53
|
-
|
|
56
|
+
post(handler) {
|
|
54
57
|
_handler = handler;
|
|
55
58
|
return api;
|
|
56
59
|
},
|
|
@@ -63,8 +66,8 @@ export function createAction<T extends z.ZodTypeAny>(schema: T | null = null) {
|
|
|
63
66
|
/**
|
|
64
67
|
* Get form renderer method
|
|
65
68
|
*/
|
|
66
|
-
render(data) {
|
|
67
|
-
const form = _form ? _form(
|
|
69
|
+
render(formState?: { data?: z.infer<T>; error?: z.ZodError | Error }) {
|
|
70
|
+
const form = _form ? _form(formState || {}) : null;
|
|
68
71
|
return form ? html`<hs-action>${form}</hs-action>` : null;
|
|
69
72
|
},
|
|
70
73
|
|
|
@@ -86,7 +89,7 @@ export function createAction<T extends z.ZodTypeAny>(schema: T | null = null) {
|
|
|
86
89
|
const formData = await c.req.formData();
|
|
87
90
|
const jsonData = formDataToJSON(formData);
|
|
88
91
|
const schemaData = schema ? schema.safeParse(jsonData) : null;
|
|
89
|
-
const data = schemaData?.success ? (schemaData.data as z.infer<T>) :
|
|
92
|
+
const data = schemaData?.success ? (schemaData.data as z.infer<T>) : undefined;
|
|
90
93
|
let error: z.ZodError | Error | null = null;
|
|
91
94
|
|
|
92
95
|
try {
|
|
@@ -95,7 +98,7 @@ export function createAction<T extends z.ZodTypeAny>(schema: T | null = null) {
|
|
|
95
98
|
}
|
|
96
99
|
|
|
97
100
|
if (!_handler) {
|
|
98
|
-
throw new Error('Action handler not set! Every action must have a handler.');
|
|
101
|
+
throw new Error('Action POST handler not set! Every action must have a POST handler.');
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
return _handler(c, { data });
|
package/src/assets.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { html } from '@hyperspan/html';
|
|
2
|
-
import { md5 } from './clientjs/md5';
|
|
3
2
|
import { readdir } from 'node:fs/promises';
|
|
4
3
|
import { resolve } from 'node:path';
|
|
5
4
|
|
|
6
5
|
const IS_PROD = process.env.NODE_ENV === 'production';
|
|
7
6
|
const PWD = import.meta.dir;
|
|
8
7
|
|
|
8
|
+
export const clientImportMap = new Map<string, string>();
|
|
9
|
+
|
|
9
10
|
/**
|
|
10
11
|
* Build client JS for end users (minimal JS for Hyperspan to work)
|
|
11
12
|
*/
|
|
@@ -94,49 +95,3 @@ export function hyperspanScriptTags() {
|
|
|
94
95
|
)}
|
|
95
96
|
`;
|
|
96
97
|
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Return a Preact component, mounted as an island in a <script> tag so it can be embedded into the page response.
|
|
100
|
-
*/
|
|
101
|
-
export async function createPreactIsland(file: string) {
|
|
102
|
-
let filePath = file.replace('file://', '');
|
|
103
|
-
|
|
104
|
-
let resultStr = 'import{h,render}from"preact";';
|
|
105
|
-
const build = await Bun.build({
|
|
106
|
-
entrypoints: [filePath],
|
|
107
|
-
minify: true,
|
|
108
|
-
external: ['react', 'preact'],
|
|
109
|
-
// @ts-ignore
|
|
110
|
-
env: 'APP_PUBLIC_*', // Inlines any ENV that starts with 'APP_PUBLIC_'
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
for (const output of build.outputs) {
|
|
114
|
-
resultStr += await output.text(); // string
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Find default export - this is our component
|
|
118
|
-
const r = /export\{([a-zA-Z]+) as default\}/g;
|
|
119
|
-
const matchExport = r.exec(resultStr);
|
|
120
|
-
const jsId = md5(resultStr);
|
|
121
|
-
|
|
122
|
-
if (!matchExport) {
|
|
123
|
-
throw new Error(
|
|
124
|
-
'File does not have a default export! Ensure a function has export default to use this.'
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Preact render/mount component
|
|
129
|
-
const fn = matchExport[1];
|
|
130
|
-
let _mounted = false;
|
|
131
|
-
|
|
132
|
-
// Return HTML that will embed this component
|
|
133
|
-
return (props: any) => {
|
|
134
|
-
if (!_mounted) {
|
|
135
|
-
_mounted = true;
|
|
136
|
-
resultStr += `render(h(${fn}, ${JSON.stringify(props)}), document.getElementById("${jsId}"));`;
|
|
137
|
-
}
|
|
138
|
-
return html.raw(
|
|
139
|
-
`<div id="${jsId}"></div><script type="module" data-source-id="${jsId}">${resultStr}</script>`
|
|
140
|
-
);
|
|
141
|
-
};
|
|
142
|
-
}
|
package/src/server.ts
CHANGED
|
@@ -3,11 +3,9 @@ import { basename, extname, join } from 'node:path';
|
|
|
3
3
|
import { TmplHtml, html, renderStream, renderAsync, render } from '@hyperspan/html';
|
|
4
4
|
import { isbot } from 'isbot';
|
|
5
5
|
import { buildClientJS, buildClientCSS } from './assets';
|
|
6
|
-
import { Hono } from 'hono';
|
|
6
|
+
import { Hono, type Context } from 'hono';
|
|
7
7
|
import { serveStatic } from 'hono/bun';
|
|
8
|
-
import type { Context, Handler } from 'hono';
|
|
9
8
|
import { HTTPException } from 'hono/http-exception';
|
|
10
|
-
import { create } from 'node:domain';
|
|
11
9
|
|
|
12
10
|
export const IS_PROD = process.env.NODE_ENV === 'production';
|
|
13
11
|
const CWD = process.cwd();
|
|
@@ -18,19 +16,28 @@ const CWD = process.cwd();
|
|
|
18
16
|
export type THSResponseTypes = TmplHtml | Response | string | null;
|
|
19
17
|
export type THSRouteHandler = (context: Context) => THSResponseTypes | Promise<THSResponseTypes>;
|
|
20
18
|
|
|
19
|
+
export type THSRoute = {
|
|
20
|
+
_kind: 'hsRoute';
|
|
21
|
+
get: (handler: THSRouteHandler) => THSRoute;
|
|
22
|
+
post: (handler: THSRouteHandler) => THSRoute;
|
|
23
|
+
put: (handler: THSRouteHandler) => THSRoute;
|
|
24
|
+
delete: (handler: THSRouteHandler) => THSRoute;
|
|
25
|
+
patch: (handler: THSRouteHandler) => THSRoute;
|
|
26
|
+
run: (method: string, context: Context) => Promise<Response>;
|
|
27
|
+
};
|
|
28
|
+
|
|
21
29
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* Route handlers should return a Response or TmplHtml object
|
|
30
|
+
* Define a route that can handle a direct HTTP request.
|
|
31
|
+
* Route handlers should return a TmplHtml or Response object
|
|
25
32
|
*/
|
|
26
|
-
export function createRoute(handler?: THSRouteHandler) {
|
|
33
|
+
export function createRoute(handler?: THSRouteHandler): THSRoute {
|
|
27
34
|
let _handlers: Record<string, THSRouteHandler> = {};
|
|
28
35
|
|
|
29
36
|
if (handler) {
|
|
30
37
|
_handlers['GET'] = handler;
|
|
31
38
|
}
|
|
32
39
|
|
|
33
|
-
const api = {
|
|
40
|
+
const api: THSRoute = {
|
|
34
41
|
_kind: 'hsRoute',
|
|
35
42
|
get(handler: THSRouteHandler) {
|
|
36
43
|
_handlers['GET'] = handler;
|
|
@@ -95,20 +102,19 @@ export function createRoute(handler?: THSRouteHandler) {
|
|
|
95
102
|
|
|
96
103
|
return api;
|
|
97
104
|
}
|
|
98
|
-
export type THSRoute = ReturnType<typeof createRoute>;
|
|
99
105
|
|
|
100
106
|
/**
|
|
101
107
|
* Create new API Route
|
|
102
108
|
* API Route handlers should return a JSON object or a Response
|
|
103
109
|
*/
|
|
104
|
-
export function createAPIRoute(handler?: THSRouteHandler) {
|
|
110
|
+
export function createAPIRoute(handler?: THSRouteHandler): THSRoute {
|
|
105
111
|
let _handlers: Record<string, THSRouteHandler> = {};
|
|
106
112
|
|
|
107
113
|
if (handler) {
|
|
108
114
|
_handlers['GET'] = handler;
|
|
109
115
|
}
|
|
110
116
|
|
|
111
|
-
const api = {
|
|
117
|
+
const api: THSRoute = {
|
|
112
118
|
_kind: 'hsRoute',
|
|
113
119
|
get(handler: THSRouteHandler) {
|
|
114
120
|
_handlers['GET'] = handler;
|
|
@@ -168,7 +174,6 @@ export function createAPIRoute(handler?: THSRouteHandler) {
|
|
|
168
174
|
|
|
169
175
|
return api;
|
|
170
176
|
}
|
|
171
|
-
export type THSAPIRoute = ReturnType<typeof createAPIRoute>;
|
|
172
177
|
|
|
173
178
|
/**
|
|
174
179
|
* Get a Hyperspan runnable route from a module import
|
|
@@ -176,7 +181,7 @@ export type THSAPIRoute = ReturnType<typeof createAPIRoute>;
|
|
|
176
181
|
*/
|
|
177
182
|
export function getRunnableRoute(route: unknown): THSRoute {
|
|
178
183
|
// Runnable already? Just return it
|
|
179
|
-
if (
|
|
184
|
+
if (isRunnableRoute(route)) {
|
|
180
185
|
return route as THSRoute;
|
|
181
186
|
}
|
|
182
187
|
|
|
@@ -199,7 +204,7 @@ export function getRunnableRoute(route: unknown): THSRoute {
|
|
|
199
204
|
);
|
|
200
205
|
}
|
|
201
206
|
|
|
202
|
-
export function
|
|
207
|
+
export function isRunnableRoute(route: unknown): boolean {
|
|
203
208
|
// @ts-ignore
|
|
204
209
|
return typeof route === 'object' && 'run' in route;
|
|
205
210
|
}
|
|
@@ -337,9 +342,7 @@ export async function createServer(config: THSServerConfig): Promise<Hono> {
|
|
|
337
342
|
routeMap.push({ route: routePattern, file: route.file });
|
|
338
343
|
|
|
339
344
|
// Import route
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
app.all(routePattern, createRouteFromModule(routeModule));
|
|
345
|
+
app.all(routePattern, createRouteFromModule(await import(fullRouteFile)));
|
|
343
346
|
}
|
|
344
347
|
|
|
345
348
|
// Help route if no routes found
|
package/dist/assets.d.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
// Generated by dts-bundle-generator v9.5.1
|
|
2
|
-
|
|
3
|
-
declare class TmplHtml {
|
|
4
|
-
_kind: string;
|
|
5
|
-
content: string;
|
|
6
|
-
asyncContent: Array<{
|
|
7
|
-
id: string;
|
|
8
|
-
promise: Promise<{
|
|
9
|
-
id: string;
|
|
10
|
-
value: unknown;
|
|
11
|
-
}>;
|
|
12
|
-
}>;
|
|
13
|
-
constructor(props: Pick<TmplHtml, "content" | "asyncContent">);
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Build client JS for end users (minimal JS for Hyperspan to work)
|
|
17
|
-
*/
|
|
18
|
-
export declare const clientJSFiles: Map<string, {
|
|
19
|
-
src: string;
|
|
20
|
-
type?: string;
|
|
21
|
-
}>;
|
|
22
|
-
export declare function buildClientJS(): Promise<void>;
|
|
23
|
-
/**
|
|
24
|
-
* Find client CSS file built for end users
|
|
25
|
-
* @TODO: Build this in code here vs. relying on tailwindcss CLI tool from package scripts
|
|
26
|
-
*/
|
|
27
|
-
export declare const clientCSSFiles: Map<string, string>;
|
|
28
|
-
export declare function buildClientCSS(): Promise<string | undefined>;
|
|
29
|
-
/**
|
|
30
|
-
* Output HTML style tag for Hyperspan app
|
|
31
|
-
*/
|
|
32
|
-
export declare function hyperspanStyleTags(): TmplHtml;
|
|
33
|
-
/**
|
|
34
|
-
* Output HTML script tag for Hyperspan app
|
|
35
|
-
* Required for functioning streaming so content can pop into place properly once ready
|
|
36
|
-
*/
|
|
37
|
-
export declare function hyperspanScriptTags(): TmplHtml;
|
|
38
|
-
/**
|
|
39
|
-
* Return a Preact component, mounted as an island in a <script> tag so it can be embedded into the page response.
|
|
40
|
-
*/
|
|
41
|
-
export declare function createPreactIsland(file: string): Promise<(props: any) => {
|
|
42
|
-
_kind: string;
|
|
43
|
-
content: string;
|
|
44
|
-
}>;
|
|
45
|
-
|
|
46
|
-
export {};
|
package/dist/server.d.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
// Generated by dts-bundle-generator v9.5.1
|
|
2
|
-
|
|
3
|
-
import { Context, Hono } from 'hono';
|
|
4
|
-
|
|
5
|
-
declare class TmplHtml {
|
|
6
|
-
_kind: string;
|
|
7
|
-
content: string;
|
|
8
|
-
asyncContent: Array<{
|
|
9
|
-
id: string;
|
|
10
|
-
promise: Promise<{
|
|
11
|
-
id: string;
|
|
12
|
-
value: unknown;
|
|
13
|
-
}>;
|
|
14
|
-
}>;
|
|
15
|
-
constructor(props: Pick<TmplHtml, "content" | "asyncContent">);
|
|
16
|
-
}
|
|
17
|
-
export declare const IS_PROD: boolean;
|
|
18
|
-
/**
|
|
19
|
-
* Types
|
|
20
|
-
*/
|
|
21
|
-
export type THSResponseTypes = TmplHtml | Response | string | null;
|
|
22
|
-
export type THSRouteHandler = (context: Context) => THSResponseTypes | Promise<THSResponseTypes>;
|
|
23
|
-
/**
|
|
24
|
-
* Route
|
|
25
|
-
* Define a route that can handle a direct HTTP request
|
|
26
|
-
* Route handlers should return a Response or TmplHtml object
|
|
27
|
-
*/
|
|
28
|
-
export declare function createRoute(handler?: THSRouteHandler): {
|
|
29
|
-
_kind: string;
|
|
30
|
-
get(handler: THSRouteHandler): any;
|
|
31
|
-
post(handler: THSRouteHandler): any;
|
|
32
|
-
put(handler: THSRouteHandler): any;
|
|
33
|
-
delete(handler: THSRouteHandler): any;
|
|
34
|
-
patch(handler: THSRouteHandler): any;
|
|
35
|
-
run(method: string, context: Context): Promise<Response>;
|
|
36
|
-
};
|
|
37
|
-
export type THSRoute = ReturnType<typeof createRoute>;
|
|
38
|
-
/**
|
|
39
|
-
* Create new API Route
|
|
40
|
-
* API Route handlers should return a JSON object or a Response
|
|
41
|
-
*/
|
|
42
|
-
export declare function createAPIRoute(handler?: THSRouteHandler): {
|
|
43
|
-
_kind: string;
|
|
44
|
-
get(handler: THSRouteHandler): any;
|
|
45
|
-
post(handler: THSRouteHandler): any;
|
|
46
|
-
put(handler: THSRouteHandler): any;
|
|
47
|
-
delete(handler: THSRouteHandler): any;
|
|
48
|
-
patch(handler: THSRouteHandler): any;
|
|
49
|
-
run(method: string, context: Context): Promise<Response>;
|
|
50
|
-
};
|
|
51
|
-
export type THSAPIRoute = ReturnType<typeof createAPIRoute>;
|
|
52
|
-
/**
|
|
53
|
-
* Get a Hyperspan runnable route from a module import
|
|
54
|
-
* @throws Error if no runnable route found
|
|
55
|
-
*/
|
|
56
|
-
export declare function getRunnableRoute(route: unknown): THSRoute;
|
|
57
|
-
export declare function isRouteRunnable(route: unknown): boolean;
|
|
58
|
-
export type THSServerConfig = {
|
|
59
|
-
appDir: string;
|
|
60
|
-
staticFileRoot: string;
|
|
61
|
-
rewrites?: Array<{
|
|
62
|
-
source: string;
|
|
63
|
-
destination: string;
|
|
64
|
-
}>;
|
|
65
|
-
beforeRoutesAdded?: (app: Hono) => void;
|
|
66
|
-
afterRoutesAdded?: (app: Hono) => void;
|
|
67
|
-
};
|
|
68
|
-
export type THSRouteMap = {
|
|
69
|
-
file: string;
|
|
70
|
-
route: string;
|
|
71
|
-
params: string[];
|
|
72
|
-
};
|
|
73
|
-
export declare function buildRoutes(config: THSServerConfig): Promise<THSRouteMap[]>;
|
|
74
|
-
/**
|
|
75
|
-
* Run route from file
|
|
76
|
-
*/
|
|
77
|
-
export declare function createRouteFromModule(RouteModule: any): (context: Context) => Promise<Response>;
|
|
78
|
-
/**
|
|
79
|
-
* Create and start Bun HTTP server
|
|
80
|
-
*/
|
|
81
|
-
export declare function createServer(config: THSServerConfig): Promise<Hono>;
|
|
82
|
-
/**
|
|
83
|
-
* Streaming HTML Response
|
|
84
|
-
*/
|
|
85
|
-
export declare class StreamResponse extends Response {
|
|
86
|
-
constructor(iterator: AsyncIterator<unknown>, options?: {});
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Does what it says on the tin...
|
|
90
|
-
*/
|
|
91
|
-
export declare function createReadableStreamFromAsyncGenerator(output: AsyncGenerator): ReadableStream<any>;
|
|
92
|
-
/**
|
|
93
|
-
* Normalize URL path
|
|
94
|
-
* Removes trailing slash and lowercases path
|
|
95
|
-
*/
|
|
96
|
-
export declare function normalizePath(urlPath: string): string;
|
|
97
|
-
|
|
98
|
-
export {};
|