@tahminator/sapling 1.5.28-beta.260afa93 → 1.5.28-beta.599ffb98

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/dist/index.cjs CHANGED
@@ -310,6 +310,34 @@ var Sapling = class Sapling {
310
310
  app.use(Sapling.json());
311
311
  }
312
312
  /**
313
+ * Register a middleware that will handle {@link ResponseStatusError}.
314
+ *
315
+ * This middleware will chain to the next middleware if it does not catch {@link ResponseStatusError}.
316
+ * You may still define middleware to handle all other errors in a separate `app.use` call.
317
+ *
318
+ * @example
319
+ * ```ts
320
+ * import express from "express";
321
+ * import { Sapling } from "@tahminator/sapling";
322
+ *
323
+ * const app = express();
324
+ *
325
+ * Sapling.loadResponseStatusErrorMiddleware(app, (err, req, res, next) => {
326
+ * // `err` is guaranteed to be of type ResponseStatusError
327
+ * res.status(err.status).json({
328
+ * success: false,
329
+ * message: err.message,
330
+ * });
331
+ * });
332
+ * ```
333
+ */
334
+ static loadResponseStatusErrorMiddleware(app, fn) {
335
+ app.use(((err, req, res, next) => {
336
+ if (err instanceof ResponseStatusError) fn(err, req, res, next);
337
+ else next(err);
338
+ }));
339
+ }
340
+ /**
313
341
  * Serialize a value into a JSON string.
314
342
  *
315
343
  * This function is used in {@link ResponseEntity} to serialize the `body`.
@@ -650,14 +678,6 @@ function Controller({ prefix = "", deps = [] } = {}) {
650
678
  const usedRoutes = /* @__PURE__ */ new Set();
651
679
  _InjectableDeps.set(targetClass, deps);
652
680
  const controllerInstance = _resolve(targetClass);
653
- if (routes.reduce((prev, r) => {
654
- if (r.method !== "USE") return prev;
655
- const fn = controllerInstance[r.fnName];
656
- return typeof fn === "function" && fn.length >= 4 ? prev + 1 : prev;
657
- }, 0) > 1) throw new Error(`Invalid @MiddlewareClass class "${targetClass.name}":
658
- Multiple 4-arg @Middleware() error handlers were found.
659
- Express will not enter routers in error mode, so an error-middleware class must expose exactly one error handler.
660
- Split these into separate @MiddlewareClass classes, or merge the logic into a single method.`);
661
681
  for (const { method, path, fnName } of routes) {
662
682
  const fn = controllerInstance[fnName];
663
683
  if (typeof fn !== "function") continue;
@@ -666,26 +686,6 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
666
686
  if (method !== "USE" && usedRoutes.has(routeKey)) throw new Error(`Duplicate route [${method}] "${path instanceof RegExp ? path.source : fp}" detected in controller "${target.name}"`);
667
687
  if (method !== "USE") usedRoutes.add(routeKey);
668
688
  const methodName = methodResolve[method];
669
- if (method === "USE" && fn.length >= 4) {
670
- const middlewareFn = async (err, request, response, next) => {
671
- try {
672
- const result = fn.bind(controllerInstance)(err, request, response, next);
673
- if (result instanceof ResponseEntity) {
674
- response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(result.getBody()));
675
- return;
676
- }
677
- if (result instanceof RedirectView) {
678
- response.redirect(result.getUrl());
679
- return;
680
- }
681
- } catch (e) {
682
- console.error(e);
683
- next(e);
684
- }
685
- };
686
- _ControllerRegistry.set(targetClass, middlewareFn);
687
- return;
688
- }
689
689
  router[methodName](fp, async (request, response, next) => {
690
690
  const schemas = _getRequestSchemas(target, fnName);
691
691
  if (schemas) {
@@ -694,6 +694,7 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
694
694
  if (schemas.query) request.query = await _parseOrThrow(schemas.query, request.query, "reqquery");
695
695
  }
696
696
  const result = await fn.bind(controllerInstance)(request, response, next);
697
+ if (method === "USE") return;
697
698
  if (result instanceof ResponseEntity) {
698
699
  response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(result.getBody()));
699
700
  return;
@@ -702,7 +703,7 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
702
703
  response.redirect(result.getUrl());
703
704
  return;
704
705
  }
705
- if (method !== "USE" && !response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${methodName.toUpperCase()} ${path instanceof RegExp ? path.source : fp}`));
706
+ if (!response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${methodName.toUpperCase()} ${path instanceof RegExp ? path.source : fp}`));
706
707
  });
707
708
  }
708
709
  _ControllerRegistry.set(targetClass, router);
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import e, { ErrorRequestHandler, NextFunction, Request, Response, Router } from "express";
1
+ import e, { NextFunction, Request, Response, Router } from "express";
2
2
 
3
3
  //#region src/html/404.d.ts
