@tahminator/sapling 2.0.5-beta.a565b2cc → 2.0.5-beta.c70dc62b
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 +140 -154
- package/dist/index.d.cts +16 -85
- package/dist/index.d.mts +16 -85
- package/dist/index.mjs +135 -152
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -258,6 +258,7 @@ var ParserError = class ParserError extends ResponseStatusError {
|
|
|
258
258
|
case "reqbody": return "request body";
|
|
259
259
|
case "reqparams": return "request params";
|
|
260
260
|
case "reqquery": return "request query";
|
|
261
|
+
case "resbody": return "response body";
|
|
261
262
|
}
|
|
262
263
|
})()}: ${formatted}`;
|
|
263
264
|
}
|
|
@@ -381,134 +382,20 @@ var Sapling = class Sapling {
|
|
|
381
382
|
static setSwaggerPath(path) {
|
|
382
383
|
_settings.doc.swaggerPath = path;
|
|
383
384
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
* const CREATE_BOOK_REQUEST_BODY_SCHEMA = z.object({
|
|
397
|
-
* name: z.string(),
|
|
398
|
-
* description: z.string().optional(),
|
|
399
|
-
* });
|
|
400
|
-
*
|
|
401
|
-
* ⠀@Controller({ prefix: "/api/book" })
|
|
402
|
-
* class BookController {
|
|
403
|
-
* ⠀@RequestBody(CREATE_BOOK_REQUEST_BODY_SCHEMA)
|
|
404
|
-
* ⠀@POST()
|
|
405
|
-
* public createBook(request: e.Request) {
|
|
406
|
-
* const { name, description } = request.body as unknown as z.infer<
|
|
407
|
-
* typeof CREATE_BOOK_REQUEST_BODY_SCHEMA
|
|
408
|
-
* >;
|
|
409
|
-
* }
|
|
410
|
-
* }
|
|
411
|
-
* ```
|
|
412
|
-
*/
|
|
413
|
-
function RequestBody(schema) {
|
|
414
|
-
return (target, propertyKey) => {
|
|
415
|
-
const ctor = target.constructor;
|
|
416
|
-
const fnName = String(propertyKey);
|
|
417
|
-
_setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "body", schema, fnName);
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
/**
|
|
421
|
-
* Apply to a route method to have `request.param` be parsed by `schema`.
|
|
422
|
-
*
|
|
423
|
-
* This annotation will parse `request.param` & then override `request.param`.
|
|
424
|
-
* You can then just simply cast `request.param` for your use
|
|
425
|
-
*
|
|
426
|
-
* @example
|
|
427
|
-
* ```ts
|
|
428
|
-
* const GET_BOOK_REQUEST_PARAM_SCHEMA = z.object({
|
|
429
|
-
* bookId: z.string(),
|
|
430
|
-
* });
|
|
431
|
-
*
|
|
432
|
-
* ⠀@Controller({ prefix: "/api/book" })
|
|
433
|
-
* class BookController {
|
|
434
|
-
* ⠀@RequestParam(GET_BOOK_REQUEST_PARAM_SCHEMA)
|
|
435
|
-
* ⠀@GET("/:bookId")
|
|
436
|
-
* public getBook(request: e.Request) {
|
|
437
|
-
* const { bookId } = request.param as unknown as z.infer<
|
|
438
|
-
* typeof GET_BOOK_REQUEST_PARAM_SCHEMA
|
|
439
|
-
* >;
|
|
440
|
-
* }
|
|
441
|
-
* }
|
|
442
|
-
* ```
|
|
443
|
-
*/
|
|
444
|
-
function RequestParam(schema) {
|
|
445
|
-
return (target, propertyKey) => {
|
|
446
|
-
const ctor = target.constructor;
|
|
447
|
-
const fnName = String(propertyKey);
|
|
448
|
-
_setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "param", schema, fnName);
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Apply to a route method to have `request.query` be parsed by `schema`.
|
|
453
|
-
*
|
|
454
|
-
* This annotation will parse `request.query` & then override `request.query`.
|
|
455
|
-
* You can then just simply cast `request.query` for your use
|
|
456
|
-
*
|
|
457
|
-
* @example
|
|
458
|
-
* ```ts
|
|
459
|
-
* const LIST_BOOKS_REQUEST_QUERY_SCHEMA = z.object({
|
|
460
|
-
* sort: z.enum(["name", "createdAt"]).optional(),
|
|
461
|
-
* q: z.string().optional(),
|
|
462
|
-
* });
|
|
463
|
-
*
|
|
464
|
-
* ⠀@Controller({ prefix: "/api/book" })
|
|
465
|
-
* class BookController {
|
|
466
|
-
* ⠀@RequestQuery(LIST_BOOKS_REQUEST_QUERY_SCHEMA)
|
|
467
|
-
* ⠀@GET()
|
|
468
|
-
* public listBooks(request: e.Request) {
|
|
469
|
-
* const { sort, q } = request.query as unknown as z.infer<
|
|
470
|
-
* typeof LIST_BOOKS_REQUEST_QUERY_SCHEMA
|
|
471
|
-
* >;
|
|
472
|
-
* }
|
|
473
|
-
* }
|
|
474
|
-
* ```
|
|
475
|
-
*/
|
|
476
|
-
function RequestQuery(schema) {
|
|
477
|
-
return (target, propertyKey) => {
|
|
478
|
-
const ctor = target.constructor;
|
|
479
|
-
const fnName = String(propertyKey);
|
|
480
|
-
_setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "query", schema, fnName);
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
function _getOrCreateRequestSchemaDefinition(ctor, fnName) {
|
|
484
|
-
const byFn = (() => {
|
|
485
|
-
const fn = _requestSchemaStore.get(ctor);
|
|
486
|
-
if (fn) return fn;
|
|
487
|
-
const newFn = /* @__PURE__ */ new Map();
|
|
488
|
-
_requestSchemaStore.set(ctor, newFn);
|
|
489
|
-
return newFn;
|
|
490
|
-
})();
|
|
491
|
-
const existing = byFn.get(fnName);
|
|
492
|
-
if (existing) return existing;
|
|
493
|
-
const created = {};
|
|
494
|
-
byFn.set(fnName, created);
|
|
495
|
-
return created;
|
|
496
|
-
}
|
|
497
|
-
function _setOnce(def, key, schema, fnName) {
|
|
498
|
-
if (def[key]) throw new Error(`Duplicate request schema for "${String(key)}" on method "${fnName}"`);
|
|
499
|
-
def[key] = schema;
|
|
500
|
-
}
|
|
501
|
-
function _getRequestSchemas(ctor, fnName) {
|
|
502
|
-
return _requestSchemaStore.get(ctor)?.get(fnName);
|
|
503
|
-
}
|
|
504
|
-
async function _parseOrThrow(schema, input, kind) {
|
|
505
|
-
const result = await schema["~standard"].validate(input);
|
|
506
|
-
if (result.issues) {
|
|
507
|
-
console.debug(`Failed to parse a schema`);
|
|
508
|
-
throw new ParserError(kind, result.issues, schema["~standard"].vendor);
|
|
385
|
+
static chainHandlers(handlers, request, response, next, index = 0) {
|
|
386
|
+
if (index >= handlers.length) {
|
|
387
|
+
next();
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
handlers[index]?.(request, response, (err) => {
|
|
391
|
+
if (err) {
|
|
392
|
+
next(err);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
Sapling.chainHandlers(handlers, request, response, next, index + 1);
|
|
396
|
+
});
|
|
509
397
|
}
|
|
510
|
-
|
|
511
|
-
}
|
|
398
|
+
};
|
|
512
399
|
//#endregion
|
|
513
400
|
//#region src/annotation/route.ts
|
|
514
401
|
const _routeStore = /* @__PURE__ */ new WeakMap();
|
|
@@ -591,6 +478,25 @@ function _getRoutes(ctor) {
|
|
|
591
478
|
return _routeStore.get(ctor) ?? [];
|
|
592
479
|
}
|
|
593
480
|
//#endregion
|
|
481
|
+
//#region src/utils.ts
|
|
482
|
+
function _getOrCreateMap(store, ctor) {
|
|
483
|
+
const existing = store.get(ctor);
|
|
484
|
+
if (existing) return existing;
|
|
485
|
+
const created = /* @__PURE__ */ new Map();
|
|
486
|
+
store.set(ctor, created);
|
|
487
|
+
return created;
|
|
488
|
+
}
|
|
489
|
+
//#endregion
|
|
490
|
+
//#region src/annotation/schema.ts
|
|
491
|
+
const _routeSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
492
|
+
const _controllerSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
493
|
+
function _getRouteSchema(ctor, fnName) {
|
|
494
|
+
return _routeSchemaStore.get(ctor)?.get(fnName);
|
|
495
|
+
}
|
|
496
|
+
function _getControllerSchema(ctor) {
|
|
497
|
+
return _controllerSchemaStore.get(ctor);
|
|
498
|
+
}
|
|
499
|
+
//#endregion
|
|
594
500
|
//#region src/helper/openapi.ts
|
|
595
501
|
var OpenAPIGenerator = class {
|
|
596
502
|
constructor() {
|
|
@@ -612,18 +518,39 @@ var OpenAPIGenerator = class {
|
|
|
612
518
|
generateSpec() {
|
|
613
519
|
const config = this.config;
|
|
614
520
|
const paths = {};
|
|
521
|
+
const tags = [];
|
|
615
522
|
for (const { class: controllerClass, prefix } of this.controllers) {
|
|
616
523
|
const routes = _getRoutes(controllerClass);
|
|
524
|
+
const controllerSchema = _getControllerSchema(controllerClass);
|
|
525
|
+
if (controllerSchema?.title) tags.push({
|
|
526
|
+
name: controllerSchema.title,
|
|
527
|
+
description: controllerSchema.description
|
|
528
|
+
});
|
|
617
529
|
for (const route of routes) {
|
|
618
530
|
if (route.method === "USE") continue;
|
|
619
|
-
const schemas =
|
|
620
|
-
const
|
|
621
|
-
|
|
531
|
+
const schemas = _getValidatorSchema(controllerClass, route.fnName);
|
|
532
|
+
const routeSchema = _getRouteSchema(controllerClass, route.fnName);
|
|
533
|
+
if (route.path instanceof RegExp) throw new Error(`You have a route with a regex path of ${route.path.source}. This is not compatible with OpenAPI.`);
|
|
534
|
+
const openApiPath = prefix + route.path;
|
|
622
535
|
if (!paths[openApiPath]) paths[openApiPath] = {};
|
|
623
|
-
const
|
|
536
|
+
const responses = {};
|
|
537
|
+
if (schemas?.responseBody) responses["200"] = {
|
|
538
|
+
description: "Successful response",
|
|
539
|
+
content: { "application/json": { schema: this.toJsonSchema(schemas.responseBody) } }
|
|
540
|
+
};
|
|
541
|
+
else responses["200"] = { description: "Successful response" };
|
|
542
|
+
if (routeSchema?.responses) for (const resp of routeSchema.responses) responses[String(resp.statusCode)] = {
|
|
543
|
+
description: `Response ${resp.statusCode}`,
|
|
544
|
+
content: { "application/json": { schema: this.toJsonSchema(resp.schema) } }
|
|
545
|
+
};
|
|
546
|
+
const operation = {
|
|
547
|
+
responses,
|
|
548
|
+
description: routeSchema?.description,
|
|
549
|
+
tags: controllerSchema?.title ? [controllerSchema.title] : void 0
|
|
550
|
+
};
|
|
624
551
|
const parameters = [];
|
|
625
|
-
if (schemas?.
|
|
626
|
-
const paramSchema = this.toJsonSchema(schemas.
|
|
552
|
+
if (schemas?.requestParam) {
|
|
553
|
+
const paramSchema = this.toJsonSchema(schemas.requestParam);
|
|
627
554
|
if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties)) parameters.push({
|
|
628
555
|
name,
|
|
629
556
|
in: "path",
|
|
@@ -631,8 +558,8 @@ var OpenAPIGenerator = class {
|
|
|
631
558
|
schema
|
|
632
559
|
});
|
|
633
560
|
}
|
|
634
|
-
if (schemas?.
|
|
635
|
-
const querySchema = this.toJsonSchema(schemas.
|
|
561
|
+
if (schemas?.requestQuery) {
|
|
562
|
+
const querySchema = this.toJsonSchema(schemas.requestQuery);
|
|
636
563
|
if (querySchema.type === "object" && querySchema.properties) for (const [name, schema] of Object.entries(querySchema.properties)) {
|
|
637
564
|
const isRequired = Array.isArray(querySchema.required) && querySchema.required.includes(name);
|
|
638
565
|
parameters.push({
|
|
@@ -644,9 +571,9 @@ var OpenAPIGenerator = class {
|
|
|
644
571
|
}
|
|
645
572
|
}
|
|
646
573
|
if (parameters.length > 0) operation.parameters = parameters;
|
|
647
|
-
if (schemas?.
|
|
574
|
+
if (schemas?.requestBody) operation.requestBody = {
|
|
648
575
|
required: true,
|
|
649
|
-
content: { "application/json": { schema: this.toJsonSchema(schemas.
|
|
576
|
+
content: { "application/json": { schema: this.toJsonSchema(schemas.requestBody) } }
|
|
650
577
|
};
|
|
651
578
|
const method = route.method.toLowerCase();
|
|
652
579
|
paths[openApiPath][method] = operation;
|
|
@@ -659,6 +586,7 @@ var OpenAPIGenerator = class {
|
|
|
659
586
|
version: config.version,
|
|
660
587
|
description: config.description
|
|
661
588
|
},
|
|
589
|
+
tags: tags.length > 0 ? tags : void 0,
|
|
662
590
|
paths
|
|
663
591
|
};
|
|
664
592
|
}
|
|
@@ -675,10 +603,10 @@ const openApiGenerator = new OpenAPIGenerator();
|
|
|
675
603
|
function _registerControllerClass(controllerClass, prefix) {
|
|
676
604
|
openApiGenerator.registerController(controllerClass, prefix);
|
|
677
605
|
}
|
|
678
|
-
function
|
|
606
|
+
function setOpenApiConfig(config) {
|
|
679
607
|
openApiGenerator.setConfig(config);
|
|
680
608
|
}
|
|
681
|
-
function
|
|
609
|
+
function generateOpenApiSpec() {
|
|
682
610
|
return openApiGenerator.generateSpec();
|
|
683
611
|
}
|
|
684
612
|
//#endregion
|
|
@@ -814,6 +742,60 @@ function _resolve(ctor) {
|
|
|
814
742
|
return _InjectableRegistry.get(ctor);
|
|
815
743
|
}
|
|
816
744
|
//#endregion
|
|
745
|
+
//#region src/annotation/validator.ts
|
|
746
|
+
const _validatorSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
747
|
+
function ResponseBody(schema) {
|
|
748
|
+
return (target, propertyKey) => {
|
|
749
|
+
const ctor = target.constructor;
|
|
750
|
+
const fnName = String(propertyKey);
|
|
751
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "responseBody", schema, fnName);
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
function RequestBody(schema) {
|
|
755
|
+
return (target, propertyKey) => {
|
|
756
|
+
const ctor = target.constructor;
|
|
757
|
+
const fnName = String(propertyKey);
|
|
758
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestBody", schema, fnName);
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
function RequestParam(schema) {
|
|
762
|
+
return (target, propertyKey) => {
|
|
763
|
+
const ctor = target.constructor;
|
|
764
|
+
const fnName = String(propertyKey);
|
|
765
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestParam", schema, fnName);
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
function RequestQuery(schema) {
|
|
769
|
+
return (target, propertyKey) => {
|
|
770
|
+
const ctor = target.constructor;
|
|
771
|
+
const fnName = String(propertyKey);
|
|
772
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestQuery", schema, fnName);
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
function _getOrCreateSchemaDefinition(ctor, fnName) {
|
|
776
|
+
const byFn = _getOrCreateMap(_validatorSchemaStore, ctor);
|
|
777
|
+
const existing = byFn.get(fnName);
|
|
778
|
+
if (existing) return existing;
|
|
779
|
+
const created = {};
|
|
780
|
+
byFn.set(fnName, created);
|
|
781
|
+
return created;
|
|
782
|
+
}
|
|
783
|
+
async function _parseOrThrow(schema, input, kind) {
|
|
784
|
+
const result = await schema["~standard"].validate(input);
|
|
785
|
+
if (result.issues) {
|
|
786
|
+
console.debug(`Failed to parse a schema`);
|
|
787
|
+
throw new ParserError(kind, result.issues, schema["~standard"].vendor);
|
|
788
|
+
}
|
|
789
|
+
return result.value;
|
|
790
|
+
}
|
|
791
|
+
function _getValidatorSchema(ctor, fnName) {
|
|
792
|
+
return _validatorSchemaStore.get(ctor)?.get(fnName);
|
|
793
|
+
}
|
|
794
|
+
function _setOnce(def, key, schema, fnName) {
|
|
795
|
+
if (def[key]) throw new Error(`Duplicate schema for "${String(key)}" on method "${fnName}"`);
|
|
796
|
+
def[key] = schema;
|
|
797
|
+
}
|
|
798
|
+
//#endregion
|
|
817
799
|
//#region src/annotation/controller.ts
|
|
818
800
|
const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
|
|
819
801
|
/**
|
|
@@ -868,12 +850,12 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
|
|
|
868
850
|
return;
|
|
869
851
|
}
|
|
870
852
|
router[methodName](fp, async (request, response, next) => {
|
|
871
|
-
const schemas =
|
|
853
|
+
const schemas = _getValidatorSchema(target, fnName);
|
|
872
854
|
if (schemas) {
|
|
873
|
-
if (schemas.
|
|
874
|
-
if (schemas.
|
|
875
|
-
if (schemas.
|
|
876
|
-
const parsedQuery = await _parseOrThrow(schemas.
|
|
855
|
+
if (schemas.requestBody) request.body = await _parseOrThrow(schemas.requestBody, request.body, "reqbody");
|
|
856
|
+
if (schemas.requestParam) request.params = await _parseOrThrow(schemas.requestParam, request.params, "reqparams");
|
|
857
|
+
if (schemas.requestQuery) {
|
|
858
|
+
const parsedQuery = await _parseOrThrow(schemas.requestQuery, request.query, "reqquery");
|
|
877
859
|
Object.defineProperty(request, "query", {
|
|
878
860
|
value: parsedQuery,
|
|
879
861
|
writable: true,
|
|
@@ -883,7 +865,8 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
|
|
|
883
865
|
}
|
|
884
866
|
const result = await fn.bind(controllerInstance)(request, response, next);
|
|
885
867
|
if (result instanceof ResponseEntity) {
|
|
886
|
-
|
|
868
|
+
const body = schemas && schemas.responseBody ? await _parseOrThrow(schemas.responseBody, result.getBody(), "resbody") : result.getBody();
|
|
869
|
+
response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(body));
|
|
887
870
|
return;
|
|
888
871
|
}
|
|
889
872
|
if (result instanceof RedirectView) {
|
|
@@ -950,7 +933,7 @@ DefaultResponseStatusErrorMiddleware = __decorate([MiddlewareClass()], DefaultRe
|
|
|
950
933
|
//#region src/middleware/default/openapi/index.ts
|
|
951
934
|
let DefaultOpenApiMiddleware = class DefaultOpenApiMiddleware {
|
|
952
935
|
handle(_request, _response, _next) {
|
|
953
|
-
return ResponseEntity.ok().body(
|
|
936
|
+
return ResponseEntity.ok().body(generateOpenApiSpec());
|
|
954
937
|
}
|
|
955
938
|
};
|
|
956
939
|
__decorate([GET(_settings.doc.openApiPath)], DefaultOpenApiMiddleware.prototype, "handle", null);
|
|
@@ -961,21 +944,21 @@ let Serve = class Serve {
|
|
|
961
944
|
constructor() {
|
|
962
945
|
this.handlers = swagger_ui_express.default.serve;
|
|
963
946
|
}
|
|
964
|
-
handle(
|
|
965
|
-
return this.handlers;
|
|
947
|
+
handle(request, response, next) {
|
|
948
|
+
return Sapling.chainHandlers(this.handlers, request, response, next);
|
|
966
949
|
}
|
|
967
950
|
};
|
|
968
|
-
__decorate([Middleware()], Serve.prototype, "handle", null);
|
|
951
|
+
__decorate([Middleware(_settings.doc.swaggerPath)], Serve.prototype, "handle", null);
|
|
969
952
|
Serve = __decorate([MiddlewareClass()], Serve);
|
|
970
953
|
let Setup = class Setup {
|
|
971
954
|
constructor() {
|
|
972
|
-
this.handler = swagger_ui_express.default.setup(
|
|
955
|
+
this.handler = swagger_ui_express.default.setup(null, { swaggerOptions: { url: _settings.doc.openApiPath } });
|
|
973
956
|
}
|
|
974
957
|
handle(request, response, next) {
|
|
975
958
|
return this.handler(request, response, next);
|
|
976
959
|
}
|
|
977
960
|
};
|
|
978
|
-
__decorate([Middleware()], Setup.prototype, "handle", null);
|
|
961
|
+
__decorate([Middleware(_settings.doc.swaggerPath)], Setup.prototype, "handle", null);
|
|
979
962
|
Setup = __decorate([MiddlewareClass()], Setup);
|
|
980
963
|
const DefaultSwaggerMiddleware = {
|
|
981
964
|
Serve,
|
|
@@ -1025,6 +1008,7 @@ exports.RedirectView = RedirectView;
|
|
|
1025
1008
|
exports.RequestBody = RequestBody;
|
|
1026
1009
|
exports.RequestParam = RequestParam;
|
|
1027
1010
|
exports.RequestQuery = RequestQuery;
|
|
1011
|
+
exports.ResponseBody = ResponseBody;
|
|
1028
1012
|
exports.ResponseEntity = ResponseEntity;
|
|
1029
1013
|
exports.ResponseEntityBuilder = ResponseEntityBuilder;
|
|
1030
1014
|
exports.ResponseStatusError = ResponseStatusError;
|
|
@@ -1033,13 +1017,15 @@ exports._ControllerRegistry = _ControllerRegistry;
|
|
|
1033
1017
|
exports._InjectableDeps = _InjectableDeps;
|
|
1034
1018
|
exports._InjectableRegistry = _InjectableRegistry;
|
|
1035
1019
|
exports._Route = _Route;
|
|
1036
|
-
exports.
|
|
1037
|
-
exports._getRequestSchemas = _getRequestSchemas;
|
|
1020
|
+
exports._getOrCreateSchemaDefinition = _getOrCreateSchemaDefinition;
|
|
1038
1021
|
exports._getRoutes = _getRoutes;
|
|
1022
|
+
exports._getValidatorSchema = _getValidatorSchema;
|
|
1039
1023
|
exports._parseOrThrow = _parseOrThrow;
|
|
1040
1024
|
exports._registerControllerClass = _registerControllerClass;
|
|
1041
1025
|
exports._resolve = _resolve;
|
|
1042
|
-
exports.
|
|
1026
|
+
exports._setOnce = _setOnce;
|
|
1043
1027
|
exports._settings = _settings;
|
|
1028
|
+
exports.generateOpenApiSpec = generateOpenApiSpec;
|
|
1044
1029
|
exports.methodResolve = methodResolve;
|
|
1045
1030
|
exports.openApiGenerator = openApiGenerator;
|
|
1031
|
+
exports.setOpenApiConfig = setOpenApiConfig;
|
package/dist/index.d.cts
CHANGED
|
@@ -437,7 +437,7 @@ declare class ResponseStatusError extends Error {
|
|
|
437
437
|
}
|
|
438
438
|
//#endregion
|
|
439
439
|
//#region src/helper/error/parse.d.ts
|
|
440
|
-
type ParserErrorLocation = "reqbody" | "reqparams" | "reqquery";
|
|
440
|
+
type ParserErrorLocation = "reqbody" | "reqparams" | "reqquery" | "resbody";
|
|
441
441
|
/**
|
|
442
442
|
* This error should be thrown when some data cannot be parsed by a given schema.
|
|
443
443
|
*/
|
|
@@ -531,6 +531,7 @@ declare class Sapling {
|
|
|
531
531
|
static setDeserializeFn(this: void, fn: (value: string) => any): void;
|
|
532
532
|
static setOpenApiPath(this: void, path: string): void;
|
|
533
533
|
static setSwaggerPath(this: void, path: string): void;
|
|
534
|
+
static chainHandlers(this: void, handlers: RequestHandler[], request: Request, response: Response$1, next: NextFunction, index?: number): void;
|
|
534
535
|
}
|
|
535
536
|
//#endregion
|
|
536
537
|
//#region node_modules/.pnpm/openapi-types@12.1.3/node_modules/openapi-types/dist/index.d.ts
|
|
@@ -869,94 +870,24 @@ declare class OpenAPIGenerator {
|
|
|
869
870
|
}
|
|
870
871
|
declare const openApiGenerator: OpenAPIGenerator;
|
|
871
872
|
declare function _registerControllerClass(controllerClass: Function, prefix: string): void;
|
|
872
|
-
declare function
|
|
873
|
-
declare function
|
|
873
|
+
declare function setOpenApiConfig(config: OpenAPIConfig): void;
|
|
874
|
+
declare function generateOpenApiSpec(): OpenAPIV3.Document;
|
|
874
875
|
//#endregion
|
|
875
|
-
//#region src/annotation/
|
|
876
|
-
type
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
876
|
+
//#region src/annotation/validator.d.ts
|
|
877
|
+
type ValidatorSchema = {
|
|
878
|
+
requestBody?: StandardSchemaV1 & StandardJSONSchemaV1;
|
|
879
|
+
requestParam?: StandardSchemaV1 & StandardJSONSchemaV1;
|
|
880
|
+
requestQuery?: StandardSchemaV1 & StandardJSONSchemaV1;
|
|
881
|
+
responseBody?: StandardSchemaV1 & StandardJSONSchemaV1;
|
|
880
882
|
};
|
|
881
|
-
|
|
882
|
-
* Apply to a route method to have `request.body` be parsed by `schema`.
|
|
883
|
-
*
|
|
884
|
-
* This annotation will parse `request.body` & then override `request.body`.
|
|
885
|
-
* You can then just simply cast `request.body` for your use
|
|
886
|
-
*
|
|
887
|
-
* @example
|
|
888
|
-
* ```ts
|
|
889
|
-
* const CREATE_BOOK_REQUEST_BODY_SCHEMA = z.object({
|
|
890
|
-
* name: z.string(),
|
|
891
|
-
* description: z.string().optional(),
|
|
892
|
-
* });
|
|
893
|
-
*
|
|
894
|
-
* ⠀@Controller({ prefix: "/api/book" })
|
|
895
|
-
* class BookController {
|
|
896
|
-
* ⠀@RequestBody(CREATE_BOOK_REQUEST_BODY_SCHEMA)
|
|
897
|
-
* ⠀@POST()
|
|
898
|
-
* public createBook(request: e.Request) {
|
|
899
|
-
* const { name, description } = request.body as unknown as z.infer<
|
|
900
|
-
* typeof CREATE_BOOK_REQUEST_BODY_SCHEMA
|
|
901
|
-
* >;
|
|
902
|
-
* }
|
|
903
|
-
* }
|
|
904
|
-
* ```
|
|
905
|
-
*/
|
|
883
|
+
declare function ResponseBody(schema: StandardSchemaV1 & StandardJSONSchemaV1): MethodDecorator;
|
|
906
884
|
declare function RequestBody(schema: StandardSchemaV1 & StandardJSONSchemaV1): MethodDecorator;
|
|
907
|
-
/**
|
|
908
|
-
* Apply to a route method to have `request.param` be parsed by `schema`.
|
|
909
|
-
*
|
|
910
|
-
* This annotation will parse `request.param` & then override `request.param`.
|
|
911
|
-
* You can then just simply cast `request.param` for your use
|
|
912
|
-
*
|
|
913
|
-
* @example
|
|
914
|
-
* ```ts
|
|
915
|
-
* const GET_BOOK_REQUEST_PARAM_SCHEMA = z.object({
|
|
916
|
-
* bookId: z.string(),
|
|
917
|
-
* });
|
|
918
|
-
*
|
|
919
|
-
* ⠀@Controller({ prefix: "/api/book" })
|
|
920
|
-
* class BookController {
|
|
921
|
-
* ⠀@RequestParam(GET_BOOK_REQUEST_PARAM_SCHEMA)
|
|
922
|
-
* ⠀@GET("/:bookId")
|
|
923
|
-
* public getBook(request: e.Request) {
|
|
924
|
-
* const { bookId } = request.param as unknown as z.infer<
|
|
925
|
-
* typeof GET_BOOK_REQUEST_PARAM_SCHEMA
|
|
926
|
-
* >;
|
|
927
|
-
* }
|
|
928
|
-
* }
|
|
929
|
-
* ```
|
|
930
|
-
*/
|
|
931
885
|
declare function RequestParam(schema: StandardSchemaV1 & StandardJSONSchemaV1): MethodDecorator;
|
|
932
|
-
/**
|
|
933
|
-
* Apply to a route method to have `request.query` be parsed by `schema`.
|
|
934
|
-
*
|
|
935
|
-
* This annotation will parse `request.query` & then override `request.query`.
|
|
936
|
-
* You can then just simply cast `request.query` for your use
|
|
937
|
-
*
|
|
938
|
-
* @example
|
|
939
|
-
* ```ts
|
|
940
|
-
* const LIST_BOOKS_REQUEST_QUERY_SCHEMA = z.object({
|
|
941
|
-
* sort: z.enum(["name", "createdAt"]).optional(),
|
|
942
|
-
* q: z.string().optional(),
|
|
943
|
-
* });
|
|
944
|
-
*
|
|
945
|
-
* ⠀@Controller({ prefix: "/api/book" })
|
|
946
|
-
* class BookController {
|
|
947
|
-
* ⠀@RequestQuery(LIST_BOOKS_REQUEST_QUERY_SCHEMA)
|
|
948
|
-
* ⠀@GET()
|
|
949
|
-
* public listBooks(request: e.Request) {
|
|
950
|
-
* const { sort, q } = request.query as unknown as z.infer<
|
|
951
|
-
* typeof LIST_BOOKS_REQUEST_QUERY_SCHEMA
|
|
952
|
-
* >;
|
|
953
|
-
* }
|
|
954
|
-
* }
|
|
955
|
-
* ```
|
|
956
|
-
*/
|
|
957
886
|
declare function RequestQuery(schema: StandardSchemaV1 & StandardJSONSchemaV1): MethodDecorator;
|
|
958
|
-
declare function
|
|
887
|
+
declare function _getOrCreateSchemaDefinition(ctor: Function, fnName: string): ValidatorSchema;
|
|
959
888
|
declare function _parseOrThrow<TSchema extends StandardSchemaV1>(schema: TSchema, input: unknown, kind: ParserErrorLocation): Promise<StandardSchemaV1.InferOutput<TSchema>>;
|
|
889
|
+
declare function _getValidatorSchema(ctor: Function, fnName: string): ValidatorSchema | undefined;
|
|
890
|
+
declare function _setOnce(def: ValidatorSchema, key: keyof ValidatorSchema, schema: StandardSchemaV1 & StandardJSONSchemaV1, fnName: string): void;
|
|
960
891
|
//#endregion
|
|
961
892
|
//#region src/middleware/default/error/base.d.ts
|
|
962
893
|
/**
|
|
@@ -992,7 +923,7 @@ declare class DefaultOpenApiMiddleware {
|
|
|
992
923
|
//#region src/middleware/default/swagger/index.d.ts
|
|
993
924
|
declare class Serve {
|
|
994
925
|
private readonly handlers;
|
|
995
|
-
handle(
|
|
926
|
+
handle(request: Request, response: Response$1, next: NextFunction): void;
|
|
996
927
|
}
|
|
997
928
|
declare class Setup {
|
|
998
929
|
private readonly handler;
|
|
@@ -1004,4 +935,4 @@ declare const DefaultSwaggerMiddleware: {
|
|
|
1004
935
|
Setup: typeof Setup;
|
|
1005
936
|
};
|
|
1006
937
|
//#endregion
|
|
1007
|
-
export { Class, Controller, DELETE, DefaultBaseErrorMiddleware, DefaultOpenApiMiddleware, DefaultParserErrorMiddleware, DefaultResponseStatusErrorMiddleware, DefaultSwaggerMiddleware, ExpressMiddlewareFn, ExpressRouterMethodKey, ExpressRouterMethods, GET, HEAD, Html404ErrorPage, HttpHeaders, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, ParserError, ParserErrorLocation, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, RouteDefinition, Sapling, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route,
|
|
938
|
+
export { Class, Controller, DELETE, DefaultBaseErrorMiddleware, DefaultOpenApiMiddleware, DefaultParserErrorMiddleware, DefaultResponseStatusErrorMiddleware, DefaultSwaggerMiddleware, ExpressMiddlewareFn, ExpressRouterMethodKey, ExpressRouterMethods, GET, HEAD, Html404ErrorPage, HttpHeaders, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, ParserError, ParserErrorLocation, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseBody, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, RouteDefinition, Sapling, ValidatorSchema, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _getOrCreateSchemaDefinition, _getRoutes, _getValidatorSchema, _parseOrThrow, _registerControllerClass, _resolve, _setOnce, _settings, generateOpenApiSpec, methodResolve, openApiGenerator, setOpenApiConfig };
|
package/dist/index.d.mts
CHANGED
|
@@ -437,7 +437,7 @@ declare class ResponseStatusError extends Error {
|
|
|
437
437
|
}
|
|
438
438
|
//#endregion
|
|
439
439
|
//#region src/helper/error/parse.d.ts
|
|
440
|
-
type ParserErrorLocation = "reqbody" | "reqparams" | "reqquery";
|
|
440
|
+
type ParserErrorLocation = "reqbody" | "reqparams" | "reqquery" | "resbody";
|
|
441
441
|
/**
|
|
442
442
|
* This error should be thrown when some data cannot be parsed by a given schema.
|
|
443
443
|
*/
|
|
@@ -531,6 +531,7 @@ declare class Sapling {
|
|
|
531
531
|
static setDeserializeFn(this: void, fn: (value: string) => any): void;
|
|
532
532
|
static setOpenApiPath(this: void, path: string): void;
|
|
533
533
|
static setSwaggerPath(this: void, path: string): void;
|
|
534
|
+
static chainHandlers(this: void, handlers: RequestHandler[], request: Request, response: Response$1, next: NextFunction, index?: number): void;
|
|
534
535
|
}
|
|
535
536
|
//#endregion
|
|
536
537
|
//#region node_modules/.pnpm/openapi-types@12.1.3/node_modules/openapi-types/dist/index.d.ts
|
|
@@ -869,94 +870,24 @@ declare class OpenAPIGenerator {
|
|
|
869
870
|
}
|
|
870
871
|
declare const openApiGenerator: OpenAPIGenerator;
|
|
871
872
|
declare function _registerControllerClass(controllerClass: Function, prefix: string): void;
|
|
872
|
-
declare function
|
|
873
|
-
declare function
|
|
873
|
+
declare function setOpenApiConfig(config: OpenAPIConfig): void;
|
|
874
|
+
declare function generateOpenApiSpec(): OpenAPIV3.Document;
|
|
874
875
|
//#endregion
|
|
875
|
-
//#region src/annotation/
|
|
876
|
-
type
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
876
|
+
//#region src/annotation/validator.d.ts
|
|
877
|
+
type ValidatorSchema = {
|
|
878
|
+
requestBody?: StandardSchemaV1 & StandardJSONSchemaV1;
|
|
879
|
+
requestParam?: StandardSchemaV1 & StandardJSONSchemaV1;
|
|
880
|
+
requestQuery?: StandardSchemaV1 & StandardJSONSchemaV1;
|
|
881
|
+
responseBody?: StandardSchemaV1 & StandardJSONSchemaV1;
|
|
880
882
|
};
|
|
881
|
-
|
|
882
|
-
* Apply to a route method to have `request.body` be parsed by `schema`.
|
|
883
|
-
*
|
|
884
|
-
* This annotation will parse `request.body` & then override `request.body`.
|
|
885
|
-
* You can then just simply cast `request.body` for your use
|
|
886
|
-
*
|
|
887
|
-
* @example
|
|
888
|
-
* ```ts
|
|
889
|
-
* const CREATE_BOOK_REQUEST_BODY_SCHEMA = z.object({
|
|
890
|
-
* name: z.string(),
|
|
891
|
-
* description: z.string().optional(),
|
|
892
|
-
* });
|
|
893
|
-
*
|
|
894
|
-
* ⠀@Controller({ prefix: "/api/book" })
|
|
895
|
-
* class BookController {
|
|
896
|
-
* ⠀@RequestBody(CREATE_BOOK_REQUEST_BODY_SCHEMA)
|
|
897
|
-
* ⠀@POST()
|
|
898
|
-
* public createBook(request: e.Request) {
|
|
899
|
-
* const { name, description } = request.body as unknown as z.infer<
|
|
900
|
-
* typeof CREATE_BOOK_REQUEST_BODY_SCHEMA
|
|
901
|
-
* >;
|
|
902
|
-
* }
|
|
903
|
-
* }
|
|
904
|
-
* ```
|
|
905
|
-
*/
|
|
883
|
+
declare function ResponseBody(schema: StandardSchemaV1 & StandardJSONSchemaV1): MethodDecorator;
|
|
906
884
|
declare function RequestBody(schema: StandardSchemaV1 & StandardJSONSchemaV1): MethodDecorator;
|
|
907
|
-
/**
|
|
908
|
-
* Apply to a route method to have `request.param` be parsed by `schema`.
|
|
909
|
-
*
|
|
910
|
-
* This annotation will parse `request.param` & then override `request.param`.
|
|
911
|
-
* You can then just simply cast `request.param` for your use
|
|
912
|
-
*
|
|
913
|
-
* @example
|
|
914
|
-
* ```ts
|
|
915
|
-
* const GET_BOOK_REQUEST_PARAM_SCHEMA = z.object({
|
|
916
|
-
* bookId: z.string(),
|
|
917
|
-
* });
|
|
918
|
-
*
|
|
919
|
-
* ⠀@Controller({ prefix: "/api/book" })
|
|
920
|
-
* class BookController {
|
|
921
|
-
* ⠀@RequestParam(GET_BOOK_REQUEST_PARAM_SCHEMA)
|
|
922
|
-
* ⠀@GET("/:bookId")
|
|
923
|
-
* public getBook(request: e.Request) {
|
|
924
|
-
* const { bookId } = request.param as unknown as z.infer<
|
|
925
|
-
* typeof GET_BOOK_REQUEST_PARAM_SCHEMA
|
|
926
|
-
* >;
|
|
927
|
-
* }
|
|
928
|
-
* }
|
|
929
|
-
* ```
|
|
930
|
-
*/
|
|
931
885
|
declare function RequestParam(schema: StandardSchemaV1 & StandardJSONSchemaV1): MethodDecorator;
|
|
932
|
-
/**
|
|
933
|
-
* Apply to a route method to have `request.query` be parsed by `schema`.
|
|
934
|
-
*
|
|
935
|
-
* This annotation will parse `request.query` & then override `request.query`.
|
|
936
|
-
* You can then just simply cast `request.query` for your use
|
|
937
|
-
*
|
|
938
|
-
* @example
|
|
939
|
-
* ```ts
|
|
940
|
-
* const LIST_BOOKS_REQUEST_QUERY_SCHEMA = z.object({
|
|
941
|
-
* sort: z.enum(["name", "createdAt"]).optional(),
|
|
942
|
-
* q: z.string().optional(),
|
|
943
|
-
* });
|
|
944
|
-
*
|
|
945
|
-
* ⠀@Controller({ prefix: "/api/book" })
|
|
946
|
-
* class BookController {
|
|
947
|
-
* ⠀@RequestQuery(LIST_BOOKS_REQUEST_QUERY_SCHEMA)
|
|
948
|
-
* ⠀@GET()
|
|
949
|
-
* public listBooks(request: e.Request) {
|
|
950
|
-
* const { sort, q } = request.query as unknown as z.infer<
|
|
951
|
-
* typeof LIST_BOOKS_REQUEST_QUERY_SCHEMA
|
|
952
|
-
* >;
|
|
953
|
-
* }
|
|
954
|
-
* }
|
|
955
|
-
* ```
|
|
956
|
-
*/
|
|
957
886
|
declare function RequestQuery(schema: StandardSchemaV1 & StandardJSONSchemaV1): MethodDecorator;
|
|
958
|
-
declare function
|
|
887
|
+
declare function _getOrCreateSchemaDefinition(ctor: Function, fnName: string): ValidatorSchema;
|
|
959
888
|
declare function _parseOrThrow<TSchema extends StandardSchemaV1>(schema: TSchema, input: unknown, kind: ParserErrorLocation): Promise<StandardSchemaV1.InferOutput<TSchema>>;
|
|
889
|
+
declare function _getValidatorSchema(ctor: Function, fnName: string): ValidatorSchema | undefined;
|
|
890
|
+
declare function _setOnce(def: ValidatorSchema, key: keyof ValidatorSchema, schema: StandardSchemaV1 & StandardJSONSchemaV1, fnName: string): void;
|
|
960
891
|
//#endregion
|
|
961
892
|
//#region src/middleware/default/error/base.d.ts
|
|
962
893
|
/**
|
|
@@ -992,7 +923,7 @@ declare class DefaultOpenApiMiddleware {
|
|
|
992
923
|
//#region src/middleware/default/swagger/index.d.ts
|
|
993
924
|
declare class Serve {
|
|
994
925
|
private readonly handlers;
|
|
995
|
-
handle(
|
|
926
|
+
handle(request: Request, response: Response$1, next: NextFunction): void;
|
|
996
927
|
}
|
|
997
928
|
declare class Setup {
|
|
998
929
|
private readonly handler;
|
|
@@ -1004,4 +935,4 @@ declare const DefaultSwaggerMiddleware: {
|
|
|
1004
935
|
Setup: typeof Setup;
|
|
1005
936
|
};
|
|
1006
937
|
//#endregion
|
|
1007
|
-
export { Class, Controller, DELETE, DefaultBaseErrorMiddleware, DefaultOpenApiMiddleware, DefaultParserErrorMiddleware, DefaultResponseStatusErrorMiddleware, DefaultSwaggerMiddleware, ExpressMiddlewareFn, ExpressRouterMethodKey, ExpressRouterMethods, GET, HEAD, Html404ErrorPage, HttpHeaders, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, ParserError, ParserErrorLocation, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, RouteDefinition, Sapling, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route,
|
|
938
|
+
export { Class, Controller, DELETE, DefaultBaseErrorMiddleware, DefaultOpenApiMiddleware, DefaultParserErrorMiddleware, DefaultResponseStatusErrorMiddleware, DefaultSwaggerMiddleware, ExpressMiddlewareFn, ExpressRouterMethodKey, ExpressRouterMethods, GET, HEAD, Html404ErrorPage, HttpHeaders, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, ParserError, ParserErrorLocation, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseBody, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, RouteDefinition, Sapling, ValidatorSchema, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _getOrCreateSchemaDefinition, _getRoutes, _getValidatorSchema, _parseOrThrow, _registerControllerClass, _resolve, _setOnce, _settings, generateOpenApiSpec, methodResolve, openApiGenerator, setOpenApiConfig };
|
package/dist/index.mjs
CHANGED
|
@@ -233,6 +233,7 @@ var ParserError = class ParserError extends ResponseStatusError {
|
|
|
233
233
|
case "reqbody": return "request body";
|
|
234
234
|
case "reqparams": return "request params";
|
|
235
235
|
case "reqquery": return "request query";
|
|
236
|
+
case "resbody": return "response body";
|
|
236
237
|
}
|
|
237
238
|
})()}: ${formatted}`;
|
|
238
239
|
}
|
|
@@ -356,134 +357,20 @@ var Sapling = class Sapling {
|
|
|
356
357
|
static setSwaggerPath(path) {
|
|
357
358
|
_settings.doc.swaggerPath = path;
|
|
358
359
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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);
|
|
360
|
+
static chainHandlers(handlers, request, response, next, index = 0) {
|
|
361
|
+
if (index >= handlers.length) {
|
|
362
|
+
next();
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
handlers[index]?.(request, response, (err) => {
|
|
366
|
+
if (err) {
|
|
367
|
+
next(err);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
Sapling.chainHandlers(handlers, request, response, next, index + 1);
|
|
371
|
+
});
|
|
484
372
|
}
|
|
485
|
-
|
|
486
|
-
}
|
|
373
|
+
};
|
|
487
374
|
//#endregion
|
|
488
375
|
//#region src/annotation/route.ts
|
|
489
376
|
const _routeStore = /* @__PURE__ */ new WeakMap();
|
|
@@ -566,6 +453,25 @@ function _getRoutes(ctor) {
|
|
|
566
453
|
return _routeStore.get(ctor) ?? [];
|
|
567
454
|
}
|
|
568
455
|
//#endregion
|
|
456
|
+
//#region src/utils.ts
|
|
457
|
+
function _getOrCreateMap(store, ctor) {
|
|
458
|
+
const existing = store.get(ctor);
|
|
459
|
+
if (existing) return existing;
|
|
460
|
+
const created = /* @__PURE__ */ new Map();
|
|
461
|
+
store.set(ctor, created);
|
|
462
|
+
return created;
|
|
463
|
+
}
|
|
464
|
+
//#endregion
|
|
465
|
+
//#region src/annotation/schema.ts
|
|
466
|
+
const _routeSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
467
|
+
const _controllerSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
468
|
+
function _getRouteSchema(ctor, fnName) {
|
|
469
|
+
return _routeSchemaStore.get(ctor)?.get(fnName);
|
|
470
|
+
}
|
|
471
|
+
function _getControllerSchema(ctor) {
|
|
472
|
+
return _controllerSchemaStore.get(ctor);
|
|
473
|
+
}
|
|
474
|
+
//#endregion
|
|
569
475
|
//#region src/helper/openapi.ts
|
|
570
476
|
var OpenAPIGenerator = class {
|
|
571
477
|
constructor() {
|
|
@@ -587,18 +493,39 @@ var OpenAPIGenerator = class {
|
|
|
587
493
|
generateSpec() {
|
|
588
494
|
const config = this.config;
|
|
589
495
|
const paths = {};
|
|
496
|
+
const tags = [];
|
|
590
497
|
for (const { class: controllerClass, prefix } of this.controllers) {
|
|
591
498
|
const routes = _getRoutes(controllerClass);
|
|
499
|
+
const controllerSchema = _getControllerSchema(controllerClass);
|
|
500
|
+
if (controllerSchema?.title) tags.push({
|
|
501
|
+
name: controllerSchema.title,
|
|
502
|
+
description: controllerSchema.description
|
|
503
|
+
});
|
|
592
504
|
for (const route of routes) {
|
|
593
505
|
if (route.method === "USE") continue;
|
|
594
|
-
const schemas =
|
|
595
|
-
const
|
|
596
|
-
|
|
506
|
+
const schemas = _getValidatorSchema(controllerClass, route.fnName);
|
|
507
|
+
const routeSchema = _getRouteSchema(controllerClass, route.fnName);
|
|
508
|
+
if (route.path instanceof RegExp) throw new Error(`You have a route with a regex path of ${route.path.source}. This is not compatible with OpenAPI.`);
|
|
509
|
+
const openApiPath = prefix + route.path;
|
|
597
510
|
if (!paths[openApiPath]) paths[openApiPath] = {};
|
|
598
|
-
const
|
|
511
|
+
const responses = {};
|
|
512
|
+
if (schemas?.responseBody) responses["200"] = {
|
|
513
|
+
description: "Successful response",
|
|
514
|
+
content: { "application/json": { schema: this.toJsonSchema(schemas.responseBody) } }
|
|
515
|
+
};
|
|
516
|
+
else responses["200"] = { description: "Successful response" };
|
|
517
|
+
if (routeSchema?.responses) for (const resp of routeSchema.responses) responses[String(resp.statusCode)] = {
|
|
518
|
+
description: `Response ${resp.statusCode}`,
|
|
519
|
+
content: { "application/json": { schema: this.toJsonSchema(resp.schema) } }
|
|
520
|
+
};
|
|
521
|
+
const operation = {
|
|
522
|
+
responses,
|
|
523
|
+
description: routeSchema?.description,
|
|
524
|
+
tags: controllerSchema?.title ? [controllerSchema.title] : void 0
|
|
525
|
+
};
|
|
599
526
|
const parameters = [];
|
|
600
|
-
if (schemas?.
|
|
601
|
-
const paramSchema = this.toJsonSchema(schemas.
|
|
527
|
+
if (schemas?.requestParam) {
|
|
528
|
+
const paramSchema = this.toJsonSchema(schemas.requestParam);
|
|
602
529
|
if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties)) parameters.push({
|
|
603
530
|
name,
|
|
604
531
|
in: "path",
|
|
@@ -606,8 +533,8 @@ var OpenAPIGenerator = class {
|
|
|
606
533
|
schema
|
|
607
534
|
});
|
|
608
535
|
}
|
|
609
|
-
if (schemas?.
|
|
610
|
-
const querySchema = this.toJsonSchema(schemas.
|
|
536
|
+
if (schemas?.requestQuery) {
|
|
537
|
+
const querySchema = this.toJsonSchema(schemas.requestQuery);
|
|
611
538
|
if (querySchema.type === "object" && querySchema.properties) for (const [name, schema] of Object.entries(querySchema.properties)) {
|
|
612
539
|
const isRequired = Array.isArray(querySchema.required) && querySchema.required.includes(name);
|
|
613
540
|
parameters.push({
|
|
@@ -619,9 +546,9 @@ var OpenAPIGenerator = class {
|
|
|
619
546
|
}
|
|
620
547
|
}
|
|
621
548
|
if (parameters.length > 0) operation.parameters = parameters;
|
|
622
|
-
if (schemas?.
|
|
549
|
+
if (schemas?.requestBody) operation.requestBody = {
|
|
623
550
|
required: true,
|
|
624
|
-
content: { "application/json": { schema: this.toJsonSchema(schemas.
|
|
551
|
+
content: { "application/json": { schema: this.toJsonSchema(schemas.requestBody) } }
|
|
625
552
|
};
|
|
626
553
|
const method = route.method.toLowerCase();
|
|
627
554
|
paths[openApiPath][method] = operation;
|
|
@@ -634,6 +561,7 @@ var OpenAPIGenerator = class {
|
|
|
634
561
|
version: config.version,
|
|
635
562
|
description: config.description
|
|
636
563
|
},
|
|
564
|
+
tags: tags.length > 0 ? tags : void 0,
|
|
637
565
|
paths
|
|
638
566
|
};
|
|
639
567
|
}
|
|
@@ -650,10 +578,10 @@ const openApiGenerator = new OpenAPIGenerator();
|
|
|
650
578
|
function _registerControllerClass(controllerClass, prefix) {
|
|
651
579
|
openApiGenerator.registerController(controllerClass, prefix);
|
|
652
580
|
}
|
|
653
|
-
function
|
|
581
|
+
function setOpenApiConfig(config) {
|
|
654
582
|
openApiGenerator.setConfig(config);
|
|
655
583
|
}
|
|
656
|
-
function
|
|
584
|
+
function generateOpenApiSpec() {
|
|
657
585
|
return openApiGenerator.generateSpec();
|
|
658
586
|
}
|
|
659
587
|
//#endregion
|
|
@@ -789,6 +717,60 @@ function _resolve(ctor) {
|
|
|
789
717
|
return _InjectableRegistry.get(ctor);
|
|
790
718
|
}
|
|
791
719
|
//#endregion
|
|
720
|
+
//#region src/annotation/validator.ts
|
|
721
|
+
const _validatorSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
722
|
+
function ResponseBody(schema) {
|
|
723
|
+
return (target, propertyKey) => {
|
|
724
|
+
const ctor = target.constructor;
|
|
725
|
+
const fnName = String(propertyKey);
|
|
726
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "responseBody", schema, fnName);
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
function RequestBody(schema) {
|
|
730
|
+
return (target, propertyKey) => {
|
|
731
|
+
const ctor = target.constructor;
|
|
732
|
+
const fnName = String(propertyKey);
|
|
733
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestBody", schema, fnName);
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
function RequestParam(schema) {
|
|
737
|
+
return (target, propertyKey) => {
|
|
738
|
+
const ctor = target.constructor;
|
|
739
|
+
const fnName = String(propertyKey);
|
|
740
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestParam", schema, fnName);
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
function RequestQuery(schema) {
|
|
744
|
+
return (target, propertyKey) => {
|
|
745
|
+
const ctor = target.constructor;
|
|
746
|
+
const fnName = String(propertyKey);
|
|
747
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestQuery", schema, fnName);
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
function _getOrCreateSchemaDefinition(ctor, fnName) {
|
|
751
|
+
const byFn = _getOrCreateMap(_validatorSchemaStore, ctor);
|
|
752
|
+
const existing = byFn.get(fnName);
|
|
753
|
+
if (existing) return existing;
|
|
754
|
+
const created = {};
|
|
755
|
+
byFn.set(fnName, created);
|
|
756
|
+
return created;
|
|
757
|
+
}
|
|
758
|
+
async function _parseOrThrow(schema, input, kind) {
|
|
759
|
+
const result = await schema["~standard"].validate(input);
|
|
760
|
+
if (result.issues) {
|
|
761
|
+
console.debug(`Failed to parse a schema`);
|
|
762
|
+
throw new ParserError(kind, result.issues, schema["~standard"].vendor);
|
|
763
|
+
}
|
|
764
|
+
return result.value;
|
|
765
|
+
}
|
|
766
|
+
function _getValidatorSchema(ctor, fnName) {
|
|
767
|
+
return _validatorSchemaStore.get(ctor)?.get(fnName);
|
|
768
|
+
}
|
|
769
|
+
function _setOnce(def, key, schema, fnName) {
|
|
770
|
+
if (def[key]) throw new Error(`Duplicate schema for "${String(key)}" on method "${fnName}"`);
|
|
771
|
+
def[key] = schema;
|
|
772
|
+
}
|
|
773
|
+
//#endregion
|
|
792
774
|
//#region src/annotation/controller.ts
|
|
793
775
|
const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
|
|
794
776
|
/**
|
|
@@ -843,12 +825,12 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
|
|
|
843
825
|
return;
|
|
844
826
|
}
|
|
845
827
|
router[methodName](fp, async (request, response, next) => {
|
|
846
|
-
const schemas =
|
|
828
|
+
const schemas = _getValidatorSchema(target, fnName);
|
|
847
829
|
if (schemas) {
|
|
848
|
-
if (schemas.
|
|
849
|
-
if (schemas.
|
|
850
|
-
if (schemas.
|
|
851
|
-
const parsedQuery = await _parseOrThrow(schemas.
|
|
830
|
+
if (schemas.requestBody) request.body = await _parseOrThrow(schemas.requestBody, request.body, "reqbody");
|
|
831
|
+
if (schemas.requestParam) request.params = await _parseOrThrow(schemas.requestParam, request.params, "reqparams");
|
|
832
|
+
if (schemas.requestQuery) {
|
|
833
|
+
const parsedQuery = await _parseOrThrow(schemas.requestQuery, request.query, "reqquery");
|
|
852
834
|
Object.defineProperty(request, "query", {
|
|
853
835
|
value: parsedQuery,
|
|
854
836
|
writable: true,
|
|
@@ -858,7 +840,8 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
|
|
|
858
840
|
}
|
|
859
841
|
const result = await fn.bind(controllerInstance)(request, response, next);
|
|
860
842
|
if (result instanceof ResponseEntity) {
|
|
861
|
-
|
|
843
|
+
const body = schemas && schemas.responseBody ? await _parseOrThrow(schemas.responseBody, result.getBody(), "resbody") : result.getBody();
|
|
844
|
+
response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(body));
|
|
862
845
|
return;
|
|
863
846
|
}
|
|
864
847
|
if (result instanceof RedirectView) {
|
|
@@ -925,7 +908,7 @@ DefaultResponseStatusErrorMiddleware = __decorate([MiddlewareClass()], DefaultRe
|
|
|
925
908
|
//#region src/middleware/default/openapi/index.ts
|
|
926
909
|
let DefaultOpenApiMiddleware = class DefaultOpenApiMiddleware {
|
|
927
910
|
handle(_request, _response, _next) {
|
|
928
|
-
return ResponseEntity.ok().body(
|
|
911
|
+
return ResponseEntity.ok().body(generateOpenApiSpec());
|
|
929
912
|
}
|
|
930
913
|
};
|
|
931
914
|
__decorate([GET(_settings.doc.openApiPath)], DefaultOpenApiMiddleware.prototype, "handle", null);
|
|
@@ -936,25 +919,25 @@ let Serve = class Serve {
|
|
|
936
919
|
constructor() {
|
|
937
920
|
this.handlers = swagger.serve;
|
|
938
921
|
}
|
|
939
|
-
handle(
|
|
940
|
-
return this.handlers;
|
|
922
|
+
handle(request, response, next) {
|
|
923
|
+
return Sapling.chainHandlers(this.handlers, request, response, next);
|
|
941
924
|
}
|
|
942
925
|
};
|
|
943
|
-
__decorate([Middleware()], Serve.prototype, "handle", null);
|
|
926
|
+
__decorate([Middleware(_settings.doc.swaggerPath)], Serve.prototype, "handle", null);
|
|
944
927
|
Serve = __decorate([MiddlewareClass()], Serve);
|
|
945
928
|
let Setup = class Setup {
|
|
946
929
|
constructor() {
|
|
947
|
-
this.handler = swagger.setup(
|
|
930
|
+
this.handler = swagger.setup(null, { swaggerOptions: { url: _settings.doc.openApiPath } });
|
|
948
931
|
}
|
|
949
932
|
handle(request, response, next) {
|
|
950
933
|
return this.handler(request, response, next);
|
|
951
934
|
}
|
|
952
935
|
};
|
|
953
|
-
__decorate([Middleware()], Setup.prototype, "handle", null);
|
|
936
|
+
__decorate([Middleware(_settings.doc.swaggerPath)], Setup.prototype, "handle", null);
|
|
954
937
|
Setup = __decorate([MiddlewareClass()], Setup);
|
|
955
938
|
const DefaultSwaggerMiddleware = {
|
|
956
939
|
Serve,
|
|
957
940
|
Setup
|
|
958
941
|
};
|
|
959
942
|
//#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,
|
|
943
|
+
export { Controller, DELETE, DefaultBaseErrorMiddleware, DefaultOpenApiMiddleware, DefaultParserErrorMiddleware, DefaultResponseStatusErrorMiddleware, DefaultSwaggerMiddleware, GET, HEAD, Html404ErrorPage, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, ParserError, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseBody, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, Sapling, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _getOrCreateSchemaDefinition, _getRoutes, _getValidatorSchema, _parseOrThrow, _registerControllerClass, _resolve, _setOnce, _settings, generateOpenApiSpec, methodResolve, openApiGenerator, setOpenApiConfig };
|