@tahminator/sapling 2.0.3 → 2.0.5-beta.23c37926

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.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import e, { Router } from "express";
2
+ import swagger from "swagger-ui-express";
2
3
  //#region src/html/404.ts
3
4
  /**
4
5
  * Default Express.js 404 error page, as a string.
@@ -238,9 +239,13 @@ var ParserError = class ParserError extends ResponseStatusError {
238
239
  };
239
240
  //#endregion
240
241
  //#region src/helper/sapling.ts
241
- const settings = {
242
+ const _settings = {
242
243
  serialize: JSON.stringify,
243
- deserialize: JSON.parse
244
+ deserialize: JSON.parse,
245
+ doc: {
246
+ openApiPath: "/openapi.json",
247
+ swaggerPath: "/swagger.html"
248
+ }
244
249
  };
245
250
  /**
246
251
  * Collection of utility functions which are essential for Sapling to function.
@@ -319,13 +324,13 @@ var Sapling = class Sapling {
319
324
  * @defaultValue `JSON.stringify`
320
325
  */
321
326
  static serialize(value) {
322
- return settings.serialize(value);
327
+ return _settings.serialize(value);
323
328
  }
324
329
  /**
325
330
  * Replace the function used for `serialize`.
326
331
  */
327
332
  static setSerializeFn(fn) {
328
- settings.serialize = fn;
333
+ _settings.serialize = fn;
329
334
  }
330
335
  /**
331
336
  * De-serialize a JSON string back to a JavaScript object.
@@ -337,16 +342,321 @@ var Sapling = class Sapling {
337
342
  * @defaultValue `JSON.parse`
338
343
  */
339
344
  static deserialize(value) {
340
- return settings.deserialize(value);
345
+ return _settings.deserialize(value);
341
346
  }
342
347
  /**
343
348
  * Replace the function used for `deserialize`
344
349
  */
345
350
  static setDeserializeFn(fn) {
346
- settings.deserialize = fn;
351
+ _settings.deserialize = fn;
352
+ }
353
+ static setOpenApiPath(path) {
354
+ _settings.doc.openApiPath = path;
355
+ }
356
+ static setSwaggerPath(path) {
357
+ _settings.doc.swaggerPath = path;
347
358
  }
348
359
  };
349
360
  //#endregion
