@hyperspan/framework 1.0.9 → 1.0.11
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 +1 -1
- package/src/actions.ts +4 -4
- package/src/middleware.ts +8 -2
- package/src/server.ts +42 -21
- package/src/types.ts +19 -7
package/package.json
CHANGED
package/src/actions.ts
CHANGED
|
@@ -109,12 +109,12 @@ export function createAction<T extends z.ZodObject<any, any>>(params: { name: st
|
|
|
109
109
|
_errorHandler = handler;
|
|
110
110
|
return api;
|
|
111
111
|
},
|
|
112
|
-
use(middleware: HS.MiddlewareFunction) {
|
|
113
|
-
route.use(middleware);
|
|
112
|
+
use(middleware: HS.MiddlewareFunction, opts?: HS.MiddlewareMethodOptions) {
|
|
113
|
+
route.use(middleware, opts);
|
|
114
114
|
return api;
|
|
115
115
|
},
|
|
116
|
-
middleware(middleware: Array<HS.MiddlewareFunction
|
|
117
|
-
route.middleware(middleware);
|
|
116
|
+
middleware(middleware: Array<HS.MiddlewareFunction>, opts?: HS.MiddlewareMethodOptions) {
|
|
117
|
+
route.middleware(middleware, opts);
|
|
118
118
|
return api;
|
|
119
119
|
},
|
|
120
120
|
fetch: route.fetch,
|
package/src/middleware.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { formDataToJSON } from './utils';
|
|
2
2
|
import { z, flattenError, prettifyError } from 'zod/v4';
|
|
3
|
+
import { HTTPResponseException } from './server';
|
|
3
4
|
|
|
4
5
|
import type { ZodAny, ZodObject, ZodError } from 'zod/v4';
|
|
5
6
|
import type { Hyperspan as HS } from './types';
|
|
6
|
-
import { HTTPResponseException } from './server';
|
|
7
7
|
|
|
8
8
|
export type TValidationType = 'json' | 'form' | 'urlencoded';
|
|
9
9
|
|
|
@@ -25,7 +25,7 @@ function inferValidationType(headers: Headers): TValidationType {
|
|
|
25
25
|
return 'json';
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export class ZodValidationError extends Error {
|
|
28
|
+
export class ZodValidationError extends Error implements HS.ZodValidationError {
|
|
29
29
|
public fieldErrors;
|
|
30
30
|
public formErrors: unknown[];
|
|
31
31
|
constructor(error: ZodError, schema: ZodObject | ZodAny) {
|
|
@@ -38,6 +38,9 @@ export class ZodValidationError extends Error {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Validate the query parameters of the request using a Zod schema.
|
|
43
|
+
*/
|
|
41
44
|
export function validateQuery(schema: ZodObject | ZodAny): HS.MiddlewareFunction {
|
|
42
45
|
return async (context: HS.Context, next: HS.NextFunction) => {
|
|
43
46
|
const query = formDataToJSON(context.req.query);
|
|
@@ -55,6 +58,9 @@ export function validateQuery(schema: ZodObject | ZodAny): HS.MiddlewareFunction
|
|
|
55
58
|
}
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Validate the body of the request using a Zod schema. Type can be inferred from the Content-Type header or provided explicitly.
|
|
63
|
+
*/
|
|
58
64
|
export function validateBody(schema: ZodObject | ZodAny, type?: TValidationType): HS.MiddlewareFunction {
|
|
59
65
|
return async (context: HS.Context, next: HS.NextFunction) => {
|
|
60
66
|
// Infer type from Content-Type header if not provided
|
package/src/server.ts
CHANGED
|
@@ -122,11 +122,11 @@ export function createContext(req: Request, route?: HS.Route): HS.Context {
|
|
|
122
122
|
export function createRoute(config: Partial<HS.RouteConfig> = {}): HS.Route {
|
|
123
123
|
const _handlers: Record<string, HS.RouteHandler> = {};
|
|
124
124
|
let _errorHandler: HS.ErrorHandler | undefined = undefined;
|
|
125
|
-
let _middleware: Record<string, Array<HS.MiddlewareFunction>> = { '*': [] };
|
|
126
125
|
|
|
127
126
|
const api: HS.Route = {
|
|
128
127
|
_kind: 'hsRoute',
|
|
129
128
|
_config: config,
|
|
129
|
+
_middleware: { GET: [], POST: [], PUT: [], PATCH: [], DELETE: [], HEAD: [], OPTIONS: [], '*': [] },
|
|
130
130
|
_methods: () => Object.keys(_handlers),
|
|
131
131
|
_path() {
|
|
132
132
|
if (this._config.path) {
|
|
@@ -141,7 +141,7 @@ export function createRoute(config: Partial<HS.RouteConfig> = {}): HS.Route {
|
|
|
141
141
|
*/
|
|
142
142
|
get(handler: HS.RouteHandler, handlerOptions?: HS.RouteHandlerOptions) {
|
|
143
143
|
_handlers['GET'] = handler;
|
|
144
|
-
_middleware['GET'] = handlerOptions?.middleware || [];
|
|
144
|
+
api._middleware['GET'] = handlerOptions?.middleware || [];
|
|
145
145
|
return api;
|
|
146
146
|
},
|
|
147
147
|
/**
|
|
@@ -149,7 +149,7 @@ export function createRoute(config: Partial<HS.RouteConfig> = {}): HS.Route {
|
|
|
149
149
|
*/
|
|
150
150
|
post(handler: HS.RouteHandler, handlerOptions?: HS.RouteHandlerOptions) {
|
|
151
151
|
_handlers['POST'] = handler;
|
|
152
|
-
_middleware['POST'] = handlerOptions?.middleware || [];
|
|
152
|
+
api._middleware['POST'] = handlerOptions?.middleware || [];
|
|
153
153
|
return api;
|
|
154
154
|
},
|
|
155
155
|
/**
|
|
@@ -157,7 +157,7 @@ export function createRoute(config: Partial<HS.RouteConfig> = {}): HS.Route {
|
|
|
157
157
|
*/
|
|
158
158
|
put(handler: HS.RouteHandler, handlerOptions?: HS.RouteHandlerOptions) {
|
|
159
159
|
_handlers['PUT'] = handler;
|
|
160
|
-
_middleware['PUT'] = handlerOptions?.middleware || [];
|
|
160
|
+
api._middleware['PUT'] = handlerOptions?.middleware || [];
|
|
161
161
|
return api;
|
|
162
162
|
},
|
|
163
163
|
/**
|
|
@@ -165,7 +165,7 @@ export function createRoute(config: Partial<HS.RouteConfig> = {}): HS.Route {
|
|
|
165
165
|
*/
|
|
166
166
|
patch(handler: HS.RouteHandler, handlerOptions?: HS.RouteHandlerOptions) {
|
|
167
167
|
_handlers['PATCH'] = handler;
|
|
168
|
-
_middleware['PATCH'] = handlerOptions?.middleware || [];
|
|
168
|
+
api._middleware['PATCH'] = handlerOptions?.middleware || [];
|
|
169
169
|
return api;
|
|
170
170
|
},
|
|
171
171
|
/**
|
|
@@ -173,7 +173,7 @@ export function createRoute(config: Partial<HS.RouteConfig> = {}): HS.Route {
|
|
|
173
173
|
*/
|
|
174
174
|
delete(handler: HS.RouteHandler, handlerOptions?: HS.RouteHandlerOptions) {
|
|
175
175
|
_handlers['DELETE'] = handler;
|
|
176
|
-
_middleware['DELETE'] = handlerOptions?.middleware || [];
|
|
176
|
+
api._middleware['DELETE'] = handlerOptions?.middleware || [];
|
|
177
177
|
return api;
|
|
178
178
|
},
|
|
179
179
|
/**
|
|
@@ -181,7 +181,7 @@ export function createRoute(config: Partial<HS.RouteConfig> = {}): HS.Route {
|
|
|
181
181
|
*/
|
|
182
182
|
options(handler: HS.RouteHandler, handlerOptions?: HS.RouteHandlerOptions) {
|
|
183
183
|
_handlers['OPTIONS'] = handler;
|
|
184
|
-
_middleware['OPTIONS'] = handlerOptions?.middleware || [];
|
|
184
|
+
api._middleware['OPTIONS'] = handlerOptions?.middleware || [];
|
|
185
185
|
return api;
|
|
186
186
|
},
|
|
187
187
|
/**
|
|
@@ -189,7 +189,7 @@ export function createRoute(config: Partial<HS.RouteConfig> = {}): HS.Route {
|
|
|
189
189
|
*/
|
|
190
190
|
all(handler: HS.RouteHandler, handlerOptions?: HS.RouteHandlerOptions) {
|
|
191
191
|
_handlers['*'] = handler;
|
|
192
|
-
_middleware['*'] = handlerOptions?.middleware || [];
|
|
192
|
+
api._middleware['*'] = handlerOptions?.middleware || [];
|
|
193
193
|
return api;
|
|
194
194
|
},
|
|
195
195
|
/**
|
|
@@ -202,16 +202,24 @@ export function createRoute(config: Partial<HS.RouteConfig> = {}): HS.Route {
|
|
|
202
202
|
/**
|
|
203
203
|
* Add a middleware function to this route (for all HTTP methods) (non-destructive)
|
|
204
204
|
*/
|
|
205
|
-
use(middleware: HS.MiddlewareFunction) {
|
|
206
|
-
|
|
205
|
+
use(middleware: HS.MiddlewareFunction, opts: { method?: HS.MiddlewareMethod } = {}) {
|
|
206
|
+
if (opts.method) {
|
|
207
|
+
api._middleware[opts.method].push(middleware);
|
|
208
|
+
} else {
|
|
209
|
+
api._middleware['*'].push(middleware);
|
|
210
|
+
}
|
|
207
211
|
return api;
|
|
208
212
|
},
|
|
209
213
|
/**
|
|
210
214
|
* Set the complete middleware stack for this route (for all HTTP methods) (destructive)
|
|
211
215
|
* NOTE: This will override the middleware stack for this route
|
|
212
216
|
*/
|
|
213
|
-
middleware(middleware: Array<HS.MiddlewareFunction
|
|
214
|
-
|
|
217
|
+
middleware(middleware: Array<HS.MiddlewareFunction>, opts: { method?: HS.MiddlewareMethod } = {}) {
|
|
218
|
+
if (opts.method) {
|
|
219
|
+
api._middleware[opts.method] = middleware;
|
|
220
|
+
} else {
|
|
221
|
+
api._middleware['*'] = middleware;
|
|
222
|
+
}
|
|
215
223
|
return api;
|
|
216
224
|
},
|
|
217
225
|
|
|
@@ -220,9 +228,9 @@ export function createRoute(config: Partial<HS.RouteConfig> = {}): HS.Route {
|
|
|
220
228
|
*/
|
|
221
229
|
async fetch(request: Request) {
|
|
222
230
|
const context = createContext(request, api);
|
|
223
|
-
const method = context.req.method;
|
|
224
|
-
const globalMiddleware = _middleware['*'] || [];
|
|
225
|
-
const methodMiddleware = _middleware[method] || [];
|
|
231
|
+
const method = context.req.method as HS.MiddlewareMethod;
|
|
232
|
+
const globalMiddleware = api._middleware['*'] || [];
|
|
233
|
+
const methodMiddleware = api._middleware[method] || [];
|
|
226
234
|
|
|
227
235
|
const methodHandler = async (context: HS.Context) => {
|
|
228
236
|
// Handle CORS preflight requests (if no OPTIONS handler is defined)
|
|
@@ -253,8 +261,18 @@ export function createRoute(config: Partial<HS.RouteConfig> = {}): HS.Route {
|
|
|
253
261
|
return context.res.error(new Error('Method not allowed'), { status: 405 });
|
|
254
262
|
}
|
|
255
263
|
|
|
256
|
-
//
|
|
257
|
-
|
|
264
|
+
// Run the route handler
|
|
265
|
+
let routeContent: unknown;
|
|
266
|
+
try {
|
|
267
|
+
routeContent = await handler(context);
|
|
268
|
+
} catch (e) {
|
|
269
|
+
// Return the response from the HTTPResponseException if it exists
|
|
270
|
+
if (e instanceof HTTPResponseException) {
|
|
271
|
+
routeContent = e._response;
|
|
272
|
+
} else {
|
|
273
|
+
throw e;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
258
276
|
|
|
259
277
|
// Return Response if returned from route handler
|
|
260
278
|
if (routeContent instanceof Response) {
|
|
@@ -296,7 +314,6 @@ export function createRoute(config: Partial<HS.RouteConfig> = {}): HS.Route {
|
|
|
296
314
|
* Creates a server object that can compose routes and middleware
|
|
297
315
|
*/
|
|
298
316
|
export async function createServer(config: HS.Config = {} as HS.Config): Promise<HS.Server> {
|
|
299
|
-
const _middleware: HS.MiddlewareFunction[] = [];
|
|
300
317
|
const _routes: HS.Route[] = [];
|
|
301
318
|
|
|
302
319
|
// Load plugins, if any
|
|
@@ -307,9 +324,13 @@ export async function createServer(config: HS.Config = {} as HS.Config): Promise
|
|
|
307
324
|
const api: HS.Server = {
|
|
308
325
|
_config: config,
|
|
309
326
|
_routes: _routes,
|
|
310
|
-
_middleware:
|
|
311
|
-
use(middleware: HS.MiddlewareFunction) {
|
|
312
|
-
|
|
327
|
+
_middleware: { GET: [], POST: [], PUT: [], PATCH: [], DELETE: [], HEAD: [], OPTIONS: [], '*': [] },
|
|
328
|
+
use(middleware: HS.MiddlewareFunction, opts?: HS.MiddlewareMethodOptions) {
|
|
329
|
+
if (opts?.method) {
|
|
330
|
+
api._middleware[opts.method].push(middleware);
|
|
331
|
+
} else {
|
|
332
|
+
api._middleware['*'].push(middleware);
|
|
333
|
+
}
|
|
313
334
|
return this;
|
|
314
335
|
},
|
|
315
336
|
get(path: string, handler: HS.RouteHandler, handlerOptions?: HS.RouteHandlerOptions) {
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { HSHtml } from '@hyperspan/html';
|
|
2
|
-
import { ZodValidationError } from './middleware';
|
|
3
2
|
import * as z from 'zod/v4';
|
|
4
3
|
|
|
5
4
|
/**
|
|
@@ -9,8 +8,8 @@ export namespace Hyperspan {
|
|
|
9
8
|
export interface Server {
|
|
10
9
|
_config: Hyperspan.Config;
|
|
11
10
|
_routes: Array<Hyperspan.Route>;
|
|
12
|
-
_middleware: Array<Hyperspan.MiddlewareFunction
|
|
13
|
-
use: (middleware: Hyperspan.MiddlewareFunction) => Hyperspan.Server;
|
|
11
|
+
_middleware: Record<Hyperspan.MiddlewareMethod, Array<Hyperspan.MiddlewareFunction>>;
|
|
12
|
+
use: (middleware: Hyperspan.MiddlewareFunction, opts?: Hyperspan.MiddlewareMethodOptions) => Hyperspan.Server;
|
|
14
13
|
get: (path: string, handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
15
14
|
post: (path: string, handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
16
15
|
put: (path: string, handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
@@ -134,11 +133,16 @@ export namespace Hyperspan {
|
|
|
134
133
|
context: Hyperspan.Context,
|
|
135
134
|
next: Hyperspan.NextFunction
|
|
136
135
|
) => Promise<Response> | Response;
|
|
136
|
+
export type MiddlewareMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS' | '*';
|
|
137
|
+
export type MiddlewareMethodOptions = {
|
|
138
|
+
method?: Hyperspan.MiddlewareMethod;
|
|
139
|
+
};
|
|
137
140
|
|
|
138
141
|
export interface Route {
|
|
139
142
|
_kind: 'hsRoute';
|
|
140
143
|
_config: Partial<Hyperspan.RouteConfig>;
|
|
141
144
|
_serverConfig?: Hyperspan.Config;
|
|
145
|
+
_middleware: Record<MiddlewareMethod, Array<Hyperspan.MiddlewareFunction>>;
|
|
142
146
|
_path(): string;
|
|
143
147
|
_methods(): string[];
|
|
144
148
|
get: (handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
@@ -149,8 +153,8 @@ export namespace Hyperspan {
|
|
|
149
153
|
options: (handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
150
154
|
all: (handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
151
155
|
errorHandler: (handler: Hyperspan.ErrorHandler) => Hyperspan.Route;
|
|
152
|
-
use: (middleware: Hyperspan.MiddlewareFunction) => Hyperspan.Route;
|
|
153
|
-
middleware: (middleware: Array<Hyperspan.MiddlewareFunction
|
|
156
|
+
use: (middleware: Hyperspan.MiddlewareFunction, opts?: Hyperspan.MiddlewareMethodOptions) => Hyperspan.Route;
|
|
157
|
+
middleware: (middleware: Array<Hyperspan.MiddlewareFunction>, opts?: Hyperspan.MiddlewareMethodOptions) => Hyperspan.Route;
|
|
154
158
|
fetch: (request: Request) => Promise<Response>;
|
|
155
159
|
};
|
|
156
160
|
|
|
@@ -179,8 +183,8 @@ export namespace Hyperspan {
|
|
|
179
183
|
render: (c: Context, props?: ActionFormProps<T>) => ActionFormResponse;
|
|
180
184
|
post: (handler: ActionFormHandler<T>) => Action<T>;
|
|
181
185
|
errorHandler: (handler: ActionFormHandler<T>) => Action<T>;
|
|
182
|
-
use: (middleware: Hyperspan.MiddlewareFunction) => Action<T>;
|
|
183
|
-
middleware: (middleware: Array<Hyperspan.MiddlewareFunction
|
|
186
|
+
use: (middleware: Hyperspan.MiddlewareFunction, opts?: Hyperspan.MiddlewareMethodOptions) => Action<T>;
|
|
187
|
+
middleware: (middleware: Array<Hyperspan.MiddlewareFunction>, opts?: Hyperspan.MiddlewareMethodOptions) => Action<T>;
|
|
184
188
|
fetch: (request: Request) => Promise<Response>;
|
|
185
189
|
}
|
|
186
190
|
|
|
@@ -198,4 +202,12 @@ export namespace Hyperspan {
|
|
|
198
202
|
*/
|
|
199
203
|
renderScriptTag: (loadScript?: ((module: unknown) => HSHtml | string | void) | string) => HSHtml;
|
|
200
204
|
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Zod validation error type. Used in actions and validation middleware.
|
|
208
|
+
*/
|
|
209
|
+
export interface ZodValidationError extends Error {
|
|
210
|
+
fieldErrors: Record<string, string[] | undefined>;
|
|
211
|
+
formErrors: unknown[];
|
|
212
|
+
}
|
|
201
213
|
}
|