@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperspan/framework",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "Hyperspan Web Framework",
5
5
  "main": "src/server.ts",
6
6
  "types": "src/server.ts",
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
- _middleware['*'].push(middleware);
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
- _middleware['*'] = middleware;
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
- // @TODO: Handle errors from route handler
257
- const routeContent = await handler(context);
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: _middleware,
311
- use(middleware: HS.MiddlewareFunction) {
312
- _middleware.push(middleware);
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>) => Hyperspan.Route;
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>) => Action<T>;
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
  }