361
+ //#region src/annotation/request.ts
362
+ const _requestSchemaStore = /* @__PURE__ */ new WeakMap();
363
+ /**
364
+ * Apply to a route method to have `request.body` be parsed by `schema`.
365
+ *
366
+ * This annotation will parse `request.body` & then override `request.body`.
367
+ * You can then just simply cast `request.body` for your use
368
+ *
369
+ * @example
370
+ * ```ts
371
+ * const CREATE_BOOK_REQUEST_BODY_SCHEMA = z.object({
372
+ * name: z.string(),
373
+ * description: z.string().optional(),
374
+ * });
375
+ *
376
+ * ⠀@Controller({ prefix: "/api/book" })
377
+ * class BookController {
378
+ * ⠀@RequestBody(CREATE_BOOK_REQUEST_BODY_SCHEMA)
379
+ * ⠀@POST()
380
+ * public createBook(request: e.Request) {
381
+ * const { name, description } = request.body as unknown as z.infer<
382
+ * typeof CREATE_BOOK_REQUEST_BODY_SCHEMA
383
+ * >;
384
+ * }
385
+ * }
386
+ * ```
387
+ */
388
+ function RequestBody(schema) {
389
+ return (target, propertyKey) => {
390
+ const ctor = target.constructor;
391
+ const fnName = String(propertyKey);
392
+ _setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "body", schema, fnName);
393
+ };
394
+ }
395
+ /**
396
+ * Apply to a route method to have `request.param` be parsed by `schema`.
397
+ *
398
+ * This annotation will parse `request.param` & then override `request.param`.
399
+ * You can then just simply cast `request.param` for your use
400
+ *
401
+ * @example
402
+ * ```ts
403
+ * const GET_BOOK_REQUEST_PARAM_SCHEMA = z.object({
404
+ * bookId: z.string(),
405
+ * });
406
+ *
407
+ * ⠀@Controller({ prefix: "/api/book" })
408
+ * class BookController {
409
+ * ⠀@RequestParam(GET_BOOK_REQUEST_PARAM_SCHEMA)
410
+ * ⠀@GET("/:bookId")
411
+ * public getBook(request: e.Request) {
412
+ * const { bookId } = request.param as unknown as z.infer<
413
+ * typeof GET_BOOK_REQUEST_PARAM_SCHEMA
414
+ * >;
415
+ * }
416
+ * }
417
+ * ```
418
+ */
419
+ function RequestParam(schema) {
420
+ return (target, propertyKey) => {
421
+ const ctor = target.constructor;
422
+ const fnName = String(propertyKey);
423
+ _setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "param", schema, fnName);
424
+ };
425
+ }
426
+ /**
427
+ * Apply to a route method to have `request.query` be parsed by `schema`.
428
+ *
429
+ * This annotation will parse `request.query` & then override `request.query`.
430
+ * You can then just simply cast `request.query` for your use
431
+ *
432
+ * @example
433
+ * ```ts
434
+ * const LIST_BOOKS_REQUEST_QUERY_SCHEMA = z.object({
435
+ * sort: z.enum(["name", "createdAt"]).optional(),
436
+ * q: z.string().optional(),
437
+ * });
438
+ *
439
+ * ⠀@Controller({ prefix: "/api/book" })
440
+ * class BookController {
441
+ * ⠀@RequestQuery(LIST_BOOKS_REQUEST_QUERY_SCHEMA)
442
+ * ⠀@GET()
443
+ * public listBooks(request: e.Request) {
444
+ * const { sort, q } = request.query as unknown as z.infer<
445
+ * typeof LIST_BOOKS_REQUEST_QUERY_SCHEMA
446
+ * >;
447
+ * }
448
+ * }
449
+ * ```
450
+ */
451
+ function RequestQuery(schema) {
452
+ return (target, propertyKey) => {
453
+ const ctor = target.constructor;
454
+ const fnName = String(propertyKey);
455
+ _setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "query", schema, fnName);
456
+ };
457
+ }
458
+ function _getOrCreateRequestSchemaDefinition(ctor, fnName) {
459
+ const byFn = (() => {
460
+ const fn = _requestSchemaStore.get(ctor);
461
+ if (fn) return fn;
462
+ const newFn = /* @__PURE__ */ new Map();
463
+ _requestSchemaStore.set(ctor, newFn);
464
+ return newFn;
465
+ })();
466
+ const existing = byFn.get(fnName);
467
+ if (existing) return existing;
468
+ const created = {};
469
+ byFn.set(fnName, created);
470
+ return created;
471
+ }
472
+ function _setOnce(def, key, schema, fnName) {
473
+ if (def[key]) throw new Error(`Duplicate request schema for "${String(key)}" on method "${fnName}"`);
474
+ def[key] = schema;
475
+ }
476
+ function _getRequestSchemas(ctor, fnName) {
477
+ return _requestSchemaStore.get(ctor)?.get(fnName);
478
+ }
479
+ async function _parseOrThrow(schema, input, kind) {
480
+ const result = await schema["~standard"].validate(input);
481
+ if (result.issues) {
482
+ console.debug(`Failed to parse a schema`);
483
+ throw new ParserError(kind, result.issues, schema["~standard"].vendor);
484
+ }
485
+ return result.value;
486
+ }
487
+ //#endregion
488
+ //#region src/annotation/route.ts
489
+ const _routeStore = /* @__PURE__ */ new WeakMap();
490
+ /**
491
+ * Custom annotation that will store all routes inside of a map,
492
+ * which can then be used to initialize all the routes to the router.
493
+ */
494
+ function _Route({ method, path = "" }) {
495
+ return (target, propertyKey) => {
496
+ const ctor = target.constructor;
497
+ const list = _routeStore.get(ctor) ?? [];
498
+ list.push({
499
+ method,
500
+ path: path ?? "",
501
+ fnName: String(propertyKey)
502
+ });
503
+ _routeStore.set(ctor, list);
504
+ };
505
+ }
506
+ /**
507
+ * Register GET route on the given path (default "") for the given controller.
508
+ */
509
+ const GET = (path = "") => _Route({
510
+ method: "GET",
511
+ path
512
+ });
513
+ /**
514
+ * Register POST route on the given path (default "") for the given controller.
515
+ */
516
+ const POST = (path = "") => _Route({
517
+ method: "POST",
518
+ path
519
+ });
520
+ /**
521
+ * Register PUT route on the given path (default "") for the given controller.
522
+ */
523
+ const PUT = (path = "") => _Route({
524
+ method: "PUT",
525
+ path
526
+ });
527
+ /**
528
+ * Register DELETE route on the given path (default "") for the given controller.
529
+ */
530
+ const DELETE = (path = "") => _Route({
531
+ method: "DELETE",
532
+ path
533
+ });
534
+ /**
535
+ * Register OPTIONS route on the given path (default "") for the given controller.
536
+ */
537
+ const OPTIONS = (path = "") => _Route({
538
+ method: "OPTIONS",
539
+ path
540
+ });
541
+ /**
542
+ * Register PATCH route on the given path (default "") for the given controller.
543
+ */
544
+ const PATCH = (path = "") => _Route({
545
+ method: "PATCH",
546
+ path
547
+ });
548
+ /**
549
+ * Register HEAD route on the given path (default "") for the given controller.
550
+ */
551
+ const HEAD = (path = "") => _Route({
552
+ method: "HEAD",
553
+ path
554
+ });
555
+ /**
556
+ * Register a middleware route on the given path (default "") for the given controller.
557
+ */
558
+ const Middleware = (path = "") => _Route({
559
+ method: "USE",
560
+ path
561
+ });
562
+ /**
563
+ * Given a class constructor, fetch all the routes attached.
564
+ */
565
+ function _getRoutes(ctor) {
566
+ return _routeStore.get(ctor) ?? [];
567
+ }
568
+ //#endregion
569
+ //#region src/helper/openapi.ts
570
+ var OpenAPIGenerator = class {
571
+ constructor() {
572
+ this.controllers = /* @__PURE__ */ new Set();
573
+ this.config = {
574
+ title: "API",
575
+ version: "1.0.0"
576
+ };
577
+ }
578
+ setConfig(config) {
579
+ this.config = config;
580
+ }
581
+ registerController(controllerClass, prefix) {
582
+ this.controllers.add({
583
+ class: controllerClass,
584
+ prefix
585
+ });
586
+ }
587
+ generateSpec() {
588
+ const config = this.config;
589
+ const paths = {};
590
+ for (const { class: controllerClass, prefix } of this.controllers) {
591
+ const routes = _getRoutes(controllerClass);
592
+ for (const route of routes) {
593
+ if (route.method === "USE") continue;
594
+ const schemas = _getRequestSchemas(controllerClass, route.fnName);
595
+ const fullPath = route.path instanceof RegExp ? route.path.source : prefix + route.path;
596
+ const openApiPath = typeof fullPath === "string" ? fullPath.replace(/:(\w+)/g, "{$1}") : fullPath;
597
+ if (!paths[openApiPath]) paths[openApiPath] = {};
598
+ const operation = { responses: { "200": { description: "Successful response" } } };
599
+ const parameters = [];
600
+ if (schemas?.param) {
601
+ const paramSchema = this.toJsonSchema(schemas.param);
602
+ if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties)) parameters.push({
603
+ name,
604
+ in: "path",
605
+ required: true,
606
+ schema
607
+ });
608
+ }
609
+ if (schemas?.query) {
610
+ const querySchema = this.toJsonSchema(schemas.query);
611
+ if (querySchema.type === "object" && querySchema.properties) for (const [name, schema] of Object.entries(querySchema.properties)) {
612
+ const isRequired = Array.isArray(querySchema.required) && querySchema.required.includes(name);
613
+ parameters.push({
614
+ name,
615
+ in: "query",
616
+ required: isRequired,
617
+ schema
618
+ });
619
+ }
620
+ }
621
+ if (parameters.length > 0) operation.parameters = parameters;
622
+ if (schemas?.body) operation.requestBody = {
623
+ required: true,
624
+ content: { "application/json": { schema: this.toJsonSchema(schemas.body) } }
625
+ };
626
+ const method = route.method.toLowerCase();
627
+ paths[openApiPath][method] = operation;
628
+ }
629
+ }
630
+ return {
631
+ openapi: "3.0.0",
632
+ info: {
633
+ title: config.title,
634
+ version: config.version,
635
+ description: config.description
636
+ },
637
+ paths
638
+ };
639
+ }
640
+ toJsonSchema(schema) {
641
+ try {
642
+ return schema["~standard"].jsonSchema.output({ target: "openapi-3.0" });
643
+ } catch (e) {
644
+ if (e instanceof Error && e.message.includes("Transforms cannot be represented in JSON Schema")) throw new Error(`${e.message}.\nIt appears that you are using z.transform() - it is highly recommended that you use z.codec instead - https://zod.dev/codecs`);
645
+ throw e;
646
+ }
647
+ }
648
+ };
649
+ const openApiGenerator = new OpenAPIGenerator();
650
+ function _registerControllerClass(controllerClass, prefix) {
651
+ openApiGenerator.registerController(controllerClass, prefix);
652
+ }
653
+ function _setOpenApiConfig(config) {
654
+ openApiGenerator.setConfig(config);
655
+ }
656
+ function _generateOpenApiSpec() {
657
+ return openApiGenerator.generateSpec();
658
+ }
659
+ //#endregion
350
660
  //#region src/types.ts
