@tahminator/sapling 2.0.5-beta.662f05c0 → 2.0.5-beta.75e5e346
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 +188 -174
- package/dist/index.d.cts +45 -86
- package/dist/index.d.mts +45 -86
- package/dist/index.mjs +177 -172
- 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,42 @@ 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 ControllerSchema(options) {
|
|
494
|
+
return (target) => {
|
|
495
|
+
_setControllerSchema(target, options);
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
function RouteSchema(options) {
|
|
499
|
+
return (target, propertyKey) => {
|
|
500
|
+
const ctor = target.constructor;
|
|
501
|
+
_setRouteSchema(ctor, String(propertyKey), options);
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
function _setRouteSchema(ctor, fnName, options) {
|
|
505
|
+
_getOrCreateMap(_routeSchemaStore, ctor).set(fnName, options);
|
|
506
|
+
}
|
|
507
|
+
function _setControllerSchema(ctor, options) {
|
|
508
|
+
_controllerSchemaStore.set(ctor, options);
|
|
509
|
+
}
|
|
510
|
+
function _getRouteSchema(ctor, fnName) {
|
|
511
|
+
return _routeSchemaStore.get(ctor)?.get(fnName);
|
|
512
|
+
}
|
|
513
|
+
function _getControllerSchema(ctor) {
|
|
514
|
+
return _controllerSchemaStore.get(ctor);
|
|
515
|
+
}
|
|
516
|
+
//#endregion
|
|
594
517
|
//#region src/helper/openapi.ts
|
|
595
518
|
var OpenAPIGenerator = class {
|
|
596
519
|
constructor() {
|
|
@@ -612,42 +535,79 @@ var OpenAPIGenerator = class {
|
|
|
612
535
|
generateSpec() {
|
|
613
536
|
const config = this.config;
|
|
614
537
|
const paths = {};
|
|
538
|
+
const tags = [];
|
|
615
539
|
for (const { class: controllerClass, prefix } of this.controllers) {
|
|
616
540
|
const routes = _getRoutes(controllerClass);
|
|
541
|
+
const controllerSchema = _getControllerSchema(controllerClass);
|
|
542
|
+
if (controllerSchema?.title) tags.push({
|
|
543
|
+
name: controllerSchema.title,
|
|
544
|
+
description: controllerSchema.description
|
|
545
|
+
});
|
|
617
546
|
for (const route of routes) {
|
|
618
547
|
if (route.method === "USE") continue;
|
|
619
|
-
const schemas =
|
|
620
|
-
const
|
|
621
|
-
|
|
548
|
+
const schemas = _getValidatorSchema(controllerClass, route.fnName);
|
|
549
|
+
const routeSchema = _getRouteSchema(controllerClass, route.fnName);
|
|
550
|
+
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.`);
|
|
551
|
+
const openApiPath = (prefix + route.path).replace(/:([A-Za-z0-9_]+)/g, "{$1}");
|
|
622
552
|
if (!paths[openApiPath]) paths[openApiPath] = {};
|
|
623
|
-
const
|
|
553
|
+
const responses = {};
|
|
554
|
+
if (schemas?.responseBody) {
|
|
555
|
+
const responseSchema = this.toJsonSchema(schemas.responseBody, "output");
|
|
556
|
+
responses["200"] = {
|
|
557
|
+
description: responseSchema.description ?? "Successful response",
|
|
558
|
+
content: { "application/json": { schema: responseSchema } }
|
|
559
|
+
};
|
|
560
|
+
} else responses["200"] = { description: "Successful response" };
|
|
561
|
+
if (routeSchema?.responses) for (const resp of routeSchema.responses) {
|
|
562
|
+
const responseSchema = resp.schema ? this.toJsonSchema(resp.schema, "output") : void 0;
|
|
563
|
+
responses[String(resp.statusCode)] = {
|
|
564
|
+
description: resp.description ?? responseSchema?.description ?? `Response ${resp.statusCode}`,
|
|
565
|
+
...responseSchema ? { content: { "application/json": { schema: responseSchema } } } : {}
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
const operation = {
|
|
569
|
+
responses,
|
|
570
|
+
summary: routeSchema?.summary,
|
|
571
|
+
description: routeSchema?.description,
|
|
572
|
+
tags: controllerSchema?.title ? [controllerSchema.title] : void 0
|
|
573
|
+
};
|
|
624
574
|
const parameters = [];
|
|
625
|
-
if (schemas?.
|
|
626
|
-
const paramSchema = this.toJsonSchema(schemas.
|
|
627
|
-
if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties))
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
575
|
+
if (schemas?.requestParam) {
|
|
576
|
+
const paramSchema = this.toJsonSchema(schemas.requestParam, "input");
|
|
577
|
+
if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties)) {
|
|
578
|
+
const parameterSchema = schema;
|
|
579
|
+
parameters.push({
|
|
580
|
+
name,
|
|
581
|
+
in: "path",
|
|
582
|
+
required: true,
|
|
583
|
+
description: parameterSchema.description,
|
|
584
|
+
schema: parameterSchema
|
|
585
|
+
});
|
|
586
|
+
}
|
|
633
587
|
}
|
|
634
|
-
if (schemas?.
|
|
635
|
-
const querySchema = this.toJsonSchema(schemas.
|
|
588
|
+
if (schemas?.requestQuery) {
|
|
589
|
+
const querySchema = this.toJsonSchema(schemas.requestQuery, "input");
|
|
636
590
|
if (querySchema.type === "object" && querySchema.properties) for (const [name, schema] of Object.entries(querySchema.properties)) {
|
|
637
591
|
const isRequired = Array.isArray(querySchema.required) && querySchema.required.includes(name);
|
|
592
|
+
const parameterSchema = schema;
|
|
638
593
|
parameters.push({
|
|
639
594
|
name,
|
|
640
595
|
in: "query",
|
|
641
596
|
required: isRequired,
|
|
642
|
-
|
|
597
|
+
description: parameterSchema.description,
|
|
598
|
+
schema: parameterSchema
|
|
643
599
|
});
|
|
644
600
|
}
|
|
645
601
|
}
|
|
646
602
|
if (parameters.length > 0) operation.parameters = parameters;
|
|
647
|
-
if (schemas?.
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
603
|
+
if (schemas?.requestBody) {
|
|
604
|
+
const requestSchema = this.toJsonSchema(schemas.requestBody, "input");
|
|
605
|
+
operation.requestBody = {
|
|
606
|
+
required: true,
|
|
607
|
+
description: requestSchema.description,
|
|
608
|
+
content: { "application/json": { schema: requestSchema } }
|
|
609
|
+
};
|
|
610
|
+
}
|
|
651
611
|
const method = route.method.toLowerCase();
|
|
652
612
|
paths[openApiPath][method] = operation;
|
|
653
613
|
}
|
|
@@ -659,12 +619,15 @@ var OpenAPIGenerator = class {
|
|
|
659
619
|
version: config.version,
|
|
660
620
|
description: config.description
|
|
661
621
|
},
|
|
622
|
+
tags: tags.length > 0 ? tags : void 0,
|
|
662
623
|
paths
|
|
663
624
|
};
|
|
664
625
|
}
|
|
665
|
-
toJsonSchema(schema) {
|
|
626
|
+
toJsonSchema(schema, direction = "output") {
|
|
666
627
|
try {
|
|
667
|
-
|
|
628
|
+
const jsonSchema = schema["~standard"].jsonSchema;
|
|
629
|
+
if (direction === "input" && jsonSchema.input) return jsonSchema.input({ target: "openapi-3.0" });
|
|
630
|
+
return jsonSchema.output({ target: "openapi-3.0" });
|
|
668
631
|
} catch (e) {
|
|
669
632
|
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`);
|
|
670
633
|
throw e;
|
|
@@ -675,10 +638,10 @@ const openApiGenerator = new OpenAPIGenerator();
|
|
|
675
638
|
function _registerControllerClass(controllerClass, prefix) {
|
|
676
639
|
openApiGenerator.registerController(controllerClass, prefix);
|
|
677
640
|
}
|
|
678
|
-
function
|
|
641
|
+
function setOpenApiConfig(config) {
|
|
679
642
|
openApiGenerator.setConfig(config);
|
|
680
643
|
}
|
|
681
|
-
function
|
|
644
|
+
function generateOpenApiSpec() {
|
|
682
645
|
return openApiGenerator.generateSpec();
|
|
683
646
|
}
|
|
684
647
|
//#endregion
|
|
@@ -814,6 +777,60 @@ function _resolve(ctor) {
|
|
|
814
777
|
return _InjectableRegistry.get(ctor);
|
|
815
778
|
}
|
|
816
779
|
//#endregion
|
|
780
|
+
//#region src/annotation/validator.ts
|
|
781
|
+
const _validatorSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
782
|
+
function ResponseBody(schema) {
|
|
783
|
+
return (target, propertyKey) => {
|
|
784
|
+
const ctor = target.constructor;
|
|
785
|
+
const fnName = String(propertyKey);
|
|
786
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "responseBody", schema, fnName);
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
function RequestBody(schema) {
|
|
790
|
+
return (target, propertyKey) => {
|
|
791
|
+
const ctor = target.constructor;
|
|
792
|
+
const fnName = String(propertyKey);
|
|
793
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestBody", schema, fnName);
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
function RequestParam(schema) {
|
|
797
|
+
return (target, propertyKey) => {
|
|
798
|
+
const ctor = target.constructor;
|
|
799
|
+
const fnName = String(propertyKey);
|
|
800
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestParam", schema, fnName);
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
function RequestQuery(schema) {
|
|
804
|
+
return (target, propertyKey) => {
|
|
805
|
+
const ctor = target.constructor;
|
|
806
|
+
const fnName = String(propertyKey);
|
|
807
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestQuery", schema, fnName);
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
function _getOrCreateSchemaDefinition(ctor, fnName) {
|
|
811
|
+
const byFn = _getOrCreateMap(_validatorSchemaStore, ctor);
|
|
812
|
+
const existing = byFn.get(fnName);
|
|
813
|
+
if (existing) return existing;
|
|
814
|
+
const created = {};
|
|
815
|
+
byFn.set(fnName, created);
|
|
816
|
+
return created;
|
|
817
|
+
}
|
|
818
|
+
async function _parseOrThrow(schema, input, kind) {
|
|
819
|
+
const result = await schema["~standard"].validate(input);
|
|
820
|
+
if (result.issues) {
|
|
821
|
+
console.debug(`Failed to parse ${schema["~standard"].vendor} schema\nissues: ${result.issues}`);
|
|
822
|
+
throw new ParserError(kind, result.issues, schema["~standard"].vendor);
|
|
823
|
+
}
|
|
824
|
+
return result.value;
|
|
825
|
+
}
|
|
826
|
+
function _getValidatorSchema(ctor, fnName) {
|
|
827
|
+
return _validatorSchemaStore.get(ctor)?.get(fnName);
|
|
828
|
+
}
|
|
829
|
+
function _setOnce(def, key, schema, fnName) {
|
|
830
|
+
if (def[key]) throw new Error(`Duplicate schema for "${String(key)}" on method "${fnName}"`);
|
|
831
|
+
def[key] = schema;
|
|
832
|
+
}
|
|
833
|
+
//#endregion
|
|
817
834
|
//#region src/annotation/controller.ts
|
|
818
835
|
const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
|
|
819
836
|
/**
|
|
@@ -868,12 +885,12 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
|
|
|
868
885
|
return;
|
|
869
886
|
}
|
|
870
887
|
router[methodName](fp, async (request, response, next) => {
|
|
871
|
-
const schemas =
|
|
888
|
+
const schemas = _getValidatorSchema(target, fnName);
|
|
872
889
|
if (schemas) {
|
|
873
|
-
if (schemas.
|
|
874
|
-
if (schemas.
|
|
875
|
-
if (schemas.
|
|
876
|
-
const parsedQuery = await _parseOrThrow(schemas.
|
|
890
|
+
if (schemas.requestBody) request.body = await _parseOrThrow(schemas.requestBody, request.body, "reqbody");
|
|
891
|
+
if (schemas.requestParam) request.params = await _parseOrThrow(schemas.requestParam, request.params, "reqparams");
|
|
892
|
+
if (schemas.requestQuery) {
|
|
893
|
+
const parsedQuery = await _parseOrThrow(schemas.requestQuery, request.query, "reqquery");
|
|
877
894
|
Object.defineProperty(request, "query", {
|
|
878
895
|
value: parsedQuery,
|
|
879
896
|
writable: true,
|
|
@@ -883,7 +900,8 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
|
|
|
883
900
|
}
|
|
884
901
|
const result = await fn.bind(controllerInstance)(request, response, next);
|
|
885
902
|
if (result instanceof ResponseEntity) {
|
|
886
|
-
|
|
903
|
+
const body = schemas && schemas.responseBody ? await _parseOrThrow(schemas.responseBody, result.getBody(), "resbody") : result.getBody();
|
|
904
|
+
response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(body));
|
|
887
905
|
return;
|
|
888
906
|
}
|
|
889
907
|
if (result instanceof RedirectView) {
|
|
@@ -950,7 +968,7 @@ DefaultResponseStatusErrorMiddleware = __decorate([MiddlewareClass()], DefaultRe
|
|
|
950
968
|
//#region src/middleware/default/openapi/index.ts
|
|
951
969
|
let DefaultOpenApiMiddleware = class DefaultOpenApiMiddleware {
|
|
952
970
|
handle(_request, _response, _next) {
|
|
953
|
-
return ResponseEntity.ok().body(
|
|
971
|
+
return ResponseEntity.ok().body(generateOpenApiSpec());
|
|
954
972
|
}
|
|
955
973
|
};
|
|
956
974
|
__decorate([GET(_settings.doc.openApiPath)], DefaultOpenApiMiddleware.prototype, "handle", null);
|
|
@@ -962,20 +980,7 @@ let Serve = class Serve {
|
|
|
962
980
|
this.handlers = swagger_ui_express.default.serve;
|
|
963
981
|
}
|
|
964
982
|
handle(request, response, next) {
|
|
965
|
-
this.
|
|
966
|
-
}
|
|
967
|
-
runNext(i, req, res, next) {
|
|
968
|
-
if (i >= this.handlers.length) {
|
|
969
|
-
next();
|
|
970
|
-
return;
|
|
971
|
-
}
|
|
972
|
-
this.handlers[i]?.(req, res, (err) => {
|
|
973
|
-
if (err) {
|
|
974
|
-
next(err);
|
|
975
|
-
return;
|
|
976
|
-
}
|
|
977
|
-
this.runNext(i + 1, req, res, next);
|
|
978
|
-
});
|
|
983
|
+
return Sapling.chainHandlers(this.handlers, request, response, next);
|
|
979
984
|
}
|
|
980
985
|
};
|
|
981
986
|
__decorate([Middleware(_settings.doc.swaggerPath)], Serve.prototype, "handle", null);
|
|
@@ -996,6 +1001,7 @@ const DefaultSwaggerMiddleware = {
|
|
|
996
1001
|
};
|
|
997
1002
|
//#endregion
|
|
998
1003
|
exports.Controller = Controller;
|
|
1004
|
+
exports.ControllerSchema = ControllerSchema;
|
|
999
1005
|
exports.DELETE = DELETE;
|
|
1000
1006
|
Object.defineProperty(exports, "DefaultBaseErrorMiddleware", {
|
|
1001
1007
|
enumerable: true,
|
|
@@ -1038,21 +1044,29 @@ exports.RedirectView = RedirectView;
|
|
|
1038
1044
|
exports.RequestBody = RequestBody;
|
|
1039
1045
|
exports.RequestParam = RequestParam;
|
|
1040
1046
|
exports.RequestQuery = RequestQuery;
|
|
1047
|
+
exports.ResponseBody = ResponseBody;
|
|
1041
1048
|
exports.ResponseEntity = ResponseEntity;
|
|
1042
1049
|
exports.ResponseEntityBuilder = ResponseEntityBuilder;
|
|
1043
1050
|
exports.ResponseStatusError = ResponseStatusError;
|
|
1051
|
+
exports.RouteSchema = RouteSchema;
|
|
1044
1052
|
exports.Sapling = Sapling;
|
|
1045
1053
|
exports._ControllerRegistry = _ControllerRegistry;
|
|
1046
1054
|
exports._InjectableDeps = _InjectableDeps;
|
|
1047
1055
|
exports._InjectableRegistry = _InjectableRegistry;
|
|
1048
1056
|
exports._Route = _Route;
|
|
1049
|
-
exports.
|
|
1050
|
-
exports.
|
|
1057
|
+
exports._getControllerSchema = _getControllerSchema;
|
|
1058
|
+
exports._getOrCreateSchemaDefinition = _getOrCreateSchemaDefinition;
|
|
1059
|
+
exports._getRouteSchema = _getRouteSchema;
|
|
1051
1060
|
exports._getRoutes = _getRoutes;
|
|
1061
|
+
exports._getValidatorSchema = _getValidatorSchema;
|
|
1052
1062
|
exports._parseOrThrow = _parseOrThrow;
|
|
1053
1063
|
exports._registerControllerClass = _registerControllerClass;
|
|
1054
1064
|
exports._resolve = _resolve;
|
|
1055
|
-
exports.
|
|
1065
|
+
exports._setControllerSchema = _setControllerSchema;
|
|
1066
|
+
exports._setOnce = _setOnce;
|
|
1067
|
+
exports._setRouteSchema = _setRouteSchema;
|
|
1056
1068
|
exports._settings = _settings;
|
|
1069
|
+
exports.generateOpenApiSpec = generateOpenApiSpec;
|
|
1057
1070
|
exports.methodResolve = methodResolve;
|
|
1058
1071
|
exports.openApiGenerator = openApiGenerator;
|
|
1072
|
+
exports.setOpenApiConfig = setOpenApiConfig;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import e, { ErrorRequestHandler, NextFunction, Request, Response as Response$1, Router } from "express";
|
|
1
|
+
import e, { ErrorRequestHandler, NextFunction, Request, RequestHandler, Response as Response$1, Router } from "express";
|
|
2
2
|
|
|
3
3
|
//#region src/html/404.d.ts
|
|
4
4
|
/**
|
|
@@ -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,53 @@ 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;
|
|
891
|
+
//#endregion
|
|
892
|
+
//#region src/annotation/schema.d.ts
|
|
893
|
+
type ResponseSchema = {
|
|
894
|
+
statusCode: HttpStatus;
|
|
895
|
+
description?: string;
|
|
896
|
+
schema?: StandardSchemaV1 & StandardJSONSchemaV1;
|
|
897
|
+
};
|
|
898
|
+
type RouteSchemaDefinition = {
|
|
899
|
+
summary?: string;
|
|
900
|
+
description?: string;
|
|
901
|
+
responses?: ResponseSchema[];
|
|
902
|
+
};
|
|
903
|
+
type ControllerSchemaDefinition = {
|
|
904
|
+
title?: string;
|
|
905
|
+
description?: string;
|
|
906
|
+
};
|
|
907
|
+
declare function ControllerSchema(options: {
|
|
908
|
+
title?: string;
|
|
909
|
+
description?: string;
|
|
910
|
+
}): ClassDecorator;
|
|
911
|
+
declare function RouteSchema(options: {
|
|
912
|
+
summary?: string;
|
|
913
|
+
description?: string;
|
|
914
|
+
responses?: ResponseSchema[];
|
|
915
|
+
}): MethodDecorator;
|
|
916
|
+
declare function _setRouteSchema(ctor: Function, fnName: string, options: RouteSchemaDefinition): void;
|
|
917
|
+
declare function _setControllerSchema(ctor: Function, options: ControllerSchemaDefinition): void;
|
|
918
|
+
declare function _getRouteSchema(ctor: Function, fnName: string): RouteSchemaDefinition | undefined;
|
|
919
|
+
declare function _getControllerSchema(ctor: Function): ControllerSchemaDefinition | undefined;
|
|
960
920
|
//#endregion
|
|
961
921
|
//#region src/middleware/default/error/base.d.ts
|
|
962
922
|
/**
|
|
@@ -993,7 +953,6 @@ declare class DefaultOpenApiMiddleware {
|
|
|
993
953
|
declare class Serve {
|
|
994
954
|
private readonly handlers;
|
|
995
955
|
handle(request: Request, response: Response$1, next: NextFunction): void;
|
|
996
|
-
private runNext;
|
|
997
956
|
}
|
|
998
957
|
declare class Setup {
|
|
999
958
|
private readonly handler;
|
|
@@ -1005,4 +964,4 @@ declare const DefaultSwaggerMiddleware: {
|
|
|
1005
964
|
Setup: typeof Setup;
|
|
1006
965
|
};
|
|
1007
966
|
//#endregion
|
|
1008
|
-
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,
|
|
967
|
+
export { Class, Controller, ControllerSchema, ControllerSchemaDefinition, 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, ResponseSchema, ResponseStatusError, RouteDefinition, RouteSchema, RouteSchemaDefinition, Sapling, ValidatorSchema, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _getControllerSchema, _getOrCreateSchemaDefinition, _getRouteSchema, _getRoutes, _getValidatorSchema, _parseOrThrow, _registerControllerClass, _resolve, _setControllerSchema, _setOnce, _setRouteSchema, _settings, generateOpenApiSpec, methodResolve, openApiGenerator, setOpenApiConfig };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import e, { ErrorRequestHandler, NextFunction, Request, Response as Response$1, Router } from "express";
|
|
1
|
+
import e, { ErrorRequestHandler, NextFunction, Request, RequestHandler, Response as Response$1, Router } from "express";
|
|
2
2
|
|
|
3
3
|
//#region src/html/404.d.ts
|
|
4
4
|
/**
|
|
@@ -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,53 @@ 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;
|
|
891
|
+
//#endregion
|
|
892
|
+
//#region src/annotation/schema.d.ts
|
|
893
|
+
type ResponseSchema = {
|
|
894
|
+
statusCode: HttpStatus;
|
|
895
|
+
description?: string;
|
|
896
|
+
schema?: StandardSchemaV1 & StandardJSONSchemaV1;
|
|
897
|
+
};
|
|
898
|
+
type RouteSchemaDefinition = {
|
|
899
|
+
summary?: string;
|
|
900
|
+
description?: string;
|
|
901
|
+
responses?: ResponseSchema[];
|
|
902
|
+
};
|
|
903
|
+
type ControllerSchemaDefinition = {
|
|
904
|
+
title?: string;
|
|
905
|
+
description?: string;
|
|
906
|
+
};
|
|
907
|
+
declare function ControllerSchema(options: {
|
|
908
|
+
title?: string;
|
|
909
|
+
description?: string;
|
|
910
|
+
}): ClassDecorator;
|
|
911
|
+
declare function RouteSchema(options: {
|
|
912
|
+
summary?: string;
|
|
913
|
+
description?: string;
|
|
914
|
+
responses?: ResponseSchema[];
|
|
915
|
+
}): MethodDecorator;
|
|
916
|
+
declare function _setRouteSchema(ctor: Function, fnName: string, options: RouteSchemaDefinition): void;
|
|
917
|
+
declare function _setControllerSchema(ctor: Function, options: ControllerSchemaDefinition): void;
|
|
918
|
+
declare function _getRouteSchema(ctor: Function, fnName: string): RouteSchemaDefinition | undefined;
|
|
919
|
+
declare function _getControllerSchema(ctor: Function): ControllerSchemaDefinition | undefined;
|
|
960
920
|
//#endregion
|
|
961
921
|
//#region src/middleware/default/error/base.d.ts
|
|
962
922
|
/**
|
|
@@ -993,7 +953,6 @@ declare class DefaultOpenApiMiddleware {
|
|
|
993
953
|
declare class Serve {
|
|
994
954
|
private readonly handlers;
|
|
995
955
|
handle(request: Request, response: Response$1, next: NextFunction): void;
|
|
996
|
-
private runNext;
|
|
997
956
|
}
|
|
998
957
|
declare class Setup {
|
|
999
958
|
private readonly handler;
|
|
@@ -1005,4 +964,4 @@ declare const DefaultSwaggerMiddleware: {
|
|
|
1005
964
|
Setup: typeof Setup;
|
|
1006
965
|
};
|
|
1007
966
|
//#endregion
|
|
1008
|
-
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,
|
|
967
|
+
export { Class, Controller, ControllerSchema, ControllerSchemaDefinition, 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, ResponseSchema, ResponseStatusError, RouteDefinition, RouteSchema, RouteSchemaDefinition, Sapling, ValidatorSchema, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _getControllerSchema, _getOrCreateSchemaDefinition, _getRouteSchema, _getRoutes, _getValidatorSchema, _parseOrThrow, _registerControllerClass, _resolve, _setControllerSchema, _setOnce, _setRouteSchema, _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,42 @@ 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 ControllerSchema(options) {
|
|
469
|
+
return (target) => {
|
|
470
|
+
_setControllerSchema(target, options);
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
function RouteSchema(options) {
|
|
474
|
+
return (target, propertyKey) => {
|
|
475
|
+
const ctor = target.constructor;
|
|
476
|
+
_setRouteSchema(ctor, String(propertyKey), options);
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
function _setRouteSchema(ctor, fnName, options) {
|
|
480
|
+
_getOrCreateMap(_routeSchemaStore, ctor).set(fnName, options);
|
|
481
|
+
}
|
|
482
|
+
function _setControllerSchema(ctor, options) {
|
|
483
|
+
_controllerSchemaStore.set(ctor, options);
|
|
484
|
+
}
|
|
485
|
+
function _getRouteSchema(ctor, fnName) {
|
|
486
|
+
return _routeSchemaStore.get(ctor)?.get(fnName);
|
|
487
|
+
}
|
|
488
|
+
function _getControllerSchema(ctor) {
|
|
489
|
+
return _controllerSchemaStore.get(ctor);
|
|
490
|
+
}
|
|
491
|
+
//#endregion
|
|
569
492
|
//#region src/helper/openapi.ts
|
|
570
493
|
var OpenAPIGenerator = class {
|
|
571
494
|
constructor() {
|
|
@@ -587,42 +510,79 @@ var OpenAPIGenerator = class {
|
|
|
587
510
|
generateSpec() {
|
|
588
511
|
const config = this.config;
|
|
589
512
|
const paths = {};
|
|
513
|
+
const tags = [];
|
|
590
514
|
for (const { class: controllerClass, prefix } of this.controllers) {
|
|
591
515
|
const routes = _getRoutes(controllerClass);
|
|
516
|
+
const controllerSchema = _getControllerSchema(controllerClass);
|
|
517
|
+
if (controllerSchema?.title) tags.push({
|
|
518
|
+
name: controllerSchema.title,
|
|
519
|
+
description: controllerSchema.description
|
|
520
|
+
});
|
|
592
521
|
for (const route of routes) {
|
|
593
522
|
if (route.method === "USE") continue;
|
|
594
|
-
const schemas =
|
|
595
|
-
const
|
|
596
|
-
|
|
523
|
+
const schemas = _getValidatorSchema(controllerClass, route.fnName);
|
|
524
|
+
const routeSchema = _getRouteSchema(controllerClass, route.fnName);
|
|
525
|
+
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.`);
|
|
526
|
+
const openApiPath = (prefix + route.path).replace(/:([A-Za-z0-9_]+)/g, "{$1}");
|
|
597
527
|
if (!paths[openApiPath]) paths[openApiPath] = {};
|
|
598
|
-
const
|
|
528
|
+
const responses = {};
|
|
529
|
+
if (schemas?.responseBody) {
|
|
530
|
+
const responseSchema = this.toJsonSchema(schemas.responseBody, "output");
|
|
531
|
+
responses["200"] = {
|
|
532
|
+
description: responseSchema.description ?? "Successful response",
|
|
533
|
+
content: { "application/json": { schema: responseSchema } }
|
|
534
|
+
};
|
|
535
|
+
} else responses["200"] = { description: "Successful response" };
|
|
536
|
+
if (routeSchema?.responses) for (const resp of routeSchema.responses) {
|
|
537
|
+
const responseSchema = resp.schema ? this.toJsonSchema(resp.schema, "output") : void 0;
|
|
538
|
+
responses[String(resp.statusCode)] = {
|
|
539
|
+
description: resp.description ?? responseSchema?.description ?? `Response ${resp.statusCode}`,
|
|
540
|
+
...responseSchema ? { content: { "application/json": { schema: responseSchema } } } : {}
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
const operation = {
|
|
544
|
+
responses,
|
|
545
|
+
summary: routeSchema?.summary,
|
|
546
|
+
description: routeSchema?.description,
|
|
547
|
+
tags: controllerSchema?.title ? [controllerSchema.title] : void 0
|
|
548
|
+
};
|
|
599
549
|
const parameters = [];
|
|
600
|
-
if (schemas?.
|
|
601
|
-
const paramSchema = this.toJsonSchema(schemas.
|
|
602
|
-
if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties))
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
550
|
+
if (schemas?.requestParam) {
|
|
551
|
+
const paramSchema = this.toJsonSchema(schemas.requestParam, "input");
|
|
552
|
+
if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties)) {
|
|
553
|
+
const parameterSchema = schema;
|
|
554
|
+
parameters.push({
|
|
555
|
+
name,
|
|
556
|
+
in: "path",
|
|
557
|
+
required: true,
|
|
558
|
+
description: parameterSchema.description,
|
|
559
|
+
schema: parameterSchema
|
|
560
|
+
});
|
|
561
|
+
}
|
|
608
562
|
}
|
|
609
|
-
if (schemas?.
|
|
610
|
-
const querySchema = this.toJsonSchema(schemas.
|
|
563
|
+
if (schemas?.requestQuery) {
|
|
564
|
+
const querySchema = this.toJsonSchema(schemas.requestQuery, "input");
|
|
611
565
|
if (querySchema.type === "object" && querySchema.properties) for (const [name, schema] of Object.entries(querySchema.properties)) {
|
|
612
566
|
const isRequired = Array.isArray(querySchema.required) && querySchema.required.includes(name);
|
|
567
|
+
const parameterSchema = schema;
|
|
613
568
|
parameters.push({
|
|
614
569
|
name,
|
|
615
570
|
in: "query",
|
|
616
571
|
required: isRequired,
|
|
617
|
-
|
|
572
|
+
description: parameterSchema.description,
|
|
573
|
+
schema: parameterSchema
|
|
618
574
|
});
|
|
619
575
|
}
|
|
620
576
|
}
|
|
621
577
|
if (parameters.length > 0) operation.parameters = parameters;
|
|
622
|
-
if (schemas?.
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
578
|
+
if (schemas?.requestBody) {
|
|
579
|
+
const requestSchema = this.toJsonSchema(schemas.requestBody, "input");
|
|
580
|
+
operation.requestBody = {
|
|
581
|
+
required: true,
|
|
582
|
+
description: requestSchema.description,
|
|
583
|
+
content: { "application/json": { schema: requestSchema } }
|
|
584
|
+
};
|
|
585
|
+
}
|
|
626
586
|
const method = route.method.toLowerCase();
|
|
627
587
|
paths[openApiPath][method] = operation;
|
|
628
588
|
}
|
|
@@ -634,12 +594,15 @@ var OpenAPIGenerator = class {
|
|
|
634
594
|
version: config.version,
|
|
635
595
|
description: config.description
|
|
636
596
|
},
|
|
597
|
+
tags: tags.length > 0 ? tags : void 0,
|
|
637
598
|
paths
|
|
638
599
|
};
|
|
639
600
|
}
|
|
640
|
-
toJsonSchema(schema) {
|
|
601
|
+
toJsonSchema(schema, direction = "output") {
|
|
641
602
|
try {
|
|
642
|
-
|
|
603
|
+
const jsonSchema = schema["~standard"].jsonSchema;
|
|
604
|
+
if (direction === "input" && jsonSchema.input) return jsonSchema.input({ target: "openapi-3.0" });
|
|
605
|
+
return jsonSchema.output({ target: "openapi-3.0" });
|
|
643
606
|
} catch (e) {
|
|
644
607
|
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
608
|
throw e;
|
|
@@ -650,10 +613,10 @@ const openApiGenerator = new OpenAPIGenerator();
|
|
|
650
613
|
function _registerControllerClass(controllerClass, prefix) {
|
|
651
614
|
openApiGenerator.registerController(controllerClass, prefix);
|
|
652
615
|
}
|
|
653
|
-
function
|
|
616
|
+
function setOpenApiConfig(config) {
|
|
654
617
|
openApiGenerator.setConfig(config);
|
|
655
618
|
}
|
|
656
|
-
function
|
|
619
|
+
function generateOpenApiSpec() {
|
|
657
620
|
return openApiGenerator.generateSpec();
|
|
658
621
|
}
|
|
659
622
|
//#endregion
|
|
@@ -789,6 +752,60 @@ function _resolve(ctor) {
|
|
|
789
752
|
return _InjectableRegistry.get(ctor);
|
|
790
753
|
}
|
|
791
754
|
//#endregion
|
|
755
|
+
//#region src/annotation/validator.ts
|
|
756
|
+
const _validatorSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
757
|
+
function ResponseBody(schema) {
|
|
758
|
+
return (target, propertyKey) => {
|
|
759
|
+
const ctor = target.constructor;
|
|
760
|
+
const fnName = String(propertyKey);
|
|
761
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "responseBody", schema, fnName);
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
function RequestBody(schema) {
|
|
765
|
+
return (target, propertyKey) => {
|
|
766
|
+
const ctor = target.constructor;
|
|
767
|
+
const fnName = String(propertyKey);
|
|
768
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestBody", schema, fnName);
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
function RequestParam(schema) {
|
|
772
|
+
return (target, propertyKey) => {
|
|
773
|
+
const ctor = target.constructor;
|
|
774
|
+
const fnName = String(propertyKey);
|
|
775
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestParam", schema, fnName);
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
function RequestQuery(schema) {
|
|
779
|
+
return (target, propertyKey) => {
|
|
780
|
+
const ctor = target.constructor;
|
|
781
|
+
const fnName = String(propertyKey);
|
|
782
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestQuery", schema, fnName);
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
function _getOrCreateSchemaDefinition(ctor, fnName) {
|
|
786
|
+
const byFn = _getOrCreateMap(_validatorSchemaStore, ctor);
|
|
787
|
+
const existing = byFn.get(fnName);
|
|
788
|
+
if (existing) return existing;
|
|
789
|
+
const created = {};
|
|
790
|
+
byFn.set(fnName, created);
|
|
791
|
+
return created;
|
|
792
|
+
}
|
|
793
|
+
async function _parseOrThrow(schema, input, kind) {
|
|
794
|
+
const result = await schema["~standard"].validate(input);
|
|
795
|
+
if (result.issues) {
|
|
796
|
+
console.debug(`Failed to parse ${schema["~standard"].vendor} schema\nissues: ${result.issues}`);
|
|
797
|
+
throw new ParserError(kind, result.issues, schema["~standard"].vendor);
|
|
798
|
+
}
|
|
799
|
+
return result.value;
|
|
800
|
+
}
|
|
801
|
+
function _getValidatorSchema(ctor, fnName) {
|
|
802
|
+
return _validatorSchemaStore.get(ctor)?.get(fnName);
|
|
803
|
+
}
|
|
804
|
+
function _setOnce(def, key, schema, fnName) {
|
|
805
|
+
if (def[key]) throw new Error(`Duplicate schema for "${String(key)}" on method "${fnName}"`);
|
|
806
|
+
def[key] = schema;
|
|
807
|
+
}
|
|
808
|
+
//#endregion
|
|
792
809
|
//#region src/annotation/controller.ts
|
|
793
810
|
const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
|
|
794
811
|
/**
|
|
@@ -843,12 +860,12 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
|
|
|
843
860
|
return;
|
|
844
861
|
}
|
|
845
862
|
router[methodName](fp, async (request, response, next) => {
|
|
846
|
-
const schemas =
|
|
863
|
+
const schemas = _getValidatorSchema(target, fnName);
|
|
847
864
|
if (schemas) {
|
|
848
|
-
if (schemas.
|
|
849
|
-
if (schemas.
|
|
850
|
-
if (schemas.
|
|
851
|
-
const parsedQuery = await _parseOrThrow(schemas.
|
|
865
|
+
if (schemas.requestBody) request.body = await _parseOrThrow(schemas.requestBody, request.body, "reqbody");
|
|
866
|
+
if (schemas.requestParam) request.params = await _parseOrThrow(schemas.requestParam, request.params, "reqparams");
|
|
867
|
+
if (schemas.requestQuery) {
|
|
868
|
+
const parsedQuery = await _parseOrThrow(schemas.requestQuery, request.query, "reqquery");
|
|
852
869
|
Object.defineProperty(request, "query", {
|
|
853
870
|
value: parsedQuery,
|
|
854
871
|
writable: true,
|
|
@@ -858,7 +875,8 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
|
|
|
858
875
|
}
|
|
859
876
|
const result = await fn.bind(controllerInstance)(request, response, next);
|
|
860
877
|
if (result instanceof ResponseEntity) {
|
|
861
|
-
|
|
878
|
+
const body = schemas && schemas.responseBody ? await _parseOrThrow(schemas.responseBody, result.getBody(), "resbody") : result.getBody();
|
|
879
|
+
response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(body));
|
|
862
880
|
return;
|
|
863
881
|
}
|
|
864
882
|
if (result instanceof RedirectView) {
|
|
@@ -925,7 +943,7 @@ DefaultResponseStatusErrorMiddleware = __decorate([MiddlewareClass()], DefaultRe
|
|
|
925
943
|
//#region src/middleware/default/openapi/index.ts
|
|
926
944
|
let DefaultOpenApiMiddleware = class DefaultOpenApiMiddleware {
|
|
927
945
|
handle(_request, _response, _next) {
|
|
928
|
-
return ResponseEntity.ok().body(
|
|
946
|
+
return ResponseEntity.ok().body(generateOpenApiSpec());
|
|
929
947
|
}
|
|
930
948
|
};
|
|
931
949
|
__decorate([GET(_settings.doc.openApiPath)], DefaultOpenApiMiddleware.prototype, "handle", null);
|
|
@@ -937,20 +955,7 @@ let Serve = class Serve {
|
|
|
937
955
|
this.handlers = swagger.serve;
|
|
938
956
|
}
|
|
939
957
|
handle(request, response, next) {
|
|
940
|
-
this.
|
|
941
|
-
}
|
|
942
|
-
runNext(i, req, res, next) {
|
|
943
|
-
if (i >= this.handlers.length) {
|
|
944
|
-
next();
|
|
945
|
-
return;
|
|
946
|
-
}
|
|
947
|
-
this.handlers[i]?.(req, res, (err) => {
|
|
948
|
-
if (err) {
|
|
949
|
-
next(err);
|
|
950
|
-
return;
|
|
951
|
-
}
|
|
952
|
-
this.runNext(i + 1, req, res, next);
|
|
953
|
-
});
|
|
958
|
+
return Sapling.chainHandlers(this.handlers, request, response, next);
|
|
954
959
|
}
|
|
955
960
|
};
|
|
956
961
|
__decorate([Middleware(_settings.doc.swaggerPath)], Serve.prototype, "handle", null);
|
|
@@ -970,4 +975,4 @@ const DefaultSwaggerMiddleware = {
|
|
|
970
975
|
Setup
|
|
971
976
|
};
|
|
972
977
|
//#endregion
|
|
973
|
-
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,
|
|
978
|
+
export { Controller, ControllerSchema, 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, RouteSchema, Sapling, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _getControllerSchema, _getOrCreateSchemaDefinition, _getRouteSchema, _getRoutes, _getValidatorSchema, _parseOrThrow, _registerControllerClass, _resolve, _setControllerSchema, _setOnce, _setRouteSchema, _settings, generateOpenApiSpec, methodResolve, openApiGenerator, setOpenApiConfig };
|