@tahminator/sapling 2.0.4 → 2.0.5-beta.18e683d9
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/README.md +94 -4
- package/dist/index.cjs +387 -189
- package/dist/index.d.cts +467 -91
- package/dist/index.d.mts +467 -91
- package/dist/index.mjs +359 -189
- package/package.json +8 -2
package/dist/index.cjs
CHANGED
|
@@ -23,6 +23,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
//#endregion
|
|
24
24
|
let express = require("express");
|
|
25
25
|
express = __toESM(express);
|
|
26
|
+
let swagger_ui_express = require("swagger-ui-express");
|
|
27
|
+
swagger_ui_express = __toESM(swagger_ui_express);
|
|
26
28
|
//#region src/html/404.ts
|
|
27
29
|
/**
|
|
28
30
|
* Default Express.js 404 error page, as a string.
|
|
@@ -256,15 +258,20 @@ var ParserError = class ParserError extends ResponseStatusError {
|
|
|
256
258
|
case "reqbody": return "request body";
|
|
257
259
|
case "reqparams": return "request params";
|
|
258
260
|
case "reqquery": return "request query";
|
|
261
|
+
case "resbody": return "response body";
|
|
259
262
|
}
|
|
260
263
|
})()}: ${formatted}`;
|
|
261
264
|
}
|
|
262
265
|
};
|
|
263
266
|
//#endregion
|
|
264
267
|
//#region src/helper/sapling.ts
|
|
265
|
-
const
|
|
268
|
+
const _settings = {
|
|
266
269
|
serialize: JSON.stringify,
|
|
267
|
-
deserialize: JSON.parse
|
|
270
|
+
deserialize: JSON.parse,
|
|
271
|
+
doc: {
|
|
272
|
+
openApiPath: "/openapi.json",
|
|
273
|
+
swaggerPath: "/swagger.html"
|
|
274
|
+
}
|
|
268
275
|
};
|
|
269
276
|
/**
|
|
270
277
|
* Collection of utility functions which are essential for Sapling to function.
|
|
@@ -343,13 +350,13 @@ var Sapling = class Sapling {
|
|
|
343
350
|
* @defaultValue `JSON.stringify`
|
|
344
351
|
*/
|
|
345
352
|
static serialize(value) {
|
|
346
|
-
return
|
|
353
|
+
return _settings.serialize(value);
|
|
347
354
|
}
|
|
348
355
|
/**
|
|
349
356
|
* Replace the function used for `serialize`.
|
|
350
357
|
*/
|
|
351
358
|
static setSerializeFn(fn) {
|
|
352
|
-
|
|
359
|
+
_settings.serialize = fn;
|
|
353
360
|
}
|
|
354
361
|
/**
|
|
355
362
|
* De-serialize a JSON string back to a JavaScript object.
|
|
@@ -361,16 +368,287 @@ var Sapling = class Sapling {
|
|
|
361
368
|
* @defaultValue `JSON.parse`
|
|
362
369
|
*/
|
|
363
370
|
static deserialize(value) {
|
|
364
|
-
return
|
|
371
|
+
return _settings.deserialize(value);
|
|
365
372
|
}
|
|
366
373
|
/**
|
|
367
374
|
* Replace the function used for `deserialize`
|
|
368
375
|
*/
|
|
369
376
|
static setDeserializeFn(fn) {
|
|
370
|
-
|
|
377
|
+
_settings.deserialize = fn;
|
|
378
|
+
}
|
|
379
|
+
static setOpenApiPath(path) {
|
|
380
|
+
_settings.doc.openApiPath = path;
|
|
381
|
+
}
|
|
382
|
+
static setSwaggerPath(path) {
|
|
383
|
+
_settings.doc.swaggerPath = path;
|
|
384
|
+
}
|
|
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
|
+
});
|
|
371
397
|
}
|
|
372
398
|
};
|
|
373
399
|
//#endregion
|
|
400
|
+
//#region src/annotation/route.ts
|
|
401
|
+
const _routeStore = /* @__PURE__ */ new WeakMap();
|
|
402
|
+
/**
|
|
403
|
+
* Custom annotation that will store all routes inside of a map,
|
|
404
|
+
* which can then be used to initialize all the routes to the router.
|
|
405
|
+
*/
|
|
406
|
+
function _Route({ method, path = "" }) {
|
|
407
|
+
return (target, propertyKey) => {
|
|
408
|
+
const ctor = target.constructor;
|
|
409
|
+
const list = _routeStore.get(ctor) ?? [];
|
|
410
|
+
list.push({
|
|
411
|
+
method,
|
|
412
|
+
path: path ?? "",
|
|
413
|
+
fnName: String(propertyKey)
|
|
414
|
+
});
|
|
415
|
+
_routeStore.set(ctor, list);
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Register GET route on the given path (default "") for the given controller.
|
|
420
|
+
*/
|
|
421
|
+
const GET = (path = "") => _Route({
|
|
422
|
+
method: "GET",
|
|
423
|
+
path
|
|
424
|
+
});
|
|
425
|
+
/**
|
|
426
|
+
* Register POST route on the given path (default "") for the given controller.
|
|
427
|
+
*/
|
|
428
|
+
const POST = (path = "") => _Route({
|
|
429
|
+
method: "POST",
|
|
430
|
+
path
|
|
431
|
+
});
|
|
432
|
+
/**
|
|
433
|
+
* Register PUT route on the given path (default "") for the given controller.
|
|
434
|
+
*/
|
|
435
|
+
const PUT = (path = "") => _Route({
|
|
436
|
+
method: "PUT",
|
|
437
|
+
path
|
|
438
|
+
});
|
|
439
|
+
/**
|
|
440
|
+
* Register DELETE route on the given path (default "") for the given controller.
|
|
441
|
+
*/
|
|
442
|
+
const DELETE = (path = "") => _Route({
|
|
443
|
+
method: "DELETE",
|
|
444
|
+
path
|
|
445
|
+
});
|
|
446
|
+
/**
|
|
447
|
+
* Register OPTIONS route on the given path (default "") for the given controller.
|
|
448
|
+
*/
|
|
449
|
+
const OPTIONS = (path = "") => _Route({
|
|
450
|
+
method: "OPTIONS",
|
|
451
|
+
path
|
|
452
|
+
});
|
|
453
|
+
/**
|
|
454
|
+
* Register PATCH route on the given path (default "") for the given controller.
|
|
455
|
+
*/
|
|
456
|
+
const PATCH = (path = "") => _Route({
|
|
457
|
+
method: "PATCH",
|
|
458
|
+
path
|
|
459
|
+
});
|
|
460
|
+
/**
|
|
461
|
+
* Register HEAD route on the given path (default "") for the given controller.
|
|
462
|
+
*/
|
|
463
|
+
const HEAD = (path = "") => _Route({
|
|
464
|
+
method: "HEAD",
|
|
465
|
+
path
|
|
466
|
+
});
|
|
467
|
+
/**
|
|
468
|
+
* Register a middleware route on the given path (default "") for the given controller.
|
|
469
|
+
*/
|
|
470
|
+
const Middleware = (path = "") => _Route({
|
|
471
|
+
method: "USE",
|
|
472
|
+
path
|
|
473
|
+
});
|
|
474
|
+
/**
|
|
475
|
+
* Given a class constructor, fetch all the routes attached.
|
|
476
|
+
*/
|
|
477
|
+
function _getRoutes(ctor) {
|
|
478
|
+
return _routeStore.get(ctor) ?? [];
|
|
479
|
+
}
|
|
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
|
|
517
|
+
//#region src/helper/openapi.ts
|
|
518
|
+
var OpenAPIGenerator = class {
|
|
519
|
+
constructor() {
|
|
520
|
+
this.controllers = /* @__PURE__ */ new Set();
|
|
521
|
+
this.config = {
|
|
522
|
+
title: "API",
|
|
523
|
+
version: "1.0.0"
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
setConfig(config) {
|
|
527
|
+
this.config = config;
|
|
528
|
+
}
|
|
529
|
+
registerController(controllerClass, prefix) {
|
|
530
|
+
this.controllers.add({
|
|
531
|
+
class: controllerClass,
|
|
532
|
+
prefix
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
generateSpec() {
|
|
536
|
+
const config = this.config;
|
|
537
|
+
const paths = {};
|
|
538
|
+
const tags = [];
|
|
539
|
+
for (const { class: controllerClass, prefix } of this.controllers) {
|
|
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
|
+
});
|
|
546
|
+
for (const route of routes) {
|
|
547
|
+
if (route.method === "USE") continue;
|
|
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}");
|
|
552
|
+
if (!paths[openApiPath]) paths[openApiPath] = {};
|
|
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 statusCode = String(resp.statusCode);
|
|
563
|
+
const existingResponse = responses[statusCode];
|
|
564
|
+
const existingSchema = existingResponse && "content" in existingResponse ? existingResponse.content?.["application/json"]?.schema : void 0;
|
|
565
|
+
const responseSchema = resp.schema ? this.toJsonSchema(resp.schema, "output") : statusCode === "200" ? existingSchema : void 0;
|
|
566
|
+
responses[statusCode] = {
|
|
567
|
+
...existingResponse,
|
|
568
|
+
description: resp.description ?? responseSchema?.description ?? existingResponse?.description ?? `Response ${resp.statusCode}`,
|
|
569
|
+
...responseSchema ? { content: { "application/json": { schema: responseSchema } } } : {}
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
const operation = {
|
|
573
|
+
responses,
|
|
574
|
+
summary: routeSchema?.summary,
|
|
575
|
+
description: routeSchema?.description,
|
|
576
|
+
tags: controllerSchema?.title ? [controllerSchema.title] : void 0
|
|
577
|
+
};
|
|
578
|
+
const parameters = [];
|
|
579
|
+
if (schemas?.requestParam) {
|
|
580
|
+
const paramSchema = this.toJsonSchema(schemas.requestParam, "input");
|
|
581
|
+
if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties)) {
|
|
582
|
+
const parameterSchema = schema;
|
|
583
|
+
parameters.push({
|
|
584
|
+
name,
|
|
585
|
+
in: "path",
|
|
586
|
+
required: true,
|
|
587
|
+
description: parameterSchema.description,
|
|
588
|
+
schema: parameterSchema
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (schemas?.requestQuery) {
|
|
593
|
+
const querySchema = this.toJsonSchema(schemas.requestQuery, "input");
|
|
594
|
+
if (querySchema.type === "object" && querySchema.properties) for (const [name, schema] of Object.entries(querySchema.properties)) {
|
|
595
|
+
const isRequired = Array.isArray(querySchema.required) && querySchema.required.includes(name);
|
|
596
|
+
const parameterSchema = schema;
|
|
597
|
+
parameters.push({
|
|
598
|
+
name,
|
|
599
|
+
in: "query",
|
|
600
|
+
required: isRequired,
|
|
601
|
+
description: parameterSchema.description,
|
|
602
|
+
schema: parameterSchema
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (parameters.length > 0) operation.parameters = parameters;
|
|
607
|
+
if (schemas?.requestBody) {
|
|
608
|
+
const requestSchema = this.toJsonSchema(schemas.requestBody, "input");
|
|
609
|
+
operation.requestBody = {
|
|
610
|
+
required: true,
|
|
611
|
+
description: requestSchema.description,
|
|
612
|
+
content: { "application/json": { schema: requestSchema } }
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
const method = route.method.toLowerCase();
|
|
616
|
+
paths[openApiPath][method] = operation;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return {
|
|
620
|
+
openapi: "3.0.0",
|
|
621
|
+
info: {
|
|
622
|
+
title: config.title,
|
|
623
|
+
version: config.version,
|
|
624
|
+
description: config.description
|
|
625
|
+
},
|
|
626
|
+
tags: tags.length > 0 ? tags : void 0,
|
|
627
|
+
paths
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
toJsonSchema(schema, direction = "output") {
|
|
631
|
+
try {
|
|
632
|
+
const jsonSchema = schema["~standard"].jsonSchema;
|
|
633
|
+
if (direction === "input" && jsonSchema.input) return jsonSchema.input({ target: "openapi-3.0" });
|
|
634
|
+
return jsonSchema.output({ target: "openapi-3.0" });
|
|
635
|
+
} catch (e) {
|
|
636
|
+
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`);
|
|
637
|
+
throw e;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
const openApiGenerator = new OpenAPIGenerator();
|
|
642
|
+
function _registerControllerClass(controllerClass, prefix) {
|
|
643
|
+
openApiGenerator.registerController(controllerClass, prefix);
|
|
644
|
+
}
|
|
645
|
+
function setOpenApiConfig(config) {
|
|
646
|
+
openApiGenerator.setConfig(config);
|
|
647
|
+
}
|
|
648
|
+
function generateOpenApiSpec() {
|
|
649
|
+
return openApiGenerator.generateSpec();
|
|
650
|
+
}
|
|
651
|
+
//#endregion
|
|
374
652
|
//#region src/types.ts
|
|
375
653
|
const methodResolve = {
|
|
376
654
|
GET: "get",
|
|
@@ -503,212 +781,58 @@ function _resolve(ctor) {
|
|
|
503
781
|
return _InjectableRegistry.get(ctor);
|
|
504
782
|
}
|
|
505
783
|
//#endregion
|
|
506
|
-
//#region src/annotation/
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
* ```ts
|
|
516
|
-
* const CREATE_BOOK_REQUEST_BODY_SCHEMA = z.object({
|
|
517
|
-
* name: z.string(),
|
|
518
|
-
* description: z.string().optional(),
|
|
519
|
-
* });
|
|
520
|
-
*
|
|
521
|
-
* ⠀@Controller({ prefix: "/api/book" })
|
|
522
|
-
* class BookController {
|
|
523
|
-
* ⠀@RequestBody(CREATE_BOOK_REQUEST_BODY_SCHEMA)
|
|
524
|
-
* ⠀@POST()
|
|
525
|
-
* public createBook(request: e.Request) {
|
|
526
|
-
* const { name, description } = request.body as unknown as z.infer<
|
|
527
|
-
* typeof CREATE_BOOK_REQUEST_BODY_SCHEMA
|
|
528
|
-
* >;
|
|
529
|
-
* }
|
|
530
|
-
* }
|
|
531
|
-
* ```
|
|
532
|
-
*/
|
|
784
|
+
//#region src/annotation/validator.ts
|
|
785
|
+
const _validatorSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
786
|
+
function ResponseBody(schema) {
|
|
787
|
+
return (target, propertyKey) => {
|
|
788
|
+
const ctor = target.constructor;
|
|
789
|
+
const fnName = String(propertyKey);
|
|
790
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "responseBody", schema, fnName);
|
|
791
|
+
};
|
|
792
|
+
}
|
|
533
793
|
function RequestBody(schema) {
|
|
534
794
|
return (target, propertyKey) => {
|
|
535
795
|
const ctor = target.constructor;
|
|
536
796
|
const fnName = String(propertyKey);
|
|
537
|
-
_setOnce(
|
|
797
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestBody", schema, fnName);
|
|
538
798
|
};
|
|
539
799
|
}
|
|
540
|
-
/**
|
|
541
|
-
* Apply to a route method to have `request.param` be parsed by `schema`.
|
|
542
|
-
*
|
|
543
|
-
* This annotation will parse `request.param` & then override `request.param`.
|
|
544
|
-
* You can then just simply cast `request.param` for your use
|
|
545
|
-
*
|
|
546
|
-
* @example
|
|
547
|
-
* ```ts
|
|
548
|
-
* const GET_BOOK_REQUEST_PARAM_SCHEMA = z.object({
|
|
549
|
-
* bookId: z.string(),
|
|
550
|
-
* });
|
|
551
|
-
*
|
|
552
|
-
* ⠀@Controller({ prefix: "/api/book" })
|
|
553
|
-
* class BookController {
|
|
554
|
-
* ⠀@RequestParam(GET_BOOK_REQUEST_PARAM_SCHEMA)
|
|
555
|
-
* ⠀@GET("/:bookId")
|
|
556
|
-
* public getBook(request: e.Request) {
|
|
557
|
-
* const { bookId } = request.param as unknown as z.infer<
|
|
558
|
-
* typeof GET_BOOK_REQUEST_PARAM_SCHEMA
|
|
559
|
-
* >;
|
|
560
|
-
* }
|
|
561
|
-
* }
|
|
562
|
-
* ```
|
|
563
|
-
*/
|
|
564
800
|
function RequestParam(schema) {
|
|
565
801
|
return (target, propertyKey) => {
|
|
566
802
|
const ctor = target.constructor;
|
|
567
803
|
const fnName = String(propertyKey);
|
|
568
|
-
_setOnce(
|
|
804
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestParam", schema, fnName);
|
|
569
805
|
};
|
|
570
806
|
}
|
|
571
|
-
/**
|
|
572
|
-
* Apply to a route method to have `request.query` be parsed by `schema`.
|
|
573
|
-
*
|
|
574
|
-
* This annotation will parse `request.query` & then override `request.query`.
|
|
575
|
-
* You can then just simply cast `request.query` for your use
|
|
576
|
-
*
|
|
577
|
-
* @example
|
|
578
|
-
* ```ts
|
|
579
|
-
* const LIST_BOOKS_REQUEST_QUERY_SCHEMA = z.object({
|
|
580
|
-
* sort: z.enum(["name", "createdAt"]).optional(),
|
|
581
|
-
* q: z.string().optional(),
|
|
582
|
-
* });
|
|
583
|
-
*
|
|
584
|
-
* ⠀@Controller({ prefix: "/api/book" })
|
|
585
|
-
* class BookController {
|
|
586
|
-
* ⠀@RequestQuery(LIST_BOOKS_REQUEST_QUERY_SCHEMA)
|
|
587
|
-
* ⠀@GET()
|
|
588
|
-
* public listBooks(request: e.Request) {
|
|
589
|
-
* const { sort, q } = request.query as unknown as z.infer<
|
|
590
|
-
* typeof LIST_BOOKS_REQUEST_QUERY_SCHEMA
|
|
591
|
-
* >;
|
|
592
|
-
* }
|
|
593
|
-
* }
|
|
594
|
-
* ```
|
|
595
|
-
*/
|
|
596
807
|
function RequestQuery(schema) {
|
|
597
808
|
return (target, propertyKey) => {
|
|
598
809
|
const ctor = target.constructor;
|
|
599
810
|
const fnName = String(propertyKey);
|
|
600
|
-
_setOnce(
|
|
811
|
+
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestQuery", schema, fnName);
|
|
601
812
|
};
|
|
602
813
|
}
|
|
603
|
-
function
|
|
604
|
-
const byFn = (
|
|
605
|
-
const fn = _requestSchemaStore.get(ctor);
|
|
606
|
-
if (fn) return fn;
|
|
607
|
-
const newFn = /* @__PURE__ */ new Map();
|
|
608
|
-
_requestSchemaStore.set(ctor, newFn);
|
|
609
|
-
return newFn;
|
|
610
|
-
})();
|
|
814
|
+
function _getOrCreateSchemaDefinition(ctor, fnName) {
|
|
815
|
+
const byFn = _getOrCreateMap(_validatorSchemaStore, ctor);
|
|
611
816
|
const existing = byFn.get(fnName);
|
|
612
817
|
if (existing) return existing;
|
|
613
818
|
const created = {};
|
|
614
819
|
byFn.set(fnName, created);
|
|
615
820
|
return created;
|
|
616
821
|
}
|
|
617
|
-
function _setOnce(def, key, schema, fnName) {
|
|
618
|
-
if (def[key]) throw new Error(`Duplicate request schema for "${String(key)}" on method "${fnName}"`);
|
|
619
|
-
def[key] = schema;
|
|
620
|
-
}
|
|
621
|
-
function _getRequestSchemas(ctor, fnName) {
|
|
622
|
-
return _requestSchemaStore.get(ctor)?.get(fnName);
|
|
623
|
-
}
|
|
624
822
|
async function _parseOrThrow(schema, input, kind) {
|
|
625
823
|
const result = await schema["~standard"].validate(input);
|
|
626
824
|
if (result.issues) {
|
|
627
|
-
console.debug(`Failed to parse
|
|
825
|
+
console.debug(`Failed to parse ${schema["~standard"].vendor} schema\nissues: ${result.issues}`);
|
|
628
826
|
throw new ParserError(kind, result.issues, schema["~standard"].vendor);
|
|
629
827
|
}
|
|
630
828
|
return result.value;
|
|
631
829
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
const _routeStore = /* @__PURE__ */ new WeakMap();
|
|
635
|
-
/**
|
|
636
|
-
* Custom annotation that will store all routes inside of a map,
|
|
637
|
-
* which can then be used to initialize all the routes to the router.
|
|
638
|
-
*/
|
|
639
|
-
function _Route({ method, path = "" }) {
|
|
640
|
-
return (target, propertyKey) => {
|
|
641
|
-
const ctor = target.constructor;
|
|
642
|
-
const list = _routeStore.get(ctor) ?? [];
|
|
643
|
-
list.push({
|
|
644
|
-
method,
|
|
645
|
-
path: path ?? "",
|
|
646
|
-
fnName: String(propertyKey)
|
|
647
|
-
});
|
|
648
|
-
_routeStore.set(ctor, list);
|
|
649
|
-
};
|
|
830
|
+
function _getValidatorSchema(ctor, fnName) {
|
|
831
|
+
return _validatorSchemaStore.get(ctor)?.get(fnName);
|
|
650
832
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
const GET = (path = "") => _Route({
|
|
655
|
-
method: "GET",
|
|
656
|
-
path
|
|
657
|
-
});
|
|
658
|
-
/**
|
|
659
|
-
* Register POST route on the given path (default "") for the given controller.
|
|
660
|
-
*/
|
|
661
|
-
const POST = (path = "") => _Route({
|
|
662
|
-
method: "POST",
|
|
663
|
-
path
|
|
664
|
-
});
|
|
665
|
-
/**
|
|
666
|
-
* Register PUT route on the given path (default "") for the given controller.
|
|
667
|
-
*/
|
|
668
|
-
const PUT = (path = "") => _Route({
|
|
669
|
-
method: "PUT",
|
|
670
|
-
path
|
|
671
|
-
});
|
|
672
|
-
/**
|
|
673
|
-
* Register DELETE route on the given path (default "") for the given controller.
|
|
674
|
-
*/
|
|
675
|
-
const DELETE = (path = "") => _Route({
|
|
676
|
-
method: "DELETE",
|
|
677
|
-
path
|
|
678
|
-
});
|
|
679
|
-
/**
|
|
680
|
-
* Register OPTIONS route on the given path (default "") for the given controller.
|
|
681
|
-
*/
|
|
682
|
-
const OPTIONS = (path = "") => _Route({
|
|
683
|
-
method: "OPTIONS",
|
|
684
|
-
path
|
|
685
|
-
});
|
|
686
|
-
/**
|
|
687
|
-
* Register PATCH route on the given path (default "") for the given controller.
|
|
688
|
-
*/
|
|
689
|
-
const PATCH = (path = "") => _Route({
|
|
690
|
-
method: "PATCH",
|
|
691
|
-
path
|
|
692
|
-
});
|
|
693
|
-
/**
|
|
694
|
-
* Register HEAD route on the given path (default "") for the given controller.
|
|
695
|
-
*/
|
|
696
|
-
const HEAD = (path = "") => _Route({
|
|
697
|
-
method: "HEAD",
|
|
698
|
-
path
|
|
699
|
-
});
|
|
700
|
-
/**
|
|
701
|
-
* Register a middleware route on the given path (default "") for the given controller.
|
|
702
|
-
*/
|
|
703
|
-
const Middleware = (path = "") => _Route({
|
|
704
|
-
method: "USE",
|
|
705
|
-
path
|
|
706
|
-
});
|
|
707
|
-
/**
|
|
708
|
-
* Given a class constructor, fetch all the routes attached.
|
|
709
|
-
*/
|
|
710
|
-
function _getRoutes(ctor) {
|
|
711
|
-
return _routeStore.get(ctor) ?? [];
|
|
833
|
+
function _setOnce(def, key, schema, fnName) {
|
|
834
|
+
if (def[key]) throw new Error(`Duplicate schema for "${String(key)}" on method "${fnName}"`);
|
|
835
|
+
def[key] = schema;
|
|
712
836
|
}
|
|
713
837
|
//#endregion
|
|
714
838
|
//#region src/annotation/controller.ts
|
|
@@ -722,6 +846,7 @@ const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
|
|
|
722
846
|
function Controller({ prefix = "", deps = [] } = {}) {
|
|
723
847
|
return (target) => {
|
|
724
848
|
const targetClass = target;
|
|
849
|
+
_registerControllerClass(target, prefix);
|
|
725
850
|
const router = (0, express.Router)();
|
|
726
851
|
const routes = _getRoutes(target);
|
|
727
852
|
const usedRoutes = /* @__PURE__ */ new Set();
|
|
@@ -764,12 +889,12 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
|
|
|
764
889
|
return;
|
|
765
890
|
}
|
|
766
891
|
router[methodName](fp, async (request, response, next) => {
|
|
767
|
-
const schemas =
|
|
892
|
+
const schemas = _getValidatorSchema(target, fnName);
|
|
768
893
|
if (schemas) {
|
|
769
|
-
if (schemas.
|
|
770
|
-
if (schemas.
|
|
771
|
-
if (schemas.
|
|
772
|
-
const parsedQuery = await _parseOrThrow(schemas.
|
|
894
|
+
if (schemas.requestBody) request.body = await _parseOrThrow(schemas.requestBody, request.body, "reqbody");
|
|
895
|
+
if (schemas.requestParam) request.params = await _parseOrThrow(schemas.requestParam, request.params, "reqparams");
|
|
896
|
+
if (schemas.requestQuery) {
|
|
897
|
+
const parsedQuery = await _parseOrThrow(schemas.requestQuery, request.query, "reqquery");
|
|
773
898
|
Object.defineProperty(request, "query", {
|
|
774
899
|
value: parsedQuery,
|
|
775
900
|
writable: true,
|
|
@@ -779,7 +904,8 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
|
|
|
779
904
|
}
|
|
780
905
|
const result = await fn.bind(controllerInstance)(request, response, next);
|
|
781
906
|
if (result instanceof ResponseEntity) {
|
|
782
|
-
|
|
907
|
+
const body = schemas && schemas.responseBody ? await _parseOrThrow(schemas.responseBody, result.getBody(), "resbody") : result.getBody();
|
|
908
|
+
response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(body));
|
|
783
909
|
return;
|
|
784
910
|
}
|
|
785
911
|
if (result instanceof RedirectView) {
|
|
@@ -813,7 +939,7 @@ function __decorate(decorators, target, key, desc) {
|
|
|
813
939
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
814
940
|
}
|
|
815
941
|
//#endregion
|
|
816
|
-
//#region src/middleware/default/base.ts
|
|
942
|
+
//#region src/middleware/default/error/base.ts
|
|
817
943
|
let DefaultBaseErrorMiddleware = class DefaultBaseErrorMiddleware {
|
|
818
944
|
handle(err, _request, _response, _next) {
|
|
819
945
|
console.error("[Error]", err);
|
|
@@ -823,7 +949,17 @@ let DefaultBaseErrorMiddleware = class DefaultBaseErrorMiddleware {
|
|
|
823
949
|
__decorate([Middleware()], DefaultBaseErrorMiddleware.prototype, "handle", null);
|
|
824
950
|
DefaultBaseErrorMiddleware = __decorate([MiddlewareClass()], DefaultBaseErrorMiddleware);
|
|
825
951
|
//#endregion
|
|
826
|
-
//#region src/middleware/default/
|
|
952
|
+
//#region src/middleware/default/error/parse.ts
|
|
953
|
+
let DefaultParserErrorMiddleware = class DefaultParserErrorMiddleware {
|
|
954
|
+
handle(err, _request, _response, next) {
|
|
955
|
+
if (err instanceof ParserError) return ResponseEntity.status(err.status).body({ message: err.message });
|
|
956
|
+
next(err);
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
__decorate([Middleware()], DefaultParserErrorMiddleware.prototype, "handle", null);
|
|
960
|
+
DefaultParserErrorMiddleware = __decorate([MiddlewareClass()], DefaultParserErrorMiddleware);
|
|
961
|
+
//#endregion
|
|
962
|
+
//#region src/middleware/default/error/responsestatus.ts
|
|
827
963
|
let DefaultResponseStatusErrorMiddleware = class DefaultResponseStatusErrorMiddleware {
|
|
828
964
|
handle(err, _request, _response, next) {
|
|
829
965
|
if (err instanceof ResponseStatusError) return ResponseEntity.status(err.status).body({ message: err.message });
|
|
@@ -833,7 +969,43 @@ let DefaultResponseStatusErrorMiddleware = class DefaultResponseStatusErrorMiddl
|
|
|
833
969
|
__decorate([Middleware()], DefaultResponseStatusErrorMiddleware.prototype, "handle", null);
|
|
834
970
|
DefaultResponseStatusErrorMiddleware = __decorate([MiddlewareClass()], DefaultResponseStatusErrorMiddleware);
|
|
835
971
|
//#endregion
|
|
972
|
+
//#region src/middleware/default/openapi/index.ts
|
|
973
|
+
let DefaultOpenApiMiddleware = class DefaultOpenApiMiddleware {
|
|
974
|
+
handle(_request, _response, _next) {
|
|
975
|
+
return ResponseEntity.ok().body(generateOpenApiSpec());
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
__decorate([GET(_settings.doc.openApiPath)], DefaultOpenApiMiddleware.prototype, "handle", null);
|
|
979
|
+
DefaultOpenApiMiddleware = __decorate([MiddlewareClass()], DefaultOpenApiMiddleware);
|
|
980
|
+
//#endregion
|
|
981
|
+
//#region src/middleware/default/swagger/index.ts
|
|
982
|
+
let Serve = class Serve {
|
|
983
|
+
constructor() {
|
|
984
|
+
this.handlers = swagger_ui_express.default.serve;
|
|
985
|
+
}
|
|
986
|
+
handle(request, response, next) {
|
|
987
|
+
return Sapling.chainHandlers(this.handlers, request, response, next);
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
__decorate([Middleware(_settings.doc.swaggerPath)], Serve.prototype, "handle", null);
|
|
991
|
+
Serve = __decorate([MiddlewareClass()], Serve);
|
|
992
|
+
let Setup = class Setup {
|
|
993
|
+
constructor() {
|
|
994
|
+
this.handler = swagger_ui_express.default.setup(null, { swaggerOptions: { url: _settings.doc.openApiPath } });
|
|
995
|
+
}
|
|
996
|
+
handle(request, response, next) {
|
|
997
|
+
return this.handler(request, response, next);
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
__decorate([Middleware(_settings.doc.swaggerPath)], Setup.prototype, "handle", null);
|
|
1001
|
+
Setup = __decorate([MiddlewareClass()], Setup);
|
|
1002
|
+
const DefaultSwaggerMiddleware = {
|
|
1003
|
+
Serve,
|
|
1004
|
+
Setup
|
|
1005
|
+
};
|
|
1006
|
+
//#endregion
|
|
836
1007
|
exports.Controller = Controller;
|
|
1008
|
+
exports.ControllerSchema = ControllerSchema;
|
|
837
1009
|
exports.DELETE = DELETE;
|
|
838
1010
|
Object.defineProperty(exports, "DefaultBaseErrorMiddleware", {
|
|
839
1011
|
enumerable: true,
|
|
@@ -841,12 +1013,25 @@ Object.defineProperty(exports, "DefaultBaseErrorMiddleware", {
|
|
|
841
1013
|
return DefaultBaseErrorMiddleware;
|
|
842
1014
|
}
|
|
843
1015
|
});
|
|
1016
|
+
Object.defineProperty(exports, "DefaultOpenApiMiddleware", {
|
|
1017
|
+
enumerable: true,
|
|
1018
|
+
get: function() {
|
|
1019
|
+
return DefaultOpenApiMiddleware;
|
|
1020
|
+
}
|
|
1021
|
+
});
|
|
1022
|
+
Object.defineProperty(exports, "DefaultParserErrorMiddleware", {
|
|
1023
|
+
enumerable: true,
|
|
1024
|
+
get: function() {
|
|
1025
|
+
return DefaultParserErrorMiddleware;
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
844
1028
|
Object.defineProperty(exports, "DefaultResponseStatusErrorMiddleware", {
|
|
845
1029
|
enumerable: true,
|
|
846
1030
|
get: function() {
|
|
847
1031
|
return DefaultResponseStatusErrorMiddleware;
|
|
848
1032
|
}
|
|
849
1033
|
});
|
|
1034
|
+
exports.DefaultSwaggerMiddleware = DefaultSwaggerMiddleware;
|
|
850
1035
|
exports.GET = GET;
|
|
851
1036
|
exports.HEAD = HEAD;
|
|
852
1037
|
exports.Html404ErrorPage = Html404ErrorPage;
|
|
@@ -863,16 +1048,29 @@ exports.RedirectView = RedirectView;
|
|
|
863
1048
|
exports.RequestBody = RequestBody;
|
|
864
1049
|
exports.RequestParam = RequestParam;
|
|
865
1050
|
exports.RequestQuery = RequestQuery;
|
|
1051
|
+
exports.ResponseBody = ResponseBody;
|
|
866
1052
|
exports.ResponseEntity = ResponseEntity;
|
|
867
1053
|
exports.ResponseEntityBuilder = ResponseEntityBuilder;
|
|
868
1054
|
exports.ResponseStatusError = ResponseStatusError;
|
|
1055
|
+
exports.RouteSchema = RouteSchema;
|
|
869
1056
|
exports.Sapling = Sapling;
|
|
870
1057
|
exports._ControllerRegistry = _ControllerRegistry;
|
|
871
1058
|
exports._InjectableDeps = _InjectableDeps;
|
|
872
1059
|
exports._InjectableRegistry = _InjectableRegistry;
|
|
873
1060
|
exports._Route = _Route;
|
|
874
|
-
exports.
|
|
1061
|
+
exports._getControllerSchema = _getControllerSchema;
|
|
1062
|
+
exports._getOrCreateSchemaDefinition = _getOrCreateSchemaDefinition;
|
|
1063
|
+
exports._getRouteSchema = _getRouteSchema;
|
|
875
1064
|
exports._getRoutes = _getRoutes;
|
|
1065
|
+
exports._getValidatorSchema = _getValidatorSchema;
|
|
876
1066
|
exports._parseOrThrow = _parseOrThrow;
|
|
1067
|
+
exports._registerControllerClass = _registerControllerClass;
|
|
877
1068
|
exports._resolve = _resolve;
|
|
1069
|
+
exports._setControllerSchema = _setControllerSchema;
|
|
1070
|
+
exports._setOnce = _setOnce;
|
|
1071
|
+
exports._setRouteSchema = _setRouteSchema;
|
|
1072
|
+
exports._settings = _settings;
|
|
1073
|
+
exports.generateOpenApiSpec = generateOpenApiSpec;
|
|
878
1074
|
exports.methodResolve = methodResolve;
|
|
1075
|
+
exports.openApiGenerator = openApiGenerator;
|
|
1076
|
+
exports.setOpenApiConfig = setOpenApiConfig;
|