4
4
  /**
@@ -29,7 +29,7 @@ type HttpHeaders = Record<string, string>;
29
29
  type ExpressMiddlewareFn = ($1: Request, $2: Response, $3: NextFunction) => void;
30
30
  //#endregion
31
31
  //#region src/annotation/controller.d.ts
32
- declare const _ControllerRegistry: WeakMap<Function, Router | ErrorRequestHandler>;
32
+ declare const _ControllerRegistry: WeakMap<Function, Router>;
33
33
  type ControllerProps = {
34
34
  /**
35
35
  * Optional URL prefix applied to all routes in the controller. Defaults to "".
@@ -416,7 +416,7 @@ declare class Sapling {
416
416
  * app.use(router);
417
417
  * ```
418
418
  */
419
- static resolve<TClass>(this: void, clazz: Class<TClass>): Router | ErrorRequestHandler;
419
+ static resolve<TClass>(this: void, clazz: Class<TClass>): Router;
420
420
  /**
421
421
  * Register this function as a middleware in order to utilize Sapling's `deserialize` function.
422
422
  *
@@ -443,6 +443,29 @@ declare class Sapling {
443
443
  * ```
444
444
  */
445
445
  static registerApp(app: e.Express): void;
446
+ /**
447
+ * Register a middleware that will handle {@link ResponseStatusError}.
448
+ *
449
+ * This middleware will chain to the next middleware if it does not catch {@link ResponseStatusError}.
450
+ * You may still define middleware to handle all other errors in a separate `app.use` call.
451
+ *
452
+ * @example
453
+ * ```ts
454
+ * import express from "express";
455
+ * import { Sapling } from "@tahminator/sapling";
456
+ *
457
+ * const app = express();
458
+ *
459
+ * Sapling.loadResponseStatusErrorMiddleware(app, (err, req, res, next) => {
460
+ * // `err` is guaranteed to be of type ResponseStatusError
461
+ * res.status(err.status).json({
462
+ * success: false,
463
+ * message: err.message,
464
+ * });
465
+ * });
466
+ * ```
467
+ */
468
+ static loadResponseStatusErrorMiddleware(this: void, app: e.Express, fn: (err: ResponseStatusError, request: e.Request, response: e.Response, next: e.NextFunction) => void): void;
446
469
  /**
447
470
  * Serialize a value into a JSON string.
448
471
  *
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import e, { ErrorRequestHandler, NextFunction, Request, Response, Router } from "express";
1
+ import e, { NextFunction, Request, Response, Router } from "express";
2
2
 
3
3
  //#region src/html/404.d.ts
4
4
  /**
@@ -29,7 +29,7 @@ type HttpHeaders = Record<string, string>;
29
29
  type ExpressMiddlewareFn = ($1: Request, $2: Response, $3: NextFunction) => void;
30
30
  //#endregion
31
31
  //#region src/annotation/controller.d.ts
32
- declare const _ControllerRegistry: WeakMap<Function, Router | ErrorRequestHandler>;
32
+ declare const _ControllerRegistry: WeakMap<Function, Router>;
33
33
  type ControllerProps = {
34
34
  /**
35
35
  * Optional URL prefix applied to all routes in the controller. Defaults to "".
@@ -416,7 +416,7 @@ declare class Sapling {
416
416
  * app.use(router);
417
417
  * ```
418
418
  */
419
- static resolve<TClass>(this: void, clazz: Class<TClass>): Router | ErrorRequestHandler;
419
+ static resolve<TClass>(this: void, clazz: Class<TClass>): Router;
420
420
  /**
421
421
  * Register this function as a middleware in order to utilize Sapling's `deserialize` function.
422
422
  *
@@ -443,6 +443,29 @@ declare class Sapling {
443
443
  * ```
444
444
  */
445
445
  static registerApp(app: e.Express): void;
