@hyperspan/framework 0.2.0 → 0.3.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/build.ts +1 -1
- package/dist/server.js +84 -64
- package/package.json +3 -3
- package/src/server.ts +119 -95
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'];
|
|
5
5
|
const outdir = './dist';
|
|
6
6
|
const target = 'node';
|
|
7
7
|
const splitting = true;
|
package/dist/server.js
CHANGED
|
@@ -1833,6 +1833,7 @@ function createConfig(config) {
|
|
|
1833
1833
|
}
|
|
1834
1834
|
function createRoute(handler) {
|
|
1835
1835
|
let _handlers = {};
|
|
1836
|
+
let _middleware = [];
|
|
1836
1837
|
if (handler) {
|
|
1837
1838
|
_handlers["GET"] = handler;
|
|
1838
1839
|
}
|
|
@@ -1858,34 +1859,52 @@ function createRoute(handler) {
|
|
|
1858
1859
|
_handlers["PATCH"] = handler2;
|
|
1859
1860
|
return api;
|
|
1860
1861
|
},
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1862
|
+
middleware(middleware) {
|
|
1863
|
+
_middleware = middleware;
|
|
1864
|
+
return api;
|
|
1865
|
+
},
|
|
1866
|
+
_getRouteHandlers() {
|
|
1867
|
+
return [
|
|
1868
|
+
..._middleware,
|
|
1869
|
+
async (context) => {
|
|
1870
|
+
const method = context.req.method.toUpperCase();
|
|
1871
|
+
try {
|
|
1872
|
+
const handler2 = _handlers[method];
|
|
1873
|
+
if (!handler2) {
|
|
1874
|
+
throw new HTTPException(405, { message: "Method not allowed" });
|
|
1875
|
+
}
|
|
1876
|
+
const routeContent = await handler2(context);
|
|
1877
|
+
if (routeContent instanceof Response) {
|
|
1878
|
+
return routeContent;
|
|
1879
|
+
}
|
|
1880
|
+
const userIsBot = isbot(context.req.header("User-Agent"));
|
|
1881
|
+
const streamOpt = context.req.query("__nostream");
|
|
1882
|
+
const streamingEnabled = !userIsBot && (streamOpt !== undefined ? streamOpt : true);
|
|
1883
|
+
if (isHSHtml(routeContent)) {
|
|
1884
|
+
if (streamingEnabled) {
|
|
1885
|
+
return new StreamResponse(renderStream(routeContent));
|
|
1886
|
+
} else {
|
|
1887
|
+
const output = await renderAsync(routeContent);
|
|
1888
|
+
return context.html(output);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
if (routeContent instanceof Response) {
|
|
1892
|
+
return routeContent;
|
|
1893
|
+
}
|
|
1894
|
+
return context.text(String(routeContent));
|
|
1895
|
+
} catch (e) {
|
|
1896
|
+
!IS_PROD && console.error(e);
|
|
1897
|
+
return await showErrorReponse(context, e);
|
|
1898
|
+
}
|
|
1880
1899
|
}
|
|
1881
|
-
|
|
1882
|
-
return context.text(String(routeContent));
|
|
1900
|
+
];
|
|
1883
1901
|
}
|
|
1884
1902
|
};
|
|
1885
1903
|
return api;
|
|
1886
1904
|
}
|
|
1887
1905
|
function createAPIRoute(handler) {
|
|
1888
1906
|
let _handlers = {};
|
|
1907
|
+
let _middleware = [];
|
|
1889
1908
|
if (handler) {
|
|
1890
1909
|
_handlers["GET"] = handler;
|
|
1891
1910
|
}
|
|
@@ -1911,30 +1930,46 @@ function createAPIRoute(handler) {
|
|
|
1911
1930
|
_handlers["PATCH"] = handler2;
|
|
1912
1931
|
return api;
|
|
1913
1932
|
},
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1933
|
+
middleware(middleware) {
|
|
1934
|
+
_middleware = middleware;
|
|
1935
|
+
return api;
|
|
1936
|
+
},
|
|
1937
|
+
_getRouteHandlers() {
|
|
1938
|
+
return [
|
|
1939
|
+
..._middleware,
|
|
1940
|
+
async (context) => {
|
|
1941
|
+
const method = context.req.method.toUpperCase();
|
|
1942
|
+
const handler2 = _handlers[method];
|
|
1943
|
+
if (!handler2) {
|
|
1944
|
+
return context.json({
|
|
1945
|
+
meta: { success: false, dtResponse: new Date },
|
|
1946
|
+
data: {},
|
|
1947
|
+
error: {
|
|
1948
|
+
message: "Method not allowed"
|
|
1949
|
+
}
|
|
1950
|
+
}, { status: 405 });
|
|
1951
|
+
}
|
|
1952
|
+
try {
|
|
1953
|
+
const response = await handler2(context);
|
|
1954
|
+
if (response instanceof Response) {
|
|
1955
|
+
return response;
|
|
1956
|
+
}
|
|
1957
|
+
return context.json({ meta: { success: true, dtResponse: new Date }, data: response }, { status: 200 });
|
|
1958
|
+
} catch (err) {
|
|
1959
|
+
const e = err;
|
|
1960
|
+
!IS_PROD && console.error(e);
|
|
1961
|
+
return context.json({
|
|
1962
|
+
meta: { success: false, dtResponse: new Date },
|
|
1963
|
+
data: {},
|
|
1964
|
+
error: {
|
|
1965
|
+
message: e.message,
|
|
1966
|
+
stack: IS_PROD ? undefined : e.stack?.split(`
|
|
1934
1967
|
`)
|
|
1968
|
+
}
|
|
1969
|
+
}, { status: 500 });
|
|
1935
1970
|
}
|
|
1936
|
-
}
|
|
1937
|
-
|
|
1971
|
+
}
|
|
1972
|
+
];
|
|
1938
1973
|
}
|
|
1939
1974
|
};
|
|
1940
1975
|
return api;
|
|
@@ -1950,13 +1985,10 @@ function getRunnableRoute(route) {
|
|
|
1950
1985
|
if (kind === "object" && "default" in route) {
|
|
1951
1986
|
return getRunnableRoute(route.default);
|
|
1952
1987
|
}
|
|
1953
|
-
throw new Error(
|
|
1988
|
+
throw new Error(`Route not runnable. Use "export default createRoute()" to create a Hyperspan route. Exported methods found were: ${Object.keys(route).join(", ")}`);
|
|
1954
1989
|
}
|
|
1955
1990
|
function isRunnableRoute(route) {
|
|
1956
|
-
return typeof route === "object" && "
|
|
1957
|
-
}
|
|
1958
|
-
function createLayout(layout) {
|
|
1959
|
-
return layout;
|
|
1991
|
+
return typeof route === "object" && "_getRouteHandlers" in route;
|
|
1960
1992
|
}
|
|
1961
1993
|
async function showErrorReponse(context, err) {
|
|
1962
1994
|
const output = render(html`
|
|
@@ -2009,20 +2041,8 @@ async function buildRoutes(config) {
|
|
|
2009
2041
|
return routes;
|
|
2010
2042
|
}
|
|
2011
2043
|
function createRouteFromModule(RouteModule) {
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
try {
|
|
2015
|
-
const runnableRoute = getRunnableRoute(RouteModule);
|
|
2016
|
-
const content = await runnableRoute.run(reqMethod, context);
|
|
2017
|
-
if (content instanceof Response) {
|
|
2018
|
-
return content;
|
|
2019
|
-
}
|
|
2020
|
-
return context.text(String(content));
|
|
2021
|
-
} catch (e) {
|
|
2022
|
-
console.error(e);
|
|
2023
|
-
return await showErrorReponse(context, e);
|
|
2024
|
-
}
|
|
2025
|
-
};
|
|
2044
|
+
const route = getRunnableRoute(RouteModule);
|
|
2045
|
+
return route._getRouteHandlers();
|
|
2026
2046
|
}
|
|
2027
2047
|
async function createServer(config) {
|
|
2028
2048
|
await Promise.all([buildClientJS(), buildClientCSS()]);
|
|
@@ -2035,7 +2055,8 @@ async function createServer(config) {
|
|
|
2035
2055
|
const fullRouteFile = join(CWD, route.file);
|
|
2036
2056
|
const routePattern = normalizePath(route.route);
|
|
2037
2057
|
routeMap.push({ route: routePattern, file: route.file });
|
|
2038
|
-
|
|
2058
|
+
const routeHandlers = createRouteFromModule(await import(fullRouteFile));
|
|
2059
|
+
app.all(routePattern, ...routeHandlers);
|
|
2039
2060
|
}
|
|
2040
2061
|
if (routeMap.length === 0) {
|
|
2041
2062
|
app.get("/", (context) => {
|
|
@@ -2101,7 +2122,6 @@ export {
|
|
|
2101
2122
|
createRouteFromModule,
|
|
2102
2123
|
createRoute,
|
|
2103
2124
|
createReadableStreamFromAsyncGenerator,
|
|
2104
|
-
createLayout,
|
|
2105
2125
|
createConfig,
|
|
2106
2126
|
createAPIRoute,
|
|
2107
2127
|
buildRoutes,
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperspan/framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Hyperspan Web Framework",
|
|
5
|
-
"main": "dist/server.
|
|
5
|
+
"main": "dist/server.ts",
|
|
6
6
|
"types": "src/server.ts",
|
|
7
7
|
"public": true,
|
|
8
8
|
"publishConfig": {
|
|
@@ -63,6 +63,6 @@
|
|
|
63
63
|
"@hyperspan/html": "^0.1.7",
|
|
64
64
|
"hono": "^4.7.10",
|
|
65
65
|
"isbot": "^5.1.28",
|
|
66
|
-
"zod": "^3.25.
|
|
66
|
+
"zod": "^3.25.42"
|
|
67
67
|
}
|
|
68
68
|
}
|
package/src/server.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { buildClientJS, buildClientCSS } from './assets';
|
|
|
6
6
|
import { Hono, type Context } from 'hono';
|
|
7
7
|
import { serveStatic } from 'hono/bun';
|
|
8
8
|
import { HTTPException } from 'hono/http-exception';
|
|
9
|
+
import type { HandlerResponse, MiddlewareHandler } from 'hono/types';
|
|
9
10
|
|
|
10
11
|
export const IS_PROD = process.env.NODE_ENV === 'production';
|
|
11
12
|
const CWD = process.cwd();
|
|
@@ -15,8 +16,7 @@ const CWD = process.cwd();
|
|
|
15
16
|
*/
|
|
16
17
|
export type THSResponseTypes = HSHtml | Response | string | null;
|
|
17
18
|
export type THSRouteHandler = (context: Context) => THSResponseTypes | Promise<THSResponseTypes>;
|
|
18
|
-
export type
|
|
19
|
-
export type THSAPIRouteHandler = (context: Context) => THSResponseTypes | Promise<THSResponseTypes>;
|
|
19
|
+
export type THSAPIRouteHandler = (context: Context) => Promise<any> | any;
|
|
20
20
|
|
|
21
21
|
export type THSRoute = {
|
|
22
22
|
_kind: 'hsRoute';
|
|
@@ -25,7 +25,8 @@ export type THSRoute = {
|
|
|
25
25
|
put: (handler: THSRouteHandler) => THSRoute;
|
|
26
26
|
delete: (handler: THSRouteHandler) => THSRoute;
|
|
27
27
|
patch: (handler: THSRouteHandler) => THSRoute;
|
|
28
|
-
|
|
28
|
+
middleware: (middleware: Array<MiddlewareHandler>) => THSRoute;
|
|
29
|
+
_getRouteHandlers: () => Array<MiddlewareHandler | ((context: Context) => HandlerResponse<any>)>;
|
|
29
30
|
};
|
|
30
31
|
|
|
31
32
|
export function createConfig(config: THSServerConfig): THSServerConfig {
|
|
@@ -38,6 +39,7 @@ export function createConfig(config: THSServerConfig): THSServerConfig {
|
|
|
38
39
|
*/
|
|
39
40
|
export function createRoute(handler?: THSRouteHandler): THSRoute {
|
|
40
41
|
let _handlers: Record<string, THSRouteHandler> = {};
|
|
42
|
+
let _middleware: Array<MiddlewareHandler> = [];
|
|
41
43
|
|
|
42
44
|
if (handler) {
|
|
43
45
|
_handlers['GET'] = handler;
|
|
@@ -65,37 +67,57 @@ export function createRoute(handler?: THSRouteHandler): THSRoute {
|
|
|
65
67
|
_handlers['PATCH'] = handler;
|
|
66
68
|
return api;
|
|
67
69
|
},
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
70
|
+
middleware(middleware: Array<MiddlewareHandler>) {
|
|
71
|
+
_middleware = middleware;
|
|
72
|
+
return api;
|
|
73
|
+
},
|
|
74
|
+
_getRouteHandlers() {
|
|
75
|
+
return [
|
|
76
|
+
..._middleware,
|
|
77
|
+
async (context: Context) => {
|
|
78
|
+
const method = context.req.method.toUpperCase();
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const handler = _handlers[method];
|
|
82
|
+
if (!handler) {
|
|
83
|
+
throw new HTTPException(405, { message: 'Method not allowed' });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const routeContent = await handler(context);
|
|
87
|
+
|
|
88
|
+
// Return Response if returned from route handler
|
|
89
|
+
if (routeContent instanceof Response) {
|
|
90
|
+
return routeContent;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// @TODO: Move this to config or something...
|
|
94
|
+
const userIsBot = isbot(context.req.header('User-Agent'));
|
|
95
|
+
const streamOpt = context.req.query('__nostream');
|
|
96
|
+
const streamingEnabled = !userIsBot && (streamOpt !== undefined ? streamOpt : true);
|
|
97
|
+
|
|
98
|
+
// Render HSHtml if returned from route handler
|
|
99
|
+
if (isHSHtml(routeContent)) {
|
|
100
|
+
if (streamingEnabled) {
|
|
101
|
+
return new StreamResponse(renderStream(routeContent as HSHtml)) as Response;
|
|
102
|
+
} else {
|
|
103
|
+
const output = await renderAsync(routeContent as HSHtml);
|
|
104
|
+
return context.html(output);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Return custom Response if returned from route handler
|
|
109
|
+
if (routeContent instanceof Response) {
|
|
110
|
+
return routeContent;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Return unknown content - not specifically handled above
|
|
114
|
+
return context.text(String(routeContent));
|
|
115
|
+
} catch (e) {
|
|
116
|
+
!IS_PROD && console.error(e);
|
|
117
|
+
return await showErrorReponse(context, e as Error);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
];
|
|
99
121
|
},
|
|
100
122
|
};
|
|
101
123
|
|
|
@@ -108,6 +130,7 @@ export function createRoute(handler?: THSRouteHandler): THSRoute {
|
|
|
108
130
|
*/
|
|
109
131
|
export function createAPIRoute(handler?: THSAPIRouteHandler): THSRoute {
|
|
110
132
|
let _handlers: Record<string, THSAPIRouteHandler> = {};
|
|
133
|
+
let _middleware: Array<MiddlewareHandler> = [];
|
|
111
134
|
|
|
112
135
|
if (handler) {
|
|
113
136
|
_handlers['GET'] = handler;
|
|
@@ -135,39 +158,59 @@ export function createAPIRoute(handler?: THSAPIRouteHandler): THSRoute {
|
|
|
135
158
|
_handlers['PATCH'] = handler;
|
|
136
159
|
return api;
|
|
137
160
|
},
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
161
|
+
middleware(middleware: Array<MiddlewareHandler>) {
|
|
162
|
+
_middleware = middleware;
|
|
163
|
+
return api;
|
|
164
|
+
},
|
|
165
|
+
_getRouteHandlers() {
|
|
166
|
+
return [
|
|
167
|
+
..._middleware,
|
|
168
|
+
async (context: Context) => {
|
|
169
|
+
const method = context.req.method.toUpperCase();
|
|
170
|
+
const handler = _handlers[method];
|
|
171
|
+
|
|
172
|
+
if (!handler) {
|
|
173
|
+
return context.json(
|
|
174
|
+
{
|
|
175
|
+
meta: { success: false, dtResponse: new Date() },
|
|
176
|
+
data: {},
|
|
177
|
+
error: {
|
|
178
|
+
message: 'Method not allowed',
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
{ status: 405 }
|
|
182
|
+
);
|
|
183
|
+
}
|
|
150
184
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
185
|
+
try {
|
|
186
|
+
const response = await handler(context);
|
|
187
|
+
|
|
188
|
+
if (response instanceof Response) {
|
|
189
|
+
return response;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return context.json(
|
|
193
|
+
{ meta: { success: true, dtResponse: new Date() }, data: response },
|
|
194
|
+
{ status: 200 }
|
|
195
|
+
);
|
|
196
|
+
} catch (err) {
|
|
197
|
+
const e = err as Error;
|
|
198
|
+
!IS_PROD && console.error(e);
|
|
199
|
+
|
|
200
|
+
return context.json(
|
|
201
|
+
{
|
|
202
|
+
meta: { success: false, dtResponse: new Date() },
|
|
203
|
+
data: {},
|
|
204
|
+
error: {
|
|
205
|
+
message: e.message,
|
|
206
|
+
stack: IS_PROD ? undefined : e.stack?.split('\n'),
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
{ status: 500 }
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
];
|
|
171
214
|
},
|
|
172
215
|
};
|
|
173
216
|
|
|
@@ -199,21 +242,13 @@ export function getRunnableRoute(route: unknown): THSRoute {
|
|
|
199
242
|
|
|
200
243
|
// No route -> error
|
|
201
244
|
throw new Error(
|
|
202
|
-
|
|
245
|
+
`Route not runnable. Use "export default createRoute()" to create a Hyperspan route. Exported methods found were: ${Object.keys(route as {}).join(', ')}`
|
|
203
246
|
);
|
|
204
247
|
}
|
|
205
248
|
|
|
206
249
|
export function isRunnableRoute(route: unknown): boolean {
|
|
207
250
|
// @ts-ignore
|
|
208
|
-
return typeof route === 'object' && '
|
|
209
|
-
}
|
|
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;
|
|
251
|
+
return typeof route === 'object' && '_getRouteHandlers' in route;
|
|
217
252
|
}
|
|
218
253
|
|
|
219
254
|
/**
|
|
@@ -305,24 +340,11 @@ export async function buildRoutes(config: THSServerConfig): Promise<THSRouteMap[
|
|
|
305
340
|
/**
|
|
306
341
|
* Run route from file
|
|
307
342
|
*/
|
|
308
|
-
export function createRouteFromModule(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const runnableRoute = getRunnableRoute(RouteModule);
|
|
314
|
-
const content = await runnableRoute.run(reqMethod, context);
|
|
315
|
-
|
|
316
|
-
if (content instanceof Response) {
|
|
317
|
-
return content;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return context.text(String(content));
|
|
321
|
-
} catch (e) {
|
|
322
|
-
console.error(e);
|
|
323
|
-
return await showErrorReponse(context, e as Error);
|
|
324
|
-
}
|
|
325
|
-
};
|
|
343
|
+
export function createRouteFromModule(
|
|
344
|
+
RouteModule: any
|
|
345
|
+
): Array<MiddlewareHandler | ((context: Context) => HandlerResponse<any>)> {
|
|
346
|
+
const route = getRunnableRoute(RouteModule);
|
|
347
|
+
return route._getRouteHandlers();
|
|
326
348
|
}
|
|
327
349
|
|
|
328
350
|
/**
|
|
@@ -349,7 +371,8 @@ export async function createServer(config: THSServerConfig): Promise<Hono> {
|
|
|
349
371
|
routeMap.push({ route: routePattern, file: route.file });
|
|
350
372
|
|
|
351
373
|
// Import route
|
|
352
|
-
|
|
374
|
+
const routeHandlers = createRouteFromModule(await import(fullRouteFile));
|
|
375
|
+
app.all(routePattern, ...routeHandlers);
|
|
353
376
|
}
|
|
354
377
|
|
|
355
378
|
// Help route if no routes found
|
|
@@ -386,6 +409,7 @@ export async function createServer(config: THSServerConfig): Promise<Hono> {
|
|
|
386
409
|
);
|
|
387
410
|
|
|
388
411
|
app.notFound((context) => {
|
|
412
|
+
// @TODO: Add a custom 404 route
|
|
389
413
|
return context.text('Not... found?', { status: 404 });
|
|
390
414
|
});
|
|
391
415
|
|