351
661
  const methodResolve = {
352
662
  GET: "get",
@@ -479,140 +789,6 @@ function _resolve(ctor) {
479
789
  return _InjectableRegistry.get(ctor);
480
790
  }
481
791
  //#endregion
482
- //#region src/annotation/request.ts
483
- const _requestSchemaStore = /* @__PURE__ */ new WeakMap();
484
- function _getOrCreateRequestSchemaDefinition(ctor, fnName) {
485
- const byFn = (() => {
486
- const fn = _requestSchemaStore.get(ctor);
487
- if (fn) return fn;
488
- const newFn = /* @__PURE__ */ new Map();
489
- _requestSchemaStore.set(ctor, newFn);
490
- return newFn;
491
- })();
492
- const existing = byFn.get(fnName);
493
- if (existing) return existing;
494
- const created = {};
495
- byFn.set(fnName, created);
496
- return created;
497
- }
498
- function _setOnce(def, key, schema, fnName) {
499
- if (def[key]) throw new Error(`Duplicate request schema for "${String(key)}" on method "${fnName}"`);
500
- def[key] = schema;
501
- }
502
- function RequestBody(schema) {
503
- return (target, propertyKey) => {
504
- const ctor = target.constructor;
505
- const fnName = String(propertyKey);
506
- _setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "body", schema, fnName);
507
- };
508
- }
509
- function RequestParam(schema) {
510
- return (target, propertyKey) => {
511
- const ctor = target.constructor;
512
- const fnName = String(propertyKey);
513
- _setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "param", schema, fnName);
514
- };
515
- }
516
- function RequestQuery(schema) {
517
- return (target, propertyKey) => {
518
- const ctor = target.constructor;
519
- const fnName = String(propertyKey);
520
- _setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "query", schema, fnName);
521
- };
522
- }
523
- function _getRequestSchemas(ctor, fnName) {
524
- return _requestSchemaStore.get(ctor)?.get(fnName);
525
- }
526
- async function _parseOrThrow(schema, input, kind) {
527
- const result = await schema["~standard"].validate(input);
528
- if (result.issues) {
529
- console.debug(`Failed to parse a schema`);
530
- throw new ParserError(kind, result.issues, schema["~standard"].vendor);
531
- }
532
- return result.value;
533
- }
534
- //#endregion
535
- //#region src/annotation/route.ts
536
- const _routeStore = /* @__PURE__ */ new WeakMap();
537
- /**
538
- * Custom annotation that will store all routes inside of a map,
539
- * which can then be used to initialize all the routes to the router.
540
- */
541
- function _Route({ method, path = "" }) {
542
- return (target, propertyKey) => {
543
- const ctor = target.constructor;
544
- const list = _routeStore.get(ctor) ?? [];
545
- list.push({
546
- method,
547
- path: path ?? "",
548
- fnName: String(propertyKey)
549
- });
550
- _routeStore.set(ctor, list);
551
- };
552
- }
553
- /**
554
- * Register GET route on the given path (default "") for the given controller.
555
- */
556
- const GET = (path = "") => _Route({
557
- method: "GET",
558
- path
559
- });
560
- /**
561
- * Register POST route on the given path (default "") for the given controller.
562
- */
563
- const POST = (path = "") => _Route({
564
- method: "POST",
565
- path
566
- });
567
- /**
568
- * Register PUT route on the given path (default "") for the given controller.
569
- */
570
- const PUT = (path = "") => _Route({
571
- method: "PUT",
572
- path
573
- });
574
- /**
575
- * Register DELETE route on the given path (default "") for the given controller.
576
- */
577
- const DELETE = (path = "") => _Route({
578
- method: "DELETE",
579
- path
580
- });
581
- /**
582
- * Register OPTIONS route on the given path (default "") for the given controller.
583
- */
584
- const OPTIONS = (path = "") => _Route({
585
- method: "OPTIONS",
586
- path
587
- });
588
- /**
589
- * Register PATCH route on the given path (default "") for the given controller.
590
- */
591
- const PATCH = (path = "") => _Route({
592
- method: "PATCH",
593
- path
594
- });
595
- /**
596
- * Register HEAD route on the given path (default "") for the given controller.
597
- */
598
- const HEAD = (path = "") => _Route({
599
- method: "HEAD",
600
- path
601
- });
602
- /**
603
- * Register a middleware route on the given path (default "") for the given controller.
604
- */
605
- const Middleware = (path = "") => _Route({
606
- method: "USE",
607
- path
608
- });
609
- /**
610
- * Given a class constructor, fetch all the routes attached.
611
- */
612
- function _getRoutes(ctor) {
613
- return _routeStore.get(ctor) ?? [];
614
- }
615
- //#endregion
616
792
  //#region src/annotation/controller.ts
