@hyperspan/framework 1.0.0-alpha.14 → 1.0.0-alpha.15
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/package.json +2 -1
- package/src/actions.ts +12 -7
- package/src/client/js.ts +14 -23
- package/src/layout.ts +2 -2
- package/src/middleware.ts +87 -1
- package/src/server.ts +43 -24
- package/src/types.ts +29 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperspan/framework",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.15",
|
|
4
4
|
"description": "Hyperspan Web Framework",
|
|
5
5
|
"main": "src/server.ts",
|
|
6
6
|
"types": "src/server.ts",
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
75
|
"@hyperspan/html": "^1.0.0-alpha",
|
|
76
|
+
"isbot": "^5.1.32",
|
|
76
77
|
"zod": "^4.1.12"
|
|
77
78
|
}
|
|
78
79
|
}
|
package/src/actions.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { html
|
|
1
|
+
import { html } from '@hyperspan/html';
|
|
2
2
|
import { createRoute, returnHTMLResponse } from './server';
|
|
3
3
|
import * as z from 'zod/v4';
|
|
4
4
|
import type { Hyperspan as HS } from './types';
|
|
5
5
|
import { assetHash, formDataToJSON } from './utils';
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
6
|
+
import { buildClientJS } from './client/js';
|
|
7
|
+
import { validateBody } from './middleware';
|
|
8
|
+
|
|
9
|
+
const actionsClientJS = await buildClientJS(import.meta.resolve('./client/_hs/hyperspan-actions.client'));
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Actions = Form + route handler
|
|
@@ -19,7 +21,7 @@ import { renderClientJS } from './client/js';
|
|
|
19
21
|
* 5. Replaces form content in place with HTML response content from server via the Idiomorph library
|
|
20
22
|
* 6. Handles any Exception thrown on server as error displayed back to user on the page
|
|
21
23
|
*/
|
|
22
|
-
export function createAction<T extends z.
|
|
24
|
+
export function createAction<T extends z.ZodObject<any, any>>(params: { name: string; schema?: T }): HS.Action<T> {
|
|
23
25
|
const { name, schema } = params;
|
|
24
26
|
const path = `/__actions/${assetHash(name)}`;
|
|
25
27
|
|
|
@@ -30,7 +32,7 @@ export function createAction<T extends z.ZodTypeAny>(params: { name: string; sch
|
|
|
30
32
|
.get((c: HS.Context) => api.render(c))
|
|
31
33
|
.post(async (c: HS.Context) => {
|
|
32
34
|
// Parse form data
|
|
33
|
-
const formData = await c.req.
|
|
35
|
+
const formData = await c.req.formData();
|
|
34
36
|
const jsonData = formDataToJSON(formData) as Partial<z.infer<T>>;
|
|
35
37
|
const schemaData = schema ? schema.safeParse(jsonData) : null;
|
|
36
38
|
const data = schemaData?.success ? (schemaData.data as Partial<z.infer<T>>) : jsonData;
|
|
@@ -69,7 +71,10 @@ export function createAction<T extends z.ZodTypeAny>(params: { name: string; sch
|
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
return await returnHTMLResponse(c, () => api.render(c, { data, error }), { status: 400 });
|
|
72
|
-
});
|
|
74
|
+
}, { middleware: schema ? [validateBody(schema)] : [] });
|
|
75
|
+
|
|
76
|
+
// Set the name of the action for the route
|
|
77
|
+
route._config.name = name;
|
|
73
78
|
|
|
74
79
|
const api: HS.Action<T> = {
|
|
75
80
|
_kind: 'hsAction',
|
|
@@ -101,7 +106,7 @@ export function createAction<T extends z.ZodTypeAny>(params: { name: string; sch
|
|
|
101
106
|
*/
|
|
102
107
|
render(c: HS.Context, props?: HS.ActionProps<T>) {
|
|
103
108
|
const formContent = api._form ? api._form(c, props || {}) : null;
|
|
104
|
-
return formContent ? html`<hs-action url="${this._path()}">${formContent}</hs-action>${
|
|
109
|
+
return formContent ? html`<hs-action url="${this._path()}">${formContent}</hs-action>${actionsClientJS.renderScriptTag()}` : null;
|
|
105
110
|
},
|
|
106
111
|
errorHandler(handler) {
|
|
107
112
|
_errorHandler = handler;
|
package/src/client/js.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { assetHash } from '../utils';
|
|
1
|
+
import { html } from '@hyperspan/html';
|
|
2
|
+
import { assetHash as assetHashFn } from '../utils';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import type { Hyperspan as HS } from '../types';
|
|
4
5
|
|
|
5
6
|
const CWD = process.cwd();
|
|
6
7
|
const IS_PROD = process.env.NODE_ENV === 'production';
|
|
@@ -11,23 +12,15 @@ export const JS_IMPORT_MAP = new Map<string, string>();
|
|
|
11
12
|
const CLIENT_JS_CACHE = new Map<string, { esmName: string, exports: string, fnArgs: string, publicPath: string }>();
|
|
12
13
|
const EXPORT_REGEX = /export\{(.*)\}/g;
|
|
13
14
|
|
|
14
|
-
type ClientJSModuleReturn = {
|
|
15
|
-
esmName: string;
|
|
16
|
-
jsId: string;
|
|
17
|
-
publicPath: string;
|
|
18
|
-
renderScriptTag: (loadScript?: ((module: unknown) => HSHtml | string) | string) => HSHtml;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
15
|
/**
|
|
22
|
-
*
|
|
16
|
+
* Build a client JS module and return a Hyperspan.ClientJSBuildResult object
|
|
23
17
|
*/
|
|
24
|
-
export async function
|
|
18
|
+
export async function buildClientJS(modulePathResolved: string): Promise<HS.ClientJSBuildResult> {
|
|
25
19
|
const modulePath = modulePathResolved.replace('file://', '');
|
|
26
|
-
const
|
|
20
|
+
const assetHash = assetHashFn(modulePath);
|
|
27
21
|
|
|
28
22
|
// Cache: Avoid re-processing the same file
|
|
29
|
-
if (!CLIENT_JS_CACHE.has(
|
|
30
|
-
|
|
23
|
+
if (!CLIENT_JS_CACHE.has(assetHash)) {
|
|
31
24
|
// Build the client JS module
|
|
32
25
|
const result = await Bun.build({
|
|
33
26
|
entrypoints: [modulePath],
|
|
@@ -62,31 +55,31 @@ export async function loadClientJS(modulePathResolved: string): Promise<ClientJS
|
|
|
62
55
|
'}';
|
|
63
56
|
}
|
|
64
57
|
const fnArgs = exports.replace(/(\w+)\s*as\s*(\w+)/g, '$1: $2');
|
|
65
|
-
CLIENT_JS_CACHE.set(
|
|
58
|
+
CLIENT_JS_CACHE.set(assetHash, { esmName, exports, fnArgs, publicPath });
|
|
66
59
|
}
|
|
67
60
|
|
|
68
|
-
const { esmName, exports, fnArgs, publicPath } = CLIENT_JS_CACHE.get(
|
|
61
|
+
const { esmName, exports, fnArgs, publicPath } = CLIENT_JS_CACHE.get(assetHash)!;
|
|
69
62
|
|
|
70
63
|
return {
|
|
64
|
+
assetHash,
|
|
71
65
|
esmName,
|
|
72
|
-
jsId,
|
|
73
66
|
publicPath,
|
|
74
67
|
renderScriptTag: (loadScript) => {
|
|
75
68
|
const t = typeof loadScript;
|
|
76
69
|
|
|
77
70
|
if (t === 'string') {
|
|
78
71
|
return html`
|
|
79
|
-
<script type="module" data-source-id="${
|
|
72
|
+
<script type="module" data-source-id="${assetHash}">import ${exports} from "${esmName}";\n(${html.raw(loadScript as string)})(${fnArgs});</script>
|
|
80
73
|
`;
|
|
81
74
|
}
|
|
82
75
|
if (t === 'function') {
|
|
83
76
|
return html`
|
|
84
|
-
<script type="module" data-source-id="${
|
|
77
|
+
<script type="module" data-source-id="${assetHash}">import ${exports} from "${esmName}";\n(${html.raw(functionToString(loadScript))})(${fnArgs});</script>
|
|
85
78
|
`;
|
|
86
79
|
}
|
|
87
80
|
|
|
88
81
|
return html`
|
|
89
|
-
<script type="module" data-source-id="${
|
|
82
|
+
<script type="module" data-source-id="${assetHash}">import "${esmName}";</script>
|
|
90
83
|
`;
|
|
91
84
|
}
|
|
92
85
|
}
|
|
@@ -97,7 +90,5 @@ export async function loadClientJS(modulePathResolved: string): Promise<ClientJS
|
|
|
97
90
|
* Handles named, async, and arrow functions
|
|
98
91
|
*/
|
|
99
92
|
export function functionToString(fn: any) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return str;
|
|
93
|
+
return fn.toString().trim();
|
|
103
94
|
}
|
package/src/layout.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { html } from '@hyperspan/html';
|
|
2
|
-
import { JS_IMPORT_MAP,
|
|
2
|
+
import { JS_IMPORT_MAP, buildClientJS } from './client/js';
|
|
3
3
|
import { CSS_PUBLIC_PATH, CSS_ROUTE_MAP } from './client/css';
|
|
4
4
|
import type { Hyperspan as HS } from './types';
|
|
5
5
|
|
|
6
|
-
const clientStreamingJS = await
|
|
6
|
+
const clientStreamingJS = await buildClientJS(import.meta.resolve('./client/_hs/hyperspan-streaming.client'));
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Output the importmap for the client so we can use ESModules on the client to load JS files on demand
|
package/src/middleware.ts
CHANGED
|
@@ -1,4 +1,91 @@
|
|
|
1
|
+
import { formDataToJSON } from './utils';
|
|
2
|
+
import { z, flattenError } from 'zod/v4';
|
|
3
|
+
|
|
4
|
+
import type { ZodAny, ZodObject, ZodError } from 'zod/v4';
|
|
1
5
|
import type { Hyperspan as HS } from './types';
|
|
6
|
+
import { HTTPResponseException } from './server';
|
|
7
|
+
|
|
8
|
+
export type TValidationType = 'json' | 'form' | 'urlencoded';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Infer the validation type from the request Content-Type header
|
|
12
|
+
*/
|
|
13
|
+
function inferValidationType(headers: Headers): TValidationType {
|
|
14
|
+
const contentType = headers.get('content-type')?.toLowerCase() || '';
|
|
15
|
+
|
|
16
|
+
if (contentType.includes('application/json')) {
|
|
17
|
+
return 'json';
|
|
18
|
+
} else if (contentType.includes('multipart/form-data')) {
|
|
19
|
+
return 'form';
|
|
20
|
+
} else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
21
|
+
return 'urlencoded';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Default to json if content-type is not recognized
|
|
25
|
+
return 'json';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class ZodValidationError extends Error {
|
|
29
|
+
constructor(flattened: ReturnType<typeof flattenError>) {
|
|
30
|
+
super('Input validation error(s)');
|
|
31
|
+
this.name = 'ZodValidationError';
|
|
32
|
+
|
|
33
|
+
// Copy all properties from flattened error
|
|
34
|
+
Object.assign(this, flattened);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function validateQuery(schema: ZodObject | ZodAny): HS.MiddlewareFunction {
|
|
39
|
+
return async (context: HS.Context, next: HS.NextFunction) => {
|
|
40
|
+
const query = formDataToJSON(context.req.query);
|
|
41
|
+
const validated = schema.safeParse(query);
|
|
42
|
+
|
|
43
|
+
if (!validated.success) {
|
|
44
|
+
const err = formatZodError(validated.error);
|
|
45
|
+
return context.res.error(err, { status: 400 });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Store the validated query in the context variables
|
|
49
|
+
context.vars.query = validated.data as z.infer<typeof schema>;
|
|
50
|
+
|
|
51
|
+
return next();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function validateBody(schema: ZodObject | ZodAny, type?: TValidationType): HS.MiddlewareFunction {
|
|
56
|
+
return async (context: HS.Context, next: HS.NextFunction) => {
|
|
57
|
+
// Infer type from Content-Type header if not provided
|
|
58
|
+
const validationType = type || inferValidationType(context.req.headers);
|
|
59
|
+
|
|
60
|
+
let body: unknown = {};
|
|
61
|
+
if (validationType === 'json') {
|
|
62
|
+
body = await context.req.raw.json();
|
|
63
|
+
} else if (validationType === 'form') {
|
|
64
|
+
const formData = await context.req.formData();
|
|
65
|
+
body = formDataToJSON(formData as FormData);
|
|
66
|
+
} else if (validationType === 'urlencoded') {
|
|
67
|
+
const urlencoded = await context.req.urlencoded();
|
|
68
|
+
body = formDataToJSON(urlencoded);
|
|
69
|
+
}
|
|
70
|
+
const validated = schema.safeParse(body);
|
|
71
|
+
|
|
72
|
+
if (!validated.success) {
|
|
73
|
+
const err = formatZodError(validated.error);
|
|
74
|
+
throw new HTTPResponseException(err, { status: 400 });
|
|
75
|
+
//return context.res.error(err, { status: 400 });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Store the validated body in the context variables
|
|
79
|
+
context.vars.body = validated.data as z.infer<typeof schema>;
|
|
80
|
+
|
|
81
|
+
return next();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function formatZodError(error: ZodError): ZodValidationError {
|
|
86
|
+
const zodError = flattenError(error);
|
|
87
|
+
return new ZodValidationError(zodError);
|
|
88
|
+
}
|
|
2
89
|
|
|
3
90
|
/**
|
|
4
91
|
* Type guard to check if a handler is a middleware function
|
|
@@ -55,4 +142,3 @@ export async function executeMiddleware(
|
|
|
55
142
|
// Start execution from the first handler
|
|
56
143
|
return await createNext(0)();
|
|
57
144
|
}
|
|
58
|
-
|
package/src/server.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HSHtml, html, isHSHtml, renderStream, renderAsync, render, _typeOf } from '@hyperspan/html';
|
|
2
|
+
import { isbot } from 'isbot';
|
|
2
3
|
import { executeMiddleware } from './middleware';
|
|
3
4
|
import { parsePath } from './utils';
|
|
4
5
|
import { Cookies } from './cookies';
|
|
@@ -8,10 +9,12 @@ import type { Hyperspan as HS } from './types';
|
|
|
8
9
|
export const IS_PROD = process.env.NODE_ENV === 'production';
|
|
9
10
|
|
|
10
11
|
export class HTTPResponseException extends Error {
|
|
12
|
+
public _error?: Error;
|
|
11
13
|
public _response?: Response;
|
|
12
|
-
constructor(body: string | undefined, options?: ResponseInit) {
|
|
13
|
-
super(body);
|
|
14
|
-
this.
|
|
14
|
+
constructor(body: string | Error | undefined, options?: ResponseInit) {
|
|
15
|
+
super(body instanceof Error ? body.message : body);
|
|
16
|
+
this._error = body instanceof Error ? body : undefined;
|
|
17
|
+
this._response = new Response(body instanceof Error ? body.message : body, options);
|
|
15
18
|
}
|
|
16
19
|
}
|
|
17
20
|
|
|
@@ -19,11 +22,22 @@ export class HTTPResponseException extends Error {
|
|
|
19
22
|
* Ensures a valid config object is returned, even with an empty object or partial object passed in
|
|
20
23
|
*/
|
|
21
24
|
export function createConfig(config: Partial<HS.Config> = {}): HS.Config {
|
|
25
|
+
const defaultConfig: HS.Config = {
|
|
26
|
+
appDir: './app',
|
|
27
|
+
publicDir: './public',
|
|
28
|
+
plugins: [],
|
|
29
|
+
responseOptions: {
|
|
30
|
+
// Disable streaming for bots by default
|
|
31
|
+
disableStreaming: (c) => isbot(c.req.raw.headers.get('user-agent') ?? ''),
|
|
32
|
+
},
|
|
33
|
+
};
|
|
22
34
|
return {
|
|
35
|
+
...defaultConfig,
|
|
23
36
|
...config,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
37
|
+
responseOptions: {
|
|
38
|
+
...defaultConfig.responseOptions,
|
|
39
|
+
...config.responseOptions,
|
|
40
|
+
},
|
|
27
41
|
};
|
|
28
42
|
}
|
|
29
43
|
|
|
@@ -102,6 +116,7 @@ export function createContext(req: Request, route?: HS.Route): HS.Context {
|
|
|
102
116
|
*/
|
|
103
117
|
export function createRoute(config: Partial<HS.RouteConfig> = {}): HS.Route {
|
|
104
118
|
const _handlers: Record<string, HS.RouteHandler> = {};
|
|
119
|
+
let _errorHandler: HS.ErrorHandler | undefined = undefined;
|
|
105
120
|
let _middleware: Record<string, Array<HS.MiddlewareFunction>> = { '*': [] };
|
|
106
121
|
|
|
107
122
|
const api: HS.Route = {
|
|
@@ -140,14 +155,6 @@ export function createRoute(config: Partial<HS.RouteConfig> = {}): HS.Route {
|
|
|
140
155
|
_middleware['PUT'] = handlerOptions?.middleware || [];
|
|
141
156
|
return api;
|
|
142
157
|
},
|
|
143
|
-
/**
|
|
144
|
-
* Add a DELETE route handler (typically to delete existing data)
|
|
145
|
-
*/
|
|
146
|
-
delete(handler: HS.RouteHandler, handlerOptions?: HS.RouteHandlerOptions) {
|
|
147
|
-
_handlers['DELETE'] = handler;
|
|
148
|
-
_middleware['DELETE'] = handlerOptions?.middleware || [];
|
|
149
|
-
return api;
|
|
150
|
-
},
|
|
151
158
|
/**
|
|
152
159
|
* Add a PATCH route handler (typically to update existing data)
|
|
153
160
|
*/
|
|
@@ -156,6 +163,14 @@ export function createRoute(config: Partial<HS.RouteConfig> = {}): HS.Route {
|
|
|
156
163
|
_middleware['PATCH'] = handlerOptions?.middleware || [];
|
|
157
164
|
return api;
|
|
158
165
|
},
|
|
166
|
+
/**
|
|
167
|
+
* Add a DELETE route handler (typically to delete existing data)
|
|
168
|
+
*/
|
|
169
|
+
delete(handler: HS.RouteHandler, handlerOptions?: HS.RouteHandlerOptions) {
|
|
170
|
+
_handlers['DELETE'] = handler;
|
|
171
|
+
_middleware['DELETE'] = handlerOptions?.middleware || [];
|
|
172
|
+
return api;
|
|
173
|
+
},
|
|
159
174
|
/**
|
|
160
175
|
* Add a OPTIONS route handler (typically to handle CORS preflight requests)
|
|
161
176
|
*/
|
|
@@ -164,8 +179,11 @@ export function createRoute(config: Partial<HS.RouteConfig> = {}): HS.Route {
|
|
|
164
179
|
_middleware['OPTIONS'] = handlerOptions?.middleware || [];
|
|
165
180
|
return api;
|
|
166
181
|
},
|
|
167
|
-
|
|
168
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Set a custom error handler for this route to fall back to if the route handler throws an error
|
|
184
|
+
*/
|
|
185
|
+
errorHandler(handler: HS.ErrorHandler) {
|
|
186
|
+
_errorHandler = handler;
|
|
169
187
|
return api;
|
|
170
188
|
},
|
|
171
189
|
/**
|
|
@@ -223,7 +241,9 @@ export function createRoute(config: Partial<HS.RouteConfig> = {}): HS.Route {
|
|
|
223
241
|
}
|
|
224
242
|
|
|
225
243
|
if (isHTMLContent(routeContent)) {
|
|
226
|
-
|
|
244
|
+
// Merge server and route-specific response options
|
|
245
|
+
const responseOptions = { ...(api._serverConfig?.responseOptions ?? {}), ...(api._config?.responseOptions ?? {}) };
|
|
246
|
+
return returnHTMLResponse(context, () => routeContent, responseOptions);
|
|
227
247
|
}
|
|
228
248
|
|
|
229
249
|
const contentType = _typeOf(routeContent);
|
|
@@ -239,8 +259,9 @@ export function createRoute(config: Partial<HS.RouteConfig> = {}): HS.Route {
|
|
|
239
259
|
try {
|
|
240
260
|
return await executeMiddleware(context, [...globalMiddleware, ...methodMiddleware, methodHandler]);
|
|
241
261
|
} catch (e) {
|
|
242
|
-
if (
|
|
243
|
-
|
|
262
|
+
if (_errorHandler !== undefined) {
|
|
263
|
+
const responseOptions = { ...(api._serverConfig?.responseOptions ?? {}), ...(api._config?.responseOptions ?? {}) };
|
|
264
|
+
return returnHTMLResponse(context, () => (_errorHandler as HS.ErrorHandler)(context, e as Error), responseOptions);
|
|
244
265
|
}
|
|
245
266
|
throw e;
|
|
246
267
|
}
|
|
@@ -328,7 +349,7 @@ function isHTMLContent(response: unknown): response is Response {
|
|
|
328
349
|
export async function returnHTMLResponse(
|
|
329
350
|
context: HS.Context,
|
|
330
351
|
handlerFn: () => unknown,
|
|
331
|
-
responseOptions?: { status?: number; headers?: Record<string, string
|
|
352
|
+
responseOptions?: { status?: number; headers?: Record<string, string>; disableStreaming?: (context: HS.Context) => boolean }
|
|
332
353
|
): Promise<Response> {
|
|
333
354
|
try {
|
|
334
355
|
const routeContent = await handlerFn();
|
|
@@ -340,12 +361,10 @@ export async function returnHTMLResponse(
|
|
|
340
361
|
|
|
341
362
|
// Render HSHtml if returned from route handler
|
|
342
363
|
if (isHSHtml(routeContent)) {
|
|
343
|
-
|
|
344
|
-
const disableStreaming = context.req.query.get('__nostream') ?? '0';
|
|
345
|
-
const streamingEnabled = disableStreaming !== '1';
|
|
364
|
+
const disableStreaming = responseOptions?.disableStreaming?.(context) ?? false;
|
|
346
365
|
|
|
347
366
|
// Stream only if enabled and there is async content to stream
|
|
348
|
-
if (
|
|
367
|
+
if (!disableStreaming && (routeContent as HSHtml).asyncContent?.length > 0) {
|
|
349
368
|
return new StreamResponse(
|
|
350
369
|
renderStream(routeContent as HSHtml, {
|
|
351
370
|
renderChunk: (chunk) => {
|
package/src/types.ts
CHANGED
|
@@ -27,6 +27,9 @@ export namespace Hyperspan {
|
|
|
27
27
|
// For customizing the routes and adding your own...
|
|
28
28
|
beforeRoutesAdded?: (server: Hyperspan.Server) => void;
|
|
29
29
|
afterRoutesAdded?: (server: Hyperspan.Server) => void;
|
|
30
|
+
responseOptions?: {
|
|
31
|
+
disableStreaming?: (context: Hyperspan.Context) => boolean;
|
|
32
|
+
};
|
|
30
33
|
};
|
|
31
34
|
|
|
32
35
|
export type CookieOptions = {
|
|
@@ -92,6 +95,9 @@ export namespace Hyperspan {
|
|
|
92
95
|
path: string;
|
|
93
96
|
params: Record<string, string | undefined>;
|
|
94
97
|
cssImports: string[];
|
|
98
|
+
responseOptions?: {
|
|
99
|
+
disableStreaming?: (context: Hyperspan.Context) => boolean;
|
|
100
|
+
};
|
|
95
101
|
};
|
|
96
102
|
export type RouteHandler = (context: Hyperspan.Context) => unknown;
|
|
97
103
|
export type RouteHandlerOptions = {
|
|
@@ -113,6 +119,11 @@ export namespace Hyperspan {
|
|
|
113
119
|
*/
|
|
114
120
|
export type NextFunction = () => Promise<Response>;
|
|
115
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Error handler function signature
|
|
124
|
+
*/
|
|
125
|
+
export type ErrorHandler = (context: Hyperspan.Context, error: Error) => unknown | undefined;
|
|
126
|
+
|
|
116
127
|
/**
|
|
117
128
|
* Middleware function signature
|
|
118
129
|
* Accepts context and next function, returns a Response
|
|
@@ -125,6 +136,7 @@ export namespace Hyperspan {
|
|
|
125
136
|
export interface Route {
|
|
126
137
|
_kind: 'hsRoute';
|
|
127
138
|
_config: Partial<Hyperspan.RouteConfig>;
|
|
139
|
+
_serverConfig?: Hyperspan.Config;
|
|
128
140
|
_path(): string;
|
|
129
141
|
_methods(): string[];
|
|
130
142
|
get: (handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
@@ -133,7 +145,7 @@ export namespace Hyperspan {
|
|
|
133
145
|
patch: (handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
134
146
|
delete: (handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
135
147
|
options: (handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
136
|
-
errorHandler: (handler: Hyperspan.
|
|
148
|
+
errorHandler: (handler: Hyperspan.ErrorHandler) => Hyperspan.Route;
|
|
137
149
|
middleware: (middleware: Array<Hyperspan.MiddlewareFunction>) => Hyperspan.Route;
|
|
138
150
|
fetch: (request: Request) => Promise<Response>;
|
|
139
151
|
};
|
|
@@ -148,7 +160,7 @@ export namespace Hyperspan {
|
|
|
148
160
|
) => ActionResponse;
|
|
149
161
|
export interface Action<T extends z.ZodTypeAny> {
|
|
150
162
|
_kind: 'hsAction';
|
|
151
|
-
_config: Hyperspan.RouteConfig
|
|
163
|
+
_config: Partial<Hyperspan.RouteConfig>;
|
|
152
164
|
_path(): string;
|
|
153
165
|
_form: null | ActionFormHandler<T>;
|
|
154
166
|
form(form: ActionFormHandler<T>): Action<T>;
|
|
@@ -158,4 +170,19 @@ export namespace Hyperspan {
|
|
|
158
170
|
middleware: (middleware: Array<Hyperspan.MiddlewareFunction>) => Action<T>;
|
|
159
171
|
fetch: (request: Request) => Promise<Response>;
|
|
160
172
|
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Client JS Module = ESM Module + Public Path + Render Script Tag
|
|
176
|
+
*/
|
|
177
|
+
export type ClientJSBuildResult = {
|
|
178
|
+
assetHash: string; // Asset hash of the module path
|
|
179
|
+
esmName: string; // Filename of the built JavaScript file without the extension
|
|
180
|
+
publicPath: string; // Full public path of the built JavaScript file
|
|
181
|
+
/**
|
|
182
|
+
* Render a <script type="module"> tag for the JS module
|
|
183
|
+
* @param loadScript - A function that loads the module or a string of code to load the module
|
|
184
|
+
* @returns HSHtml Template with the <script type="module"> tag
|
|
185
|
+
*/
|
|
186
|
+
renderScriptTag: (loadScript?: ((module: unknown) => HSHtml | string) | string) => HSHtml;
|
|
187
|
+
}
|
|
161
188
|
}
|