@tahminator/sapling 2.0.5-beta.c70dc62b → 2.0.5-beta.e0403942
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 +161 -12
- package/dist/index.cjs +331 -166
- package/dist/index.d.cts +246 -98
- package/dist/index.d.mts +246 -98
- package/dist/index.mjs +324 -164
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -48,6 +48,7 @@ const Html404ErrorPage = (error) => `<!DOCTYPE html>
|
|
|
48
48
|
* You can either return `new RedirectView(url)` or `RedirectView.redirect(url)` inside of a controller method.
|
|
49
49
|
*/
|
|
50
50
|
var RedirectView = class RedirectView {
|
|
51
|
+
_url;
|
|
51
52
|
constructor(url) {
|
|
52
53
|
this._url = url;
|
|
53
54
|
}
|
|
@@ -143,8 +144,10 @@ let HttpStatus = /* @__PURE__ */ function(HttpStatus) {
|
|
|
143
144
|
* @typeParam T - the type of the response body
|
|
144
145
|
*/
|
|
145
146
|
var ResponseEntity = class {
|
|
147
|
+
_statusCode;
|
|
148
|
+
_headers = {};
|
|
149
|
+
_body;
|
|
146
150
|
constructor(body, headers = {}, statusCode = 200) {
|
|
147
|
-
this._headers = {};
|
|
148
151
|
this._body = body;
|
|
149
152
|
this._headers = headers;
|
|
150
153
|
this._statusCode = statusCode;
|
|
@@ -197,8 +200,9 @@ var ResponseEntity = class {
|
|
|
197
200
|
* ensuring type safety when constructing the response.
|
|
198
201
|
*/
|
|
199
202
|
var ResponseEntityBuilder = class {
|
|
203
|
+
_statusCode;
|
|
204
|
+
_headers = {};
|
|
200
205
|
constructor(statusCode) {
|
|
201
|
-
this._headers = {};
|
|
202
206
|
this._statusCode = statusCode;
|
|
203
207
|
}
|
|
204
208
|
/**
|
|
@@ -230,6 +234,7 @@ var ResponseEntityBuilder = class {
|
|
|
230
234
|
* @see {@link Sapling.loadResponseStatusErrorMiddleware}
|
|
231
235
|
*/
|
|
232
236
|
var ResponseStatusError = class ResponseStatusError extends Error {
|
|
237
|
+
status;
|
|
233
238
|
constructor(status, message) {
|
|
234
239
|
super(message ?? "Something went wrong.");
|
|
235
240
|
this.status = status;
|
|
@@ -241,26 +246,27 @@ var ResponseStatusError = class ResponseStatusError extends Error {
|
|
|
241
246
|
//#endregion
|
|
242
247
|
//#region src/helper/error/parse.ts
|
|
243
248
|
/**
|
|
244
|
-
* This error should be thrown when some data cannot be parsed by a given schema.
|
|
249
|
+
* This error should be thrown when some data cannot be parsed by a given Standard Schema compatible schema.
|
|
245
250
|
*/
|
|
246
251
|
var ParserError = class ParserError extends ResponseStatusError {
|
|
247
|
-
constructor(location, issues, vendor) {
|
|
248
|
-
super(400, ParserError.formatMessage(location, issues, vendor));
|
|
252
|
+
constructor(location, issues, vendor, functionName) {
|
|
253
|
+
super(400, ParserError.formatMessage(location, issues, vendor, functionName));
|
|
249
254
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
250
255
|
}
|
|
251
|
-
static formatMessage(location, issues, vendor) {
|
|
256
|
+
static formatMessage(location, issues, vendor, functionName) {
|
|
252
257
|
const formatted = issues.map((i) => {
|
|
253
258
|
const path = Array.isArray(i.path) ? i.path.map((seg) => typeof seg === "object" && seg ? String(seg.key) : String(seg)).join(".") : "";
|
|
254
259
|
return path ? `${path}: ${i.message}` : i.message;
|
|
255
260
|
}).join("; ");
|
|
256
|
-
return
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
return `Failed to parse ${this.getPrettyLocationString(location)} with ${vendor} on ${functionName}: ${formatted}`;
|
|
262
|
+
}
|
|
263
|
+
static getPrettyLocationString(location) {
|
|
264
|
+
switch (location) {
|
|
265
|
+
case "reqbody": return "request body";
|
|
266
|
+
case "reqparams": return "request params";
|
|
267
|
+
case "reqquery": return "request query";
|
|
268
|
+
case "resbody": return "response body";
|
|
269
|
+
}
|
|
264
270
|
}
|
|
265
271
|
};
|
|
266
272
|
//#endregion
|
|
@@ -270,7 +276,11 @@ const _settings = {
|
|
|
270
276
|
deserialize: JSON.parse,
|
|
271
277
|
doc: {
|
|
272
278
|
openApiPath: "/openapi.json",
|
|
273
|
-
swaggerPath: "/swagger.html"
|
|
279
|
+
swaggerPath: "/swagger.html",
|
|
280
|
+
metadata: {
|
|
281
|
+
title: "API",
|
|
282
|
+
version: "1.0.0"
|
|
283
|
+
}
|
|
274
284
|
}
|
|
275
285
|
};
|
|
276
286
|
/**
|
|
@@ -376,12 +386,59 @@ var Sapling = class Sapling {
|
|
|
376
386
|
static setDeserializeFn(fn) {
|
|
377
387
|
_settings.deserialize = fn;
|
|
378
388
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
static
|
|
383
|
-
|
|
384
|
-
|
|
389
|
+
/**
|
|
390
|
+
* Modify extra settings
|
|
391
|
+
*/
|
|
392
|
+
static Extras = {
|
|
393
|
+
/**
|
|
394
|
+
* Modify default settings applied to OpenAPI & Swagger
|
|
395
|
+
*/
|
|
396
|
+
swaggerAndOpenApi: {
|
|
397
|
+
/**
|
|
398
|
+
* Set base OpenAPI metadata values.
|
|
399
|
+
*
|
|
400
|
+
* @default { title: "API", version: "1.0.0" }
|
|
401
|
+
*/
|
|
402
|
+
setMetadata(metadata) {
|
|
403
|
+
_settings.doc.metadata = metadata;
|
|
404
|
+
},
|
|
405
|
+
/**
|
|
406
|
+
* change default endpoint that will serve OpenAPI spec.
|
|
407
|
+
* Swagger will also load this endpoint on load.
|
|
408
|
+
*
|
|
409
|
+
* @default `/openapi.json`
|
|
410
|
+
*/
|
|
411
|
+
setOpenApiPath(path) {
|
|
412
|
+
_settings.doc.openApiPath = path;
|
|
413
|
+
},
|
|
414
|
+
/**
|
|
415
|
+
* change Swagger endpoint.
|
|
416
|
+
*
|
|
417
|
+
* @default `/swagger.html`
|
|
418
|
+
*/
|
|
419
|
+
setSwaggerPath(path) {
|
|
420
|
+
_settings.doc.swaggerPath = path;
|
|
421
|
+
}
|
|
422
|
+
} };
|
|
423
|
+
/**
|
|
424
|
+
* This method can be used in a `@MiddlewareClass` to register any libraries
|
|
425
|
+
* that expect you to register multiple registers at once. An example is `swagger-ui-express`
|
|
426
|
+
*
|
|
427
|
+
* @example
|
|
428
|
+
* ```ts
|
|
429
|
+
* ⠀@MiddlewareClass()
|
|
430
|
+
* class Serve {
|
|
431
|
+
* // `swagger.serve` returns multiple Express handlers for all the assets and routes
|
|
432
|
+
* // that will be served
|
|
433
|
+
* private readonly handlers: RequestHandler[] = swagger.serve;
|
|
434
|
+
*
|
|
435
|
+
* ⠀@Middleware(_settings.doc.swaggerPath)
|
|
436
|
+
* handle(request: Request, response: Response, next: NextFunction) {
|
|
437
|
+
* return Sapling.chainHandlers(this.handlers, request, response, next);
|
|
438
|
+
* }
|
|
439
|
+
* }
|
|
440
|
+
* ```
|
|
441
|
+
*/
|
|
385
442
|
static chainHandlers(handlers, request, response, next, index = 0) {
|
|
386
443
|
if (index >= handlers.length) {
|
|
387
444
|
next();
|
|
@@ -478,18 +535,33 @@ function _getRoutes(ctor) {
|
|
|
478
535
|
return _routeStore.get(ctor) ?? [];
|
|
479
536
|
}
|
|
480
537
|
//#endregion
|
|
481
|
-
//#region src/
|
|
482
|
-
function
|
|
538
|
+
//#region src/annotation/schema.ts
|
|
539
|
+
function ControllerSchema(options) {
|
|
540
|
+
return (target) => {
|
|
541
|
+
_setControllerSchema(target, options);
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
function RouteSchema(options) {
|
|
545
|
+
return (target, propertyKey) => {
|
|
546
|
+
const ctor = target.constructor;
|
|
547
|
+
_setRouteSchema(ctor, String(propertyKey), options);
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
const _routeSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
551
|
+
const _controllerSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
552
|
+
function getOrCreateRouteSchemaStore(store, ctor) {
|
|
483
553
|
const existing = store.get(ctor);
|
|
484
554
|
if (existing) return existing;
|
|
485
555
|
const created = /* @__PURE__ */ new Map();
|
|
486
556
|
store.set(ctor, created);
|
|
487
557
|
return created;
|
|
488
558
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
559
|
+
function _setRouteSchema(ctor, fnName, options) {
|
|
560
|
+
getOrCreateRouteSchemaStore(_routeSchemaStore, ctor).set(fnName, options);
|
|
561
|
+
}
|
|
562
|
+
function _setControllerSchema(ctor, options) {
|
|
563
|
+
_controllerSchemaStore.set(ctor, options);
|
|
564
|
+
}
|
|
493
565
|
function _getRouteSchema(ctor, fnName) {
|
|
494
566
|
return _routeSchemaStore.get(ctor)?.get(fnName);
|
|
495
567
|
}
|
|
@@ -497,26 +569,78 @@ function _getControllerSchema(ctor) {
|
|
|
497
569
|
return _controllerSchemaStore.get(ctor);
|
|
498
570
|
}
|
|
499
571
|
//#endregion
|
|
572
|
+
//#region src/annotation/validator.ts
|
|
573
|
+
const _validatorSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
574
|
+
function ResponseBody(schema) {
|
|
575
|
+
return (target, propertyKey) => {
|
|
576
|
+
const ctor = target.constructor;
|
|
577
|
+
const fnName = String(propertyKey);
|
|
578
|
+
_saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "responseBody", schema, fnName);
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
function RequestBody(schema) {
|
|
582
|
+
return (target, propertyKey) => {
|
|
583
|
+
const ctor = target.constructor;
|
|
584
|
+
const fnName = String(propertyKey);
|
|
585
|
+
_saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "requestBody", schema, fnName);
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
function RequestParam(schema) {
|
|
589
|
+
return (target, propertyKey) => {
|
|
590
|
+
const ctor = target.constructor;
|
|
591
|
+
const fnName = String(propertyKey);
|
|
592
|
+
_saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "requestParam", schema, fnName);
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
function RequestQuery(schema) {
|
|
596
|
+
return (target, propertyKey) => {
|
|
597
|
+
const ctor = target.constructor;
|
|
598
|
+
const fnName = String(propertyKey);
|
|
599
|
+
_saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "requestQuery", schema, fnName);
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
function getOrCreateValidatorSchemaStore(store, ctor) {
|
|
603
|
+
const existing = store.get(ctor);
|
|
604
|
+
if (existing) return existing;
|
|
605
|
+
const created = /* @__PURE__ */ new Map();
|
|
606
|
+
store.set(ctor, created);
|
|
607
|
+
return created;
|
|
608
|
+
}
|
|
609
|
+
function _getOrCreateSchemaDefinition(ctor, fnName) {
|
|
610
|
+
const byFn = getOrCreateValidatorSchemaStore(_validatorSchemaStore, ctor);
|
|
611
|
+
const existing = byFn.get(fnName);
|
|
612
|
+
if (existing) return existing;
|
|
613
|
+
const created = {};
|
|
614
|
+
byFn.set(fnName, created);
|
|
615
|
+
return created;
|
|
616
|
+
}
|
|
617
|
+
async function _parseOrThrow(schema, input, location, fnName) {
|
|
618
|
+
const result = await schema["~standard"].validate(input);
|
|
619
|
+
if (result.issues) throw new ParserError(location, result.issues, schema["~standard"].vendor, fnName);
|
|
620
|
+
return result.value;
|
|
621
|
+
}
|
|
622
|
+
function _saveValidatorSchema(def, key, schema, fnName) {
|
|
623
|
+
if (def[key]) throw new Error(`Duplicate schema for "${String(key)}" on method "${fnName}"`);
|
|
624
|
+
def[key] = schema;
|
|
625
|
+
}
|
|
626
|
+
function _getValidatorSchema(ctor, fnName) {
|
|
627
|
+
return _validatorSchemaStore.get(ctor)?.get(fnName);
|
|
628
|
+
}
|
|
629
|
+
//#endregion
|
|
500
630
|
//#region src/helper/openapi.ts
|
|
501
631
|
var OpenAPIGenerator = class {
|
|
502
|
-
|
|
503
|
-
this.controllers = /* @__PURE__ */ new Set();
|
|
504
|
-
this.config = {
|
|
505
|
-
title: "API",
|
|
506
|
-
version: "1.0.0"
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
setConfig(config) {
|
|
510
|
-
this.config = config;
|
|
511
|
-
}
|
|
632
|
+
controllers = /* @__PURE__ */ new Set();
|
|
512
633
|
registerController(controllerClass, prefix) {
|
|
513
634
|
this.controllers.add({
|
|
514
635
|
class: controllerClass,
|
|
515
636
|
prefix
|
|
516
637
|
});
|
|
517
638
|
}
|
|
639
|
+
get metadata() {
|
|
640
|
+
return _settings.doc.metadata;
|
|
641
|
+
}
|
|
518
642
|
generateSpec() {
|
|
519
|
-
const
|
|
643
|
+
const metadata = this.metadata;
|
|
520
644
|
const paths = {};
|
|
521
645
|
const tags = [];
|
|
522
646
|
for (const { class: controllerClass, prefix } of this.controllers) {
|
|
@@ -531,50 +655,70 @@ var OpenAPIGenerator = class {
|
|
|
531
655
|
const schemas = _getValidatorSchema(controllerClass, route.fnName);
|
|
532
656
|
const routeSchema = _getRouteSchema(controllerClass, route.fnName);
|
|
533
657
|
if (route.path instanceof RegExp) throw new Error(`You have a route with a regex path of ${route.path.source}. This is not compatible with OpenAPI.`);
|
|
534
|
-
const openApiPath = prefix + route.path;
|
|
658
|
+
const openApiPath = (prefix + route.path).replace(/:([A-Za-z0-9_]+)/g, "{$1}");
|
|
535
659
|
if (!paths[openApiPath]) paths[openApiPath] = {};
|
|
536
660
|
const responses = {};
|
|
537
|
-
if (schemas?.responseBody)
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
661
|
+
if (schemas?.responseBody) {
|
|
662
|
+
const responseSchema = this.toJsonSchema(schemas.responseBody, "output");
|
|
663
|
+
responses["200"] = {
|
|
664
|
+
description: responseSchema.description ?? "Successful response",
|
|
665
|
+
content: { "application/json": { schema: responseSchema } }
|
|
666
|
+
};
|
|
667
|
+
} else responses["200"] = { description: "Successful response" };
|
|
668
|
+
if (routeSchema?.responses) for (const resp of routeSchema.responses) {
|
|
669
|
+
const statusCode = String(resp.statusCode);
|
|
670
|
+
const existingResponse = responses[statusCode];
|
|
671
|
+
const existingSchema = existingResponse && "content" in existingResponse ? existingResponse.content?.["application/json"]?.schema : void 0;
|
|
672
|
+
const responseSchema = resp.schema ? this.toJsonSchema(resp.schema, "output") : statusCode === "200" ? existingSchema : void 0;
|
|
673
|
+
responses[statusCode] = {
|
|
674
|
+
...existingResponse,
|
|
675
|
+
description: resp.description ?? responseSchema?.description ?? existingResponse?.description ?? `Response ${resp.statusCode}`,
|
|
676
|
+
...responseSchema ? { content: { "application/json": { schema: responseSchema } } } : {}
|
|
677
|
+
};
|
|
678
|
+
}
|
|
546
679
|
const operation = {
|
|
547
680
|
responses,
|
|
681
|
+
summary: routeSchema?.summary,
|
|
548
682
|
description: routeSchema?.description,
|
|
549
683
|
tags: controllerSchema?.title ? [controllerSchema.title] : void 0
|
|
550
684
|
};
|
|
551
685
|
const parameters = [];
|
|
552
686
|
if (schemas?.requestParam) {
|
|
553
|
-
const paramSchema = this.toJsonSchema(schemas.requestParam);
|
|
554
|
-
if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties))
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
687
|
+
const paramSchema = this.toJsonSchema(schemas.requestParam, "input");
|
|
688
|
+
if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties)) {
|
|
689
|
+
const parameterSchema = schema;
|
|
690
|
+
parameters.push({
|
|
691
|
+
name,
|
|
692
|
+
in: "path",
|
|
693
|
+
required: true,
|
|
694
|
+
description: parameterSchema.description,
|
|
695
|
+
schema: parameterSchema
|
|
696
|
+
});
|
|
697
|
+
}
|
|
560
698
|
}
|
|
561
699
|
if (schemas?.requestQuery) {
|
|
562
|
-
const querySchema = this.toJsonSchema(schemas.requestQuery);
|
|
700
|
+
const querySchema = this.toJsonSchema(schemas.requestQuery, "input");
|
|
563
701
|
if (querySchema.type === "object" && querySchema.properties) for (const [name, schema] of Object.entries(querySchema.properties)) {
|
|
564
702
|
const isRequired = Array.isArray(querySchema.required) && querySchema.required.includes(name);
|
|
703
|
+
const parameterSchema = schema;
|
|
565
704
|
parameters.push({
|
|
566
705
|
name,
|
|
567
706
|
in: "query",
|
|
568
707
|
required: isRequired,
|
|
569
|
-
|
|
708
|
+
description: parameterSchema.description,
|
|
709
|
+
schema: parameterSchema
|
|
570
710
|
});
|
|
571
711
|
}
|
|
572
712
|
}
|
|
573
713
|
if (parameters.length > 0) operation.parameters = parameters;
|
|
574
|
-
if (schemas?.requestBody)
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
714
|
+
if (schemas?.requestBody) {
|
|
715
|
+
const requestSchema = this.toJsonSchema(schemas.requestBody, "input");
|
|
716
|
+
operation.requestBody = {
|
|
717
|
+
required: true,
|
|
718
|
+
description: requestSchema.description,
|
|
719
|
+
content: { "application/json": { schema: requestSchema } }
|
|
720
|
+
};
|
|
721
|
+
}
|
|
578
722
|
const method = route.method.toLowerCase();
|
|
579
723
|
paths[openApiPath][method] = operation;
|
|
580
724
|
}
|
|
@@ -582,30 +726,28 @@ var OpenAPIGenerator = class {
|
|
|
582
726
|
return {
|
|
583
727
|
openapi: "3.0.0",
|
|
584
728
|
info: {
|
|
585
|
-
title:
|
|
586
|
-
version:
|
|
587
|
-
description:
|
|
729
|
+
title: metadata.title,
|
|
730
|
+
version: metadata.version,
|
|
731
|
+
description: metadata.description
|
|
588
732
|
},
|
|
589
733
|
tags: tags.length > 0 ? tags : void 0,
|
|
590
734
|
paths
|
|
591
735
|
};
|
|
592
736
|
}
|
|
593
|
-
toJsonSchema(schema) {
|
|
737
|
+
toJsonSchema(schema, direction = "output") {
|
|
594
738
|
try {
|
|
595
|
-
|
|
739
|
+
const jsonSchema = schema["~standard"].jsonSchema;
|
|
740
|
+
return direction === "input" ? jsonSchema.input({ target: "openapi-3.0" }) : jsonSchema.output({ target: "openapi-3.0" });
|
|
596
741
|
} catch (e) {
|
|
597
|
-
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
|
|
742
|
+
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`, { cause: e });
|
|
598
743
|
throw e;
|
|
599
744
|
}
|
|
600
745
|
}
|
|
601
746
|
};
|
|
602
747
|
const openApiGenerator = new OpenAPIGenerator();
|
|
603
|
-
function
|
|
748
|
+
function _registerController(controllerClass, prefix) {
|
|
604
749
|
openApiGenerator.registerController(controllerClass, prefix);
|
|
605
750
|
}
|
|
606
|
-
function setOpenApiConfig(config) {
|
|
607
|
-
openApiGenerator.setConfig(config);
|
|
608
|
-
}
|
|
609
751
|
function generateOpenApiSpec() {
|
|
610
752
|
return openApiGenerator.generateSpec();
|
|
611
753
|
}
|
|
@@ -742,60 +884,6 @@ function _resolve(ctor) {
|
|
|
742
884
|
return _InjectableRegistry.get(ctor);
|
|
743
885
|
}
|
|
744
886
|
//#endregion
|
|
745
|
-
//#region src/annotation/validator.ts
|
|
746
|
-
const _validatorSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
747
|
-
function ResponseBody(schema) {
|
|
748
|
-
return (target, propertyKey) => {
|
|
749
|
-
const ctor = target.constructor;
|
|
750
|
-
const fnName = String(propertyKey);
|
|
751
|
-
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "responseBody", schema, fnName);
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
|
-
function RequestBody(schema) {
|
|
755
|
-
return (target, propertyKey) => {
|
|
756
|
-
const ctor = target.constructor;
|
|
757
|
-
const fnName = String(propertyKey);
|
|
758
|
-
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestBody", schema, fnName);
|
|
759
|
-
};
|
|
760
|
-
}
|
|
761
|
-
function RequestParam(schema) {
|
|
762
|
-
return (target, propertyKey) => {
|
|
763
|
-
const ctor = target.constructor;
|
|
764
|
-
const fnName = String(propertyKey);
|
|
765
|
-
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestParam", schema, fnName);
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
function RequestQuery(schema) {
|
|
769
|
-
return (target, propertyKey) => {
|
|
770
|
-
const ctor = target.constructor;
|
|
771
|
-
const fnName = String(propertyKey);
|
|
772
|
-
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestQuery", schema, fnName);
|
|
773
|
-
};
|
|
774
|
-
}
|
|
775
|
-
function _getOrCreateSchemaDefinition(ctor, fnName) {
|
|
776
|
-
const byFn = _getOrCreateMap(_validatorSchemaStore, ctor);
|
|
777
|
-
const existing = byFn.get(fnName);
|
|
778
|
-
if (existing) return existing;
|
|
779
|
-
const created = {};
|
|
780
|
-
byFn.set(fnName, created);
|
|
781
|
-
return created;
|
|
782
|
-
}
|
|
783
|
-
async function _parseOrThrow(schema, input, kind) {
|
|
784
|
-
const result = await schema["~standard"].validate(input);
|
|
785
|
-
if (result.issues) {
|
|
786
|
-
console.debug(`Failed to parse a schema`);
|
|
787
|
-
throw new ParserError(kind, result.issues, schema["~standard"].vendor);
|
|
788
|
-
}
|
|
789
|
-
return result.value;
|
|
790
|
-
}
|
|
791
|
-
function _getValidatorSchema(ctor, fnName) {
|
|
792
|
-
return _validatorSchemaStore.get(ctor)?.get(fnName);
|
|
793
|
-
}
|
|
794
|
-
function _setOnce(def, key, schema, fnName) {
|
|
795
|
-
if (def[key]) throw new Error(`Duplicate schema for "${String(key)}" on method "${fnName}"`);
|
|
796
|
-
def[key] = schema;
|
|
797
|
-
}
|
|
798
|
-
//#endregion
|
|
799
887
|
//#region src/annotation/controller.ts
|
|
800
888
|
const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
|
|
801
889
|
/**
|
|
@@ -807,7 +895,7 @@ const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
|
|
|
807
895
|
function Controller({ prefix = "", deps = [] } = {}) {
|
|
808
896
|
return (target) => {
|
|
809
897
|
const targetClass = target;
|
|
810
|
-
|
|
898
|
+
_registerController(target, prefix);
|
|
811
899
|
const router = (0, express.Router)();
|
|
812
900
|
const routes = _getRoutes(target);
|
|
813
901
|
const usedRoutes = /* @__PURE__ */ new Set();
|
|
@@ -832,15 +920,20 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
|
|
|
832
920
|
if (method === "USE" && fn.length >= 4) {
|
|
833
921
|
const middlewareFn = async (err, request, response, next) => {
|
|
834
922
|
try {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
923
|
+
await validate({
|
|
924
|
+
target,
|
|
925
|
+
fnName,
|
|
926
|
+
request
|
|
927
|
+
});
|
|
928
|
+
await handleResult({
|
|
929
|
+
result: fn.bind(controllerInstance)(err, request, response, next),
|
|
930
|
+
response,
|
|
931
|
+
target,
|
|
932
|
+
fnName,
|
|
933
|
+
method,
|
|
934
|
+
path: path instanceof RegExp ? path.source : fp,
|
|
935
|
+
isErrorMiddleware: true
|
|
936
|
+
});
|
|
844
937
|
} catch (e) {
|
|
845
938
|
console.error(e);
|
|
846
939
|
next(e);
|
|
@@ -850,35 +943,52 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
|
|
|
850
943
|
return;
|
|
851
944
|
}
|
|
852
945
|
router[methodName](fp, async (request, response, next) => {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
}
|
|
866
|
-
const result = await fn.bind(controllerInstance)(request, response, next);
|
|
867
|
-
if (result instanceof ResponseEntity) {
|
|
868
|
-
const body = schemas && schemas.responseBody ? await _parseOrThrow(schemas.responseBody, result.getBody(), "resbody") : result.getBody();
|
|
869
|
-
response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(body));
|
|
870
|
-
return;
|
|
871
|
-
}
|
|
872
|
-
if (result instanceof RedirectView) {
|
|
873
|
-
response.redirect(result.getUrl());
|
|
874
|
-
return;
|
|
875
|
-
}
|
|
876
|
-
if (method !== "USE" && !response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${methodName.toUpperCase()} ${path instanceof RegExp ? path.source : fp}`));
|
|
946
|
+
await validate({
|
|
947
|
+
target,
|
|
948
|
+
fnName,
|
|
949
|
+
request
|
|
950
|
+
});
|
|
951
|
+
await handleResult({
|
|
952
|
+
result: await fn.bind(controllerInstance)(request, response, next),
|
|
953
|
+
response,
|
|
954
|
+
target,
|
|
955
|
+
fnName,
|
|
956
|
+
method,
|
|
957
|
+
path: path instanceof RegExp ? path.source : fp
|
|
958
|
+
});
|
|
877
959
|
});
|
|
878
960
|
}
|
|
879
961
|
_ControllerRegistry.set(targetClass, router);
|
|
880
962
|
};
|
|
881
963
|
}
|
|
964
|
+
async function handleResult({ result, target, fnName, response, method, path, isErrorMiddleware = false }) {
|
|
965
|
+
const schemas = _getValidatorSchema(target, fnName);
|
|
966
|
+
if (result instanceof ResponseEntity) {
|
|
967
|
+
const body = schemas && schemas.responseBody ? await _parseOrThrow(schemas.responseBody, result.getBody(), "resbody", fnName) : result.getBody();
|
|
968
|
+
response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(body));
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
if (result instanceof RedirectView) {
|
|
972
|
+
response.redirect(result.getUrl());
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
if (!isErrorMiddleware && method !== "USE" && !response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${method} ${path}`));
|
|
976
|
+
}
|
|
977
|
+
async function validate({ target, fnName, request }) {
|
|
978
|
+
const schemas = _getValidatorSchema(target, fnName);
|
|
979
|
+
if (schemas) {
|
|
980
|
+
if (schemas.requestBody) request.body = await _parseOrThrow(schemas.requestBody, request.body, "reqbody", fnName);
|
|
981
|
+
if (schemas.requestParam) request.params = await _parseOrThrow(schemas.requestParam, request.params, "reqparams", fnName);
|
|
982
|
+
if (schemas.requestQuery) {
|
|
983
|
+
const parsedQuery = await _parseOrThrow(schemas.requestQuery, request.query, "reqquery", fnName);
|
|
984
|
+
Object.defineProperty(request, "query", {
|
|
985
|
+
value: parsedQuery,
|
|
986
|
+
writable: true,
|
|
987
|
+
configurable: true
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
882
992
|
//#endregion
|
|
883
993
|
//#region src/annotation/middleware.ts
|
|
884
994
|
/**
|
|
@@ -913,7 +1023,10 @@ DefaultBaseErrorMiddleware = __decorate([MiddlewareClass()], DefaultBaseErrorMid
|
|
|
913
1023
|
//#region src/middleware/default/error/parse.ts
|
|
914
1024
|
let DefaultParserErrorMiddleware = class DefaultParserErrorMiddleware {
|
|
915
1025
|
handle(err, _request, _response, next) {
|
|
916
|
-
if (err instanceof ParserError)
|
|
1026
|
+
if (err instanceof ParserError) {
|
|
1027
|
+
console.warn(err);
|
|
1028
|
+
return ResponseEntity.status(err.status).body({ message: err.message });
|
|
1029
|
+
}
|
|
917
1030
|
next(err);
|
|
918
1031
|
}
|
|
919
1032
|
};
|
|
@@ -940,17 +1053,48 @@ __decorate([GET(_settings.doc.openApiPath)], DefaultOpenApiMiddleware.prototype,
|
|
|
940
1053
|
DefaultOpenApiMiddleware = __decorate([MiddlewareClass()], DefaultOpenApiMiddleware);
|
|
941
1054
|
//#endregion
|
|
942
1055
|
//#region src/middleware/default/swagger/index.ts
|
|
1056
|
+
/**
|
|
1057
|
+
* Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
|
|
1058
|
+
*
|
|
1059
|
+
* Configure any middleware-specific settings with `Sapling.Extras.swaggerAndOpenApi`
|
|
1060
|
+
*
|
|
1061
|
+
* You must register `DefaultSwaggerMiddleware.Serve` & `DefaultSwaggerMiddleware.Setup` after `DefaultOpenApiMiddleware`
|
|
1062
|
+
*
|
|
1063
|
+
* ```ts
|
|
1064
|
+
* const middlewares = [
|
|
1065
|
+
* DefaultOpenApiMiddleware,
|
|
1066
|
+
* DefaultSwaggerMiddleware.Serve,
|
|
1067
|
+
* DefaultSwaggerMiddleware.Setup,
|
|
1068
|
+
* ];
|
|
1069
|
+
* middlewares.map(Sapling.resolve).forEach((r) => app.use(r));
|
|
1070
|
+
* ```
|
|
1071
|
+
*/
|
|
943
1072
|
let Serve = class Serve {
|
|
944
|
-
|
|
945
|
-
this.handlers = swagger_ui_express.default.serve;
|
|
946
|
-
}
|
|
1073
|
+
handlers = swagger_ui_express.default.serve;
|
|
947
1074
|
handle(request, response, next) {
|
|
948
1075
|
return Sapling.chainHandlers(this.handlers, request, response, next);
|
|
949
1076
|
}
|
|
950
1077
|
};
|
|
951
1078
|
__decorate([Middleware(_settings.doc.swaggerPath)], Serve.prototype, "handle", null);
|
|
952
1079
|
Serve = __decorate([MiddlewareClass()], Serve);
|
|
1080
|
+
/**
|
|
1081
|
+
* Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
|
|
1082
|
+
*
|
|
1083
|
+
* Configure any middleware-specific settings with `Sapling.Extras.swaggerAndOpenApi`
|
|
1084
|
+
*
|
|
1085
|
+
* You must register `DefaultSwaggerMiddleware.Serve` & `DefaultSwaggerMiddleware.Setup` after `DefaultOpenApiMiddleware`
|
|
1086
|
+
*
|
|
1087
|
+
* ```ts
|
|
1088
|
+
* const middlewares = [
|
|
1089
|
+
* DefaultOpenApiMiddleware,
|
|
1090
|
+
* DefaultSwaggerMiddleware.Serve,
|
|
1091
|
+
* DefaultSwaggerMiddleware.Setup,
|
|
1092
|
+
* ];
|
|
1093
|
+
* middlewares.map(Sapling.resolve).forEach((r) => app.use(r));
|
|
1094
|
+
* ```
|
|
1095
|
+
*/
|
|
953
1096
|
let Setup = class Setup {
|
|
1097
|
+
handler;
|
|
954
1098
|
constructor() {
|
|
955
1099
|
this.handler = swagger_ui_express.default.setup(null, { swaggerOptions: { url: _settings.doc.openApiPath } });
|
|
956
1100
|
}
|
|
@@ -960,12 +1104,29 @@ let Setup = class Setup {
|
|
|
960
1104
|
};
|
|
961
1105
|
__decorate([Middleware(_settings.doc.swaggerPath)], Setup.prototype, "handle", null);
|
|
962
1106
|
Setup = __decorate([MiddlewareClass()], Setup);
|
|
1107
|
+
/**
|
|
1108
|
+
* Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
|
|
1109
|
+
*
|
|
1110
|
+
* Configure any middleware-specific settings with `Sapling.Extras.swaggerAndOpenApi`
|
|
1111
|
+
*
|
|
1112
|
+
* You must register `DefaultSwaggerMiddleware.Serve` & `DefaultSwaggerMiddleware.Setup` after `DefaultOpenApiMiddleware`
|
|
1113
|
+
*
|
|
1114
|
+
* ```ts
|
|
1115
|
+
* const middlewares = [
|
|
1116
|
+
* DefaultOpenApiMiddleware,
|
|
1117
|
+
* DefaultSwaggerMiddleware.Serve,
|
|
1118
|
+
* DefaultSwaggerMiddleware.Setup,
|
|
1119
|
+
* ];
|
|
1120
|
+
* middlewares.map(Sapling.resolve).forEach((r) => app.use(r));
|
|
1121
|
+
* ```
|
|
1122
|
+
*/
|
|
963
1123
|
const DefaultSwaggerMiddleware = {
|
|
964
1124
|
Serve,
|
|
965
1125
|
Setup
|
|
966
1126
|
};
|
|
967
1127
|
//#endregion
|
|
968
1128
|
exports.Controller = Controller;
|
|
1129
|
+
exports.ControllerSchema = ControllerSchema;
|
|
969
1130
|
exports.DELETE = DELETE;
|
|
970
1131
|
Object.defineProperty(exports, "DefaultBaseErrorMiddleware", {
|
|
971
1132
|
enumerable: true,
|
|
@@ -1012,20 +1173,24 @@ exports.ResponseBody = ResponseBody;
|
|
|
1012
1173
|
exports.ResponseEntity = ResponseEntity;
|
|
1013
1174
|
exports.ResponseEntityBuilder = ResponseEntityBuilder;
|
|
1014
1175
|
exports.ResponseStatusError = ResponseStatusError;
|
|
1176
|
+
exports.RouteSchema = RouteSchema;
|
|
1015
1177
|
exports.Sapling = Sapling;
|
|
1016
1178
|
exports._ControllerRegistry = _ControllerRegistry;
|
|
1017
1179
|
exports._InjectableDeps = _InjectableDeps;
|
|
1018
1180
|
exports._InjectableRegistry = _InjectableRegistry;
|
|
1019
1181
|
exports._Route = _Route;
|
|
1182
|
+
exports._getControllerSchema = _getControllerSchema;
|
|
1020
1183
|
exports._getOrCreateSchemaDefinition = _getOrCreateSchemaDefinition;
|
|
1184
|
+
exports._getRouteSchema = _getRouteSchema;
|
|
1021
1185
|
exports._getRoutes = _getRoutes;
|
|
1022
1186
|
exports._getValidatorSchema = _getValidatorSchema;
|
|
1023
1187
|
exports._parseOrThrow = _parseOrThrow;
|
|
1024
|
-
exports.
|
|
1188
|
+
exports._registerController = _registerController;
|
|
1025
1189
|
exports._resolve = _resolve;
|
|
1026
|
-
exports.
|
|
1190
|
+
exports._saveValidatorSchema = _saveValidatorSchema;
|
|
1191
|
+
exports._setControllerSchema = _setControllerSchema;
|
|
1192
|
+
exports._setRouteSchema = _setRouteSchema;
|
|
1027
1193
|
exports._settings = _settings;
|
|
1028
1194
|
exports.generateOpenApiSpec = generateOpenApiSpec;
|
|
1029
1195
|
exports.methodResolve = methodResolve;
|
|
1030
1196
|
exports.openApiGenerator = openApiGenerator;
|
|
1031
|
-
exports.setOpenApiConfig = setOpenApiConfig;
|