617
793
  const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
618
794
  /**
@@ -624,6 +800,7 @@ const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
624
800
  function Controller({ prefix = "", deps = [] } = {}) {
625
801
  return (target) => {
626
802
  const targetClass = target;
803
+ _registerControllerClass(target, prefix);
627
804
  const router = Router();
628
805
  const routes = _getRoutes(target);
629
806
  const usedRoutes = /* @__PURE__ */ new Set();
@@ -715,7 +892,7 @@ function __decorate(decorators, target, key, desc) {
715
892
  return c > 3 && r && Object.defineProperty(target, key, r), r;
716
893
  }
717
894
  //#endregion
718
- //#region src/middleware/default/base.ts
895
+ //#region src/middleware/default/error/base.ts
719
896
  let DefaultBaseErrorMiddleware = class DefaultBaseErrorMiddleware {
720
897
  handle(err, _request, _response, _next) {
721
898
  console.error("[Error]", err);
@@ -725,7 +902,17 @@ let DefaultBaseErrorMiddleware = class DefaultBaseErrorMiddleware {
725
902
  __decorate([Middleware()], DefaultBaseErrorMiddleware.prototype, "handle", null);
726
903
  DefaultBaseErrorMiddleware = __decorate([MiddlewareClass()], DefaultBaseErrorMiddleware);
727
904
  //#endregion
728
- //#region src/middleware/default/responsestatus.ts
905
+ //#region src/middleware/default/error/parse.ts
906
+ let DefaultParserErrorMiddleware = class DefaultParserErrorMiddleware {
907
+ handle(err, _request, _response, next) {
908
+ if (err instanceof ParserError) return ResponseEntity.status(err.status).body({ message: err.message });
909
+ next(err);
910
+ }
911
+ };
912
+ __decorate([Middleware()], DefaultParserErrorMiddleware.prototype, "handle", null);
913
+ DefaultParserErrorMiddleware = __decorate([MiddlewareClass()], DefaultParserErrorMiddleware);
914
+ //#endregion
915
+ //#region src/middleware/default/error/responsestatus.ts
729
916
  let DefaultResponseStatusErrorMiddleware = class DefaultResponseStatusErrorMiddleware {
730
917
  handle(err, _request, _response, next) {
731
918
  if (err instanceof ResponseStatusError) return ResponseEntity.status(err.status).body({ message: err.message });
@@ -735,4 +922,39 @@ let DefaultResponseStatusErrorMiddleware = class DefaultResponseStatusErrorMiddl
735
922
  __decorate([Middleware()], DefaultResponseStatusErrorMiddleware.prototype, "handle", null);
736
923
  DefaultResponseStatusErrorMiddleware = __decorate([MiddlewareClass()], DefaultResponseStatusErrorMiddleware);
737
924
  //#endregion
738
- export { Controller, DELETE, DefaultBaseErrorMiddleware, DefaultResponseStatusErrorMiddleware, GET, HEAD, Html404ErrorPage, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, ParserError, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, Sapling, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _getRequestSchemas, _getRoutes, _parseOrThrow, _resolve, methodResolve };
925
+ //#region src/middleware/default/openapi/index.ts
926
+ let DefaultOpenApiMiddleware = class DefaultOpenApiMiddleware {
927
+ handle(_request, _response, _next) {
928
+ return ResponseEntity.ok().body(_generateOpenApiSpec());
929
+ }
930
+ };
931
+ __decorate([GET(_settings.doc.openApiPath)], DefaultOpenApiMiddleware.prototype, "handle", null);
932
+ DefaultOpenApiMiddleware = __decorate([MiddlewareClass()], DefaultOpenApiMiddleware);
933
+ //#endregion
934
+ //#region src/middleware/default/swagger/index.ts
935
+ let Serve = class Serve {
936
+ constructor() {
937
+ this.handlers = swagger.serve;
938
+ }
939
+ handle(_request, _response, _next) {
940
+ return this.handlers;
941
+ }
942
+ };
943
+ __decorate([Middleware(_settings.doc.swaggerPath)], Serve.prototype, "handle", null);
944
+ Serve = __decorate([MiddlewareClass()], Serve);
945
+ let Setup = class Setup {
946
+ constructor() {
947
+ this.handler = swagger.setup(void 0, { swaggerOptions: { url: _settings.doc.openApiPath } });
948
+ }
949
+ handle(request, response, next) {
950
+ return this.handler(request, response, next);
951
+ }
952
+ };
953
+ __decorate([Middleware(_settings.doc.swaggerPath)], Setup.prototype, "handle", null);
954
+ Setup = __decorate([MiddlewareClass()], Setup);
955
+ const DefaultSwaggerMiddleware = {
956
+ Serve,
957
+ Setup
958
+ };
959
+ //#endregion
960
+ export { Controller, DELETE, DefaultBaseErrorMiddleware, DefaultOpenApiMiddleware, DefaultParserErrorMiddleware, DefaultResponseStatusErrorMiddleware, DefaultSwaggerMiddleware, GET, HEAD, Html404ErrorPage, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, ParserError, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, Sapling, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _generateOpenApiSpec, _getRequestSchemas, _getRoutes, _parseOrThrow, _registerControllerClass, _resolve, _setOpenApiConfig, _settings, methodResolve, openApiGenerator };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tahminator/sapling",
3
- "version": "2.0.3",
3
+ "version": "2.0.5-beta.23c37926",
4
4
  "author": "Tahmid Ahmed",
5
5
  "description": "A library to help you write cleaner Express.js code",
6
6
  "repository": {
@@ -44,12 +44,14 @@
44
44
  "@standard-schema/spec": "^1.1.0",
45
45
  "@types/express": "^5",
46
46
  "@types/supertest": "^7.2.0",
47
+ "@types/swagger-ui-express": "^4.1.8",
47
48
  "@vitest/coverage-istanbul": "^4.1.2",
48
49
  "eslint": "^10.1.0",
49
50
  "eslint-plugin-perfectionist": "^5.7.0",
50
51
  "globals": "^17.4.0",
51
52
  "jiti": "^2.6.1",
52
53
  "jsdom": "^29.0.1",
54
+ "openapi-types": "12.1.3",
53
55
  "prettier": "^3.8.1",
54
56
  "superjson": "^2.2.6",
55
57
  "supertest": "^7.2.2",
@@ -60,6 +62,10 @@
60
62
  "zod": "^4.4.3"
61
63
  },
62
64
  "inlinedDependencies": {
63
- "@standard-schema/spec": "1.1.0"
65
+ "@standard-schema/spec": "1.1.0",
66
+ "openapi-types": "12.1.3"
67
+ },
68
+ "dependencies": {
69
+ "swagger-ui-express": "^5.0.1"
64
70
  }
65
71
  }