446
+ /**
447
+ * Register a middleware that will handle {@link ResponseStatusError}.
448
+ *
449
+ * This middleware will chain to the next middleware if it does not catch {@link ResponseStatusError}.
450
+ * You may still define middleware to handle all other errors in a separate `app.use` call.
451
+ *
452
+ * @example
453
+ * ```ts
454
+ * import express from "express";
455
+ * import { Sapling } from "@tahminator/sapling";
456
+ *
457
+ * const app = express();
458
+ *
459
+ * Sapling.loadResponseStatusErrorMiddleware(app, (err, req, res, next) => {
460
+ * // `err` is guaranteed to be of type ResponseStatusError
461
+ * res.status(err.status).json({
462
+ * success: false,
463
+ * message: err.message,
464
+ * });
465
+ * });
466
+ * ```
467
+ */
468
+ static loadResponseStatusErrorMiddleware(this: void, app: e.Express, fn: (err: ResponseStatusError, request: e.Request, response: e.Response, next: e.NextFunction) => void): void;
446
469
  /**
447
470
  * Serialize a value into a JSON string.
448
471
  *
package/dist/index.mjs CHANGED
@@ -286,6 +286,34 @@ var Sapling = class Sapling {
286
286
  app.use(Sapling.json());
287
287
  }
288
288
  /**
289
+ * Register a middleware that will handle {@link ResponseStatusError}.
290
+ *
291
+ * This middleware will chain to the next middleware if it does not catch {@link ResponseStatusError}.
292
+ * You may still define middleware to handle all other errors in a separate `app.use` call.
293
+ *
294
+ * @example
295
+ * ```ts
296
+ * import express from "express";
297
+ * import { Sapling } from "@tahminator/sapling";
298
+ *
299
+ * const app = express();
300
+ *
301
+ * Sapling.loadResponseStatusErrorMiddleware(app, (err, req, res, next) => {
302
+ * // `err` is guaranteed to be of type ResponseStatusError
303
+ * res.status(err.status).json({
304
+ * success: false,
305
+ * message: err.message,
306
+ * });
307
+ * });
308
+ * ```
309
+ */
310
+ static loadResponseStatusErrorMiddleware(app, fn) {
311
+ app.use(((err, req, res, next) => {
312
+ if (err instanceof ResponseStatusError) fn(err, req, res, next);
313
+ else next(err);
314
+ }));
315
+ }
316
+ /**
289
317
  * Serialize a value into a JSON string.
290
318
  *
291
319
  * This function is used in {@link ResponseEntity} to serialize the `body`.
@@ -626,14 +654,6 @@ function Controller({ prefix = "", deps = [] } = {}) {
626
654
  const usedRoutes = /* @__PURE__ */ new Set();
627
655
  _InjectableDeps.set(targetClass, deps);
628
656
  const controllerInstance = _resolve(targetClass);
629
- if (routes.reduce((prev, r) => {
630
- if (r.method !== "USE") return prev;
631
- const fn = controllerInstance[r.fnName];
632
- return typeof fn === "function" && fn.length >= 4 ? prev + 1 : prev;
633
- }, 0) > 1) throw new Error(`Invalid @MiddlewareClass class "${targetClass.name}":
634
- Multiple 4-arg @Middleware() error handlers were found.
635
- Express will not enter routers in error mode, so an error-middleware class must expose exactly one error handler.
636
- Split these into separate @MiddlewareClass classes, or merge the logic into a single method.`);
637
657
  for (const { method, path, fnName } of routes) {
638
658
  const fn = controllerInstance[fnName];
639
659
  if (typeof fn !== "function") continue;
@@ -642,26 +662,6 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
642
662
  if (method !== "USE" && usedRoutes.has(routeKey)) throw new Error(`Duplicate route [${method}] "${path instanceof RegExp ? path.source : fp}" detected in controller "${target.name}"`);
643
663
  if (method !== "USE") usedRoutes.add(routeKey);
644
664
  const methodName = methodResolve[method];
645
- if (method === "USE" && fn.length >= 4) {
646
- const middlewareFn = async (err, request, response, next) => {
647
- try {
648
- const result = fn.bind(controllerInstance)(err, request, response, next);
649
- if (result instanceof ResponseEntity) {
650
- response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(result.getBody()));
651
- return;
652
- }
653
- if (result instanceof RedirectView) {
654
- response.redirect(result.getUrl());
655
- return;
656
- }
657
- } catch (e) {
658
- console.error(e);
659
- next(e);
660
- }
661
- };
662
- _ControllerRegistry.set(targetClass, middlewareFn);
663
- return;
664
- }
665
665
  router[methodName](fp, async (request, response, next) => {
666
666
  const schemas = _getRequestSchemas(target, fnName);
667
667
  if (schemas) {
@@ -670,6 +670,7 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
670
670
  if (schemas.query) request.query = await _parseOrThrow(schemas.query, request.query, "reqquery");
671
671
  }
672
672
  const result = await fn.bind(controllerInstance)(request, response, next);
673
+ if (method === "USE") return;
673
674
  if (result instanceof ResponseEntity) {
674
675
  response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(result.getBody()));
675
676
  return;
@@ -678,7 +679,7 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
678
679
  response.redirect(result.getUrl());
679
680
  return;
680
681
  }
681
- if (method !== "USE" && !response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${methodName.toUpperCase()} ${path instanceof RegExp ? path.source : fp}`));
682
+ if (!response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${methodName.toUpperCase()} ${path instanceof RegExp ? path.source : fp}`));
682
683
  });
683
684
  }
684
685
  _ControllerRegistry.set(targetClass, router);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tahminator/sapling",
3
- "version": "1.5.28-beta.260afa93",
3
+ "version": "1.5.28-beta.599ffb98",
4
4
  "author": "Tahmid Ahmed",
5
5
  "description": "A library to help you write cleaner Express.js code",
6
6
  "repository": {