@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.mjs
CHANGED
|
@@ -23,6 +23,7 @@ const Html404ErrorPage = (error) => `<!DOCTYPE html>
|
|
|
23
23
|
* You can either return `new RedirectView(url)` or `RedirectView.redirect(url)` inside of a controller method.
|
|
24
24
|
*/
|
|
25
25
|
var RedirectView = class RedirectView {
|
|
26
|
+
_url;
|
|
26
27
|
constructor(url) {
|
|
27
28
|
this._url = url;
|
|
28
29
|
}
|
|
@@ -118,8 +119,10 @@ let HttpStatus = /* @__PURE__ */ function(HttpStatus) {
|
|
|
118
119
|
* @typeParam T - the type of the response body
|
|
119
120
|
*/
|
|
120
121
|
var ResponseEntity = class {
|
|
122
|
+
_statusCode;
|
|
123
|
+
_headers = {};
|
|
124
|
+
_body;
|
|
121
125
|
constructor(body, headers = {}, statusCode = 200) {
|
|
122
|
-
this._headers = {};
|
|
123
126
|
this._body = body;
|
|
124
127
|
this._headers = headers;
|
|
125
128
|
this._statusCode = statusCode;
|
|
@@ -172,8 +175,9 @@ var ResponseEntity = class {
|
|
|
172
175
|
* ensuring type safety when constructing the response.
|
|
173
176
|
*/
|
|
174
177
|
var ResponseEntityBuilder = class {
|
|
178
|
+
_statusCode;
|
|
179
|
+
_headers = {};
|
|
175
180
|
constructor(statusCode) {
|
|
176
|
-
this._headers = {};
|
|
177
181
|
this._statusCode = statusCode;
|
|
178
182
|
}
|
|
179
183
|
/**
|
|
@@ -205,6 +209,7 @@ var ResponseEntityBuilder = class {
|
|
|
205
209
|
* @see {@link Sapling.loadResponseStatusErrorMiddleware}
|
|
206
210
|
*/
|
|
207
211
|
var ResponseStatusError = class ResponseStatusError extends Error {
|
|
212
|
+
status;
|
|
208
213
|
constructor(status, message) {
|
|
209
214
|
super(message ?? "Something went wrong.");
|
|
210
215
|
this.status = status;
|
|
@@ -216,26 +221,27 @@ var ResponseStatusError = class ResponseStatusError extends Error {
|
|
|
216
221
|
//#endregion
|
|
217
222
|
//#region src/helper/error/parse.ts
|
|
218
223
|
/**
|
|
219
|
-
* This error should be thrown when some data cannot be parsed by a given schema.
|
|
224
|
+
* This error should be thrown when some data cannot be parsed by a given Standard Schema compatible schema.
|
|
220
225
|
*/
|
|
221
226
|
var ParserError = class ParserError extends ResponseStatusError {
|
|
222
|
-
constructor(location, issues, vendor) {
|
|
223
|
-
super(400, ParserError.formatMessage(location, issues, vendor));
|
|
227
|
+
constructor(location, issues, vendor, functionName) {
|
|
228
|
+
super(400, ParserError.formatMessage(location, issues, vendor, functionName));
|
|
224
229
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
225
230
|
}
|
|
226
|
-
static formatMessage(location, issues, vendor) {
|
|
231
|
+
static formatMessage(location, issues, vendor, functionName) {
|
|
227
232
|
const formatted = issues.map((i) => {
|
|
228
233
|
const path = Array.isArray(i.path) ? i.path.map((seg) => typeof seg === "object" && seg ? String(seg.key) : String(seg)).join(".") : "";
|
|
229
234
|
return path ? `${path}: ${i.message}` : i.message;
|
|
230
235
|
}).join("; ");
|
|
231
|
-
return
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
236
|
+
return `Failed to parse ${this.getPrettyLocationString(location)} with ${vendor} on ${functionName}: ${formatted}`;
|
|
237
|
+
}
|
|
238
|
+
static getPrettyLocationString(location) {
|
|
239
|
+
switch (location) {
|
|
240
|
+
case "reqbody": return "request body";
|
|
241
|
+
case "reqparams": return "request params";
|
|
242
|
+
case "reqquery": return "request query";
|
|
243
|
+
case "resbody": return "response body";
|
|
244
|
+
}
|
|
239
245
|
}
|
|
240
246
|
};
|
|
241
247
|
//#endregion
|
|
@@ -245,7 +251,11 @@ const _settings = {
|
|
|
245
251
|
deserialize: JSON.parse,
|
|
246
252
|
doc: {
|
|
247
253
|
openApiPath: "/openapi.json",
|
|
248
|
-
swaggerPath: "/swagger.html"
|
|
254
|
+
swaggerPath: "/swagger.html",
|
|
255
|
+
metadata: {
|
|
256
|
+
title: "API",
|
|
257
|
+
version: "1.0.0"
|
|
258
|
+
}
|
|
249
259
|
}
|
|
250
260
|
};
|
|
251
261
|
/**
|
|
@@ -351,12 +361,59 @@ var Sapling = class Sapling {
|
|
|
351
361
|
static setDeserializeFn(fn) {
|
|
352
362
|
_settings.deserialize = fn;
|
|
353
363
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
static
|
|
358
|
-
|
|
359
|
-
|
|
364
|
+
/**
|
|
365
|
+
* Modify extra settings
|
|
366
|
+
*/
|
|
367
|
+
static Extras = {
|
|
368
|
+
/**
|
|
369
|
+
* Modify default settings applied to OpenAPI & Swagger
|
|
370
|
+
*/
|
|
371
|
+
swaggerAndOpenApi: {
|
|
372
|
+
/**
|
|
373
|
+
* Set base OpenAPI metadata values.
|
|
374
|
+
*
|
|
375
|
+
* @default { title: "API", version: "1.0.0" }
|
|
376
|
+
*/
|
|
377
|
+
setMetadata(metadata) {
|
|
378
|
+
_settings.doc.metadata = metadata;
|
|
379
|
+
},
|
|
380
|
+
/**
|
|
381
|
+
* change default endpoint that will serve OpenAPI spec.
|
|
382
|
+
* Swagger will also load this endpoint on load.
|
|
383
|
+
*
|
|
384
|
+
* @default `/openapi.json`
|
|
385
|
+
*/
|
|
386
|
+
setOpenApiPath(path) {
|
|
387
|
+
_settings.doc.openApiPath = path;
|
|
388
|
+
},
|
|
389
|
+
/**
|
|
390
|
+
* change Swagger endpoint.
|
|
391
|
+
*
|
|
392
|
+
* @default `/swagger.html`
|
|
393
|
+
*/
|
|
394
|
+
setSwaggerPath(path) {
|
|
395
|
+
_settings.doc.swaggerPath = path;
|
|
396
|
+
}
|
|
397
|
+
} };
|
|
398
|
+
/**
|
|
399
|
+
* This method can be used in a `@MiddlewareClass` to register any libraries
|
|
400
|
+
* that expect you to register multiple registers at once. An example is `swagger-ui-express`
|
|
401
|
+
*
|
|
402
|
+
* @example
|
|
403
|
+
* ```ts
|
|
404
|
+
* ⠀@MiddlewareClass()
|
|
405
|
+
* class Serve {
|
|
406
|
+
* // `swagger.serve` returns multiple Express handlers for all the assets and routes
|
|
407
|
+
* // that will be served
|
|
408
|
+
* private readonly handlers: RequestHandler[] = swagger.serve;
|
|
409
|
+
*
|
|
410
|
+
* ⠀@Middleware(_settings.doc.swaggerPath)
|
|
411
|
+
* handle(request: Request, response: Response, next: NextFunction) {
|
|
412
|
+
* return Sapling.chainHandlers(this.handlers, request, response, next);
|
|
413
|
+
* }
|
|
414
|
+
* }
|
|
415
|
+
* ```
|
|
416
|
+
*/
|
|
360
417
|
static chainHandlers(handlers, request, response, next, index = 0) {
|
|
361
418
|
if (index >= handlers.length) {
|
|
362
419
|
next();
|
|
@@ -453,18 +510,33 @@ function _getRoutes(ctor) {
|
|
|
453
510
|
return _routeStore.get(ctor) ?? [];
|
|
454
511
|
}
|
|
455
512
|
//#endregion
|
|
456
|
-
//#region src/
|
|
457
|
-
function
|
|
513
|
+
//#region src/annotation/schema.ts
|
|
514
|
+
function ControllerSchema(options) {
|
|
515
|
+
return (target) => {
|
|
516
|
+
_setControllerSchema(target, options);
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function RouteSchema(options) {
|
|
520
|
+
return (target, propertyKey) => {
|
|
521
|
+
const ctor = target.constructor;
|
|
522
|
+
_setRouteSchema(ctor, String(propertyKey), options);
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
const _routeSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
526
|
+
const _controllerSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
527
|
+
function getOrCreateRouteSchemaStore(store, ctor) {
|
|
458
528
|
const existing = store.get(ctor);
|
|
459
529
|
if (existing) return existing;
|
|
460
530
|
const created = /* @__PURE__ */ new Map();
|
|
461
531
|
store.set(ctor, created);
|
|
462
532
|
return created;
|
|
463
533
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
534
|
+
function _setRouteSchema(ctor, fnName, options) {
|
|
535
|
+
getOrCreateRouteSchemaStore(_routeSchemaStore, ctor).set(fnName, options);
|
|
536
|
+
}
|
|
537
|
+
function _setControllerSchema(ctor, options) {
|
|
538
|
+
_controllerSchemaStore.set(ctor, options);
|
|
539
|
+
}
|
|
468
540
|
function _getRouteSchema(ctor, fnName) {
|
|
469
541
|
return _routeSchemaStore.get(ctor)?.get(fnName);
|
|
470
542
|
}
|
|
@@ -472,26 +544,78 @@ function _getControllerSchema(ctor) {
|
|
|
472
544
|
return _controllerSchemaStore.get(ctor);
|
|
473
545
|
}
|
|
474
546
|
//#endregion
|
|
547
|
+
//#region src/annotation/validator.ts
|
|
548
|
+
const _validatorSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
549
|
+
function ResponseBody(schema) {
|
|
550
|
+
return (target, propertyKey) => {
|
|
551
|
+
const ctor = target.constructor;
|
|
552
|
+
const fnName = String(propertyKey);
|
|
553
|
+
_saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "responseBody", schema, fnName);
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
function RequestBody(schema) {
|
|
557
|
+
return (target, propertyKey) => {
|
|
558
|
+
const ctor = target.constructor;
|
|
559
|
+
const fnName = String(propertyKey);
|
|
560
|
+
_saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "requestBody", schema, fnName);
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function RequestParam(schema) {
|
|
564
|
+
return (target, propertyKey) => {
|
|
565
|
+
const ctor = target.constructor;
|
|
566
|
+
const fnName = String(propertyKey);
|
|
567
|
+
_saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "requestParam", schema, fnName);
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
function RequestQuery(schema) {
|
|
571
|
+
return (target, propertyKey) => {
|
|
572
|
+
const ctor = target.constructor;
|
|
573
|
+
const fnName = String(propertyKey);
|
|
574
|
+
_saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "requestQuery", schema, fnName);
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function getOrCreateValidatorSchemaStore(store, ctor) {
|
|
578
|
+
const existing = store.get(ctor);
|
|
579
|
+
if (existing) return existing;
|
|
580
|
+
const created = /* @__PURE__ */ new Map();
|
|
581
|
+
store.set(ctor, created);
|
|
582
|
+
return created;
|
|
583
|
+
}
|
|
584
|
+
function _getOrCreateSchemaDefinition(ctor, fnName) {
|
|
585
|
+
const byFn = getOrCreateValidatorSchemaStore(_validatorSchemaStore, ctor);
|
|
586
|
+
const existing = byFn.get(fnName);
|
|
587
|
+
if (existing) return existing;
|
|
588
|
+
const created = {};
|
|
589
|
+
byFn.set(fnName, created);
|
|
590
|
+
return created;
|
|
591
|
+
}
|
|
592
|
+
async function _parseOrThrow(schema, input, location, fnName) {
|
|
593
|
+
const result = await schema["~standard"].validate(input);
|
|
594
|
+
if (result.issues) throw new ParserError(location, result.issues, schema["~standard"].vendor, fnName);
|
|
595
|
+
return result.value;
|
|
596
|
+
}
|
|
597
|
+
function _saveValidatorSchema(def, key, schema, fnName) {
|
|
598
|
+
if (def[key]) throw new Error(`Duplicate schema for "${String(key)}" on method "${fnName}"`);
|
|
599
|
+
def[key] = schema;
|
|
600
|
+
}
|
|
601
|
+
function _getValidatorSchema(ctor, fnName) {
|
|
602
|
+
return _validatorSchemaStore.get(ctor)?.get(fnName);
|
|
603
|
+
}
|
|
604
|
+
//#endregion
|
|
475
605
|
//#region src/helper/openapi.ts
|
|
476
606
|
var OpenAPIGenerator = class {
|
|
477
|
-
|
|
478
|
-
this.controllers = /* @__PURE__ */ new Set();
|
|
479
|
-
this.config = {
|
|
480
|
-
title: "API",
|
|
481
|
-
version: "1.0.0"
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
setConfig(config) {
|
|
485
|
-
this.config = config;
|
|
486
|
-
}
|
|
607
|
+
controllers = /* @__PURE__ */ new Set();
|
|
487
608
|
registerController(controllerClass, prefix) {
|
|
488
609
|
this.controllers.add({
|
|
489
610
|
class: controllerClass,
|
|
490
611
|
prefix
|
|
491
612
|
});
|
|
492
613
|
}
|
|
614
|
+
get metadata() {
|
|
615
|
+
return _settings.doc.metadata;
|
|
616
|
+
}
|
|
493
617
|
generateSpec() {
|
|
494
|
-
const
|
|
618
|
+
const metadata = this.metadata;
|
|
495
619
|
const paths = {};
|
|
496
620
|
const tags = [];
|
|
497
621
|
for (const { class: controllerClass, prefix } of this.controllers) {
|
|
@@ -506,50 +630,70 @@ var OpenAPIGenerator = class {
|
|
|
506
630
|
const schemas = _getValidatorSchema(controllerClass, route.fnName);
|
|
507
631
|
const routeSchema = _getRouteSchema(controllerClass, route.fnName);
|
|
508
632
|
if (route.path instanceof RegExp) throw new Error(`You have a route with a regex path of ${route.path.source}. This is not compatible with OpenAPI.`);
|
|
509
|
-
const openApiPath = prefix + route.path;
|
|
633
|
+
const openApiPath = (prefix + route.path).replace(/:([A-Za-z0-9_]+)/g, "{$1}");
|
|
510
634
|
if (!paths[openApiPath]) paths[openApiPath] = {};
|
|
511
635
|
const responses = {};
|
|
512
|
-
if (schemas?.responseBody)
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
636
|
+
if (schemas?.responseBody) {
|
|
637
|
+
const responseSchema = this.toJsonSchema(schemas.responseBody, "output");
|
|
638
|
+
responses["200"] = {
|
|
639
|
+
description: responseSchema.description ?? "Successful response",
|
|
640
|
+
content: { "application/json": { schema: responseSchema } }
|
|
641
|
+
};
|
|
642
|
+
} else responses["200"] = { description: "Successful response" };
|
|
643
|
+
if (routeSchema?.responses) for (const resp of routeSchema.responses) {
|
|
644
|
+
const statusCode = String(resp.statusCode);
|
|
645
|
+
const existingResponse = responses[statusCode];
|
|
646
|
+
const existingSchema = existingResponse && "content" in existingResponse ? existingResponse.content?.["application/json"]?.schema : void 0;
|
|
647
|
+
const responseSchema = resp.schema ? this.toJsonSchema(resp.schema, "output") : statusCode === "200" ? existingSchema : void 0;
|
|
648
|
+
responses[statusCode] = {
|
|
649
|
+
...existingResponse,
|
|
650
|
+
description: resp.description ?? responseSchema?.description ?? existingResponse?.description ?? `Response ${resp.statusCode}`,
|
|
651
|
+
...responseSchema ? { content: { "application/json": { schema: responseSchema } } } : {}
|
|
652
|
+
};
|
|
653
|
+
}
|
|
521
654
|
const operation = {
|
|
522
655
|
responses,
|
|
656
|
+
summary: routeSchema?.summary,
|
|
523
657
|
description: routeSchema?.description,
|
|
524
658
|
tags: controllerSchema?.title ? [controllerSchema.title] : void 0
|
|
525
659
|
};
|
|
526
660
|
const parameters = [];
|
|
527
661
|
if (schemas?.requestParam) {
|
|
528
|
-
const paramSchema = this.toJsonSchema(schemas.requestParam);
|
|
529
|
-
if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties))
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
662
|
+
const paramSchema = this.toJsonSchema(schemas.requestParam, "input");
|
|
663
|
+
if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties)) {
|
|
664
|
+
const parameterSchema = schema;
|
|
665
|
+
parameters.push({
|
|
666
|
+
name,
|
|
667
|
+
in: "path",
|
|
668
|
+
required: true,
|
|
669
|
+
description: parameterSchema.description,
|
|
670
|
+
schema: parameterSchema
|
|
671
|
+
});
|
|
672
|
+
}
|
|
535
673
|
}
|
|
536
674
|
if (schemas?.requestQuery) {
|
|
537
|
-
const querySchema = this.toJsonSchema(schemas.requestQuery);
|
|
675
|
+
const querySchema = this.toJsonSchema(schemas.requestQuery, "input");
|
|
538
676
|
if (querySchema.type === "object" && querySchema.properties) for (const [name, schema] of Object.entries(querySchema.properties)) {
|
|
539
677
|
const isRequired = Array.isArray(querySchema.required) && querySchema.required.includes(name);
|
|
678
|
+
const parameterSchema = schema;
|
|
540
679
|
parameters.push({
|
|
541
680
|
name,
|
|
542
681
|
in: "query",
|
|
543
682
|
required: isRequired,
|
|
544
|
-
|
|
683
|
+
description: parameterSchema.description,
|
|
684
|
+
schema: parameterSchema
|
|
545
685
|
});
|
|
546
686
|
}
|
|
547
687
|
}
|
|
548
688
|
if (parameters.length > 0) operation.parameters = parameters;
|
|
549
|
-
if (schemas?.requestBody)
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
689
|
+
if (schemas?.requestBody) {
|
|
690
|
+
const requestSchema = this.toJsonSchema(schemas.requestBody, "input");
|
|
691
|
+
operation.requestBody = {
|
|
692
|
+
required: true,
|
|
693
|
+
description: requestSchema.description,
|
|
694
|
+
content: { "application/json": { schema: requestSchema } }
|
|
695
|
+
};
|
|
696
|
+
}
|
|
553
697
|
const method = route.method.toLowerCase();
|
|
554
698
|
paths[openApiPath][method] = operation;
|
|
555
699
|
}
|
|
@@ -557,30 +701,28 @@ var OpenAPIGenerator = class {
|
|
|
557
701
|
return {
|
|
558
702
|
openapi: "3.0.0",
|
|
559
703
|
info: {
|
|
560
|
-
title:
|
|
561
|
-
version:
|
|
562
|
-
description:
|
|
704
|
+
title: metadata.title,
|
|
705
|
+
version: metadata.version,
|
|
706
|
+
description: metadata.description
|
|
563
707
|
},
|
|
564
708
|
tags: tags.length > 0 ? tags : void 0,
|
|
565
709
|
paths
|
|
566
710
|
};
|
|
567
711
|
}
|
|
568
|
-
toJsonSchema(schema) {
|
|
712
|
+
toJsonSchema(schema, direction = "output") {
|
|
569
713
|
try {
|
|
570
|
-
|
|
714
|
+
const jsonSchema = schema["~standard"].jsonSchema;
|
|
715
|
+
return direction === "input" ? jsonSchema.input({ target: "openapi-3.0" }) : jsonSchema.output({ target: "openapi-3.0" });
|
|
571
716
|
} catch (e) {
|
|
572
|
-
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
|
|
717
|
+
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 });
|
|
573
718
|
throw e;
|
|
574
719
|
}
|
|
575
720
|
}
|
|
576
721
|
};
|
|
577
722
|
const openApiGenerator = new OpenAPIGenerator();
|
|
578
|
-
function
|
|
723
|
+
function _registerController(controllerClass, prefix) {
|
|
579
724
|
openApiGenerator.registerController(controllerClass, prefix);
|
|
580
725
|
}
|
|
581
|
-
function setOpenApiConfig(config) {
|
|
582
|
-
openApiGenerator.setConfig(config);
|
|
583
|
-
}
|
|
584
726
|
function generateOpenApiSpec() {
|
|
585
727
|
return openApiGenerator.generateSpec();
|
|
586
728
|
}
|
|
@@ -717,60 +859,6 @@ function _resolve(ctor) {
|
|
|
717
859
|
return _InjectableRegistry.get(ctor);
|
|
718
860
|
}
|
|
719
861
|
//#endregion
|
|
720
|
-
//#region src/annotation/validator.ts
|
|
721
|
-
const _validatorSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
722
|
-
function ResponseBody(schema) {
|
|
723
|
-
return (target, propertyKey) => {
|
|
724
|
-
const ctor = target.constructor;
|
|
725
|
-
const fnName = String(propertyKey);
|
|
726
|
-
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "responseBody", schema, fnName);
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
|
-
function RequestBody(schema) {
|
|
730
|
-
return (target, propertyKey) => {
|
|
731
|
-
const ctor = target.constructor;
|
|
732
|
-
const fnName = String(propertyKey);
|
|
733
|
-
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestBody", schema, fnName);
|
|
734
|
-
};
|
|
735
|
-
}
|
|
736
|
-
function RequestParam(schema) {
|
|
737
|
-
return (target, propertyKey) => {
|
|
738
|
-
const ctor = target.constructor;
|
|
739
|
-
const fnName = String(propertyKey);
|
|
740
|
-
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestParam", schema, fnName);
|
|
741
|
-
};
|
|
742
|
-
}
|
|
743
|
-
function RequestQuery(schema) {
|
|
744
|
-
return (target, propertyKey) => {
|
|
745
|
-
const ctor = target.constructor;
|
|
746
|
-
const fnName = String(propertyKey);
|
|
747
|
-
_setOnce(_getOrCreateSchemaDefinition(ctor, fnName), "requestQuery", schema, fnName);
|
|
748
|
-
};
|
|
749
|
-
}
|
|
750
|
-
function _getOrCreateSchemaDefinition(ctor, fnName) {
|
|
751
|
-
const byFn = _getOrCreateMap(_validatorSchemaStore, ctor);
|
|
752
|
-
const existing = byFn.get(fnName);
|
|
753
|
-
if (existing) return existing;
|
|
754
|
-
const created = {};
|
|
755
|
-
byFn.set(fnName, created);
|
|
756
|
-
return created;
|
|
757
|
-
}
|
|
758
|
-
async function _parseOrThrow(schema, input, kind) {
|
|
759
|
-
const result = await schema["~standard"].validate(input);
|
|
760
|
-
if (result.issues) {
|
|
761
|
-
console.debug(`Failed to parse a schema`);
|
|
762
|
-
throw new ParserError(kind, result.issues, schema["~standard"].vendor);
|
|
763
|
-
}
|
|
764
|
-
return result.value;
|
|
765
|
-
}
|
|
766
|
-
function _getValidatorSchema(ctor, fnName) {
|
|
767
|
-
return _validatorSchemaStore.get(ctor)?.get(fnName);
|
|
768
|
-
}
|
|
769
|
-
function _setOnce(def, key, schema, fnName) {
|
|
770
|
-
if (def[key]) throw new Error(`Duplicate schema for "${String(key)}" on method "${fnName}"`);
|
|
771
|
-
def[key] = schema;
|
|
772
|
-
}
|
|
773
|
-
//#endregion
|
|
774
862
|
//#region src/annotation/controller.ts
|
|
775
863
|
const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
|
|
776
864
|
/**
|
|
@@ -782,7 +870,7 @@ const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
|
|
|
782
870
|
function Controller({ prefix = "", deps = [] } = {}) {
|
|
783
871
|
return (target) => {
|
|
784
872
|
const targetClass = target;
|
|
785
|
-
|
|
873
|
+
_registerController(target, prefix);
|
|
786
874
|
const router = Router();
|
|
787
875
|
const routes = _getRoutes(target);
|
|
788
876
|
const usedRoutes = /* @__PURE__ */ new Set();
|
|
@@ -807,15 +895,20 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
|
|
|
807
895
|
if (method === "USE" && fn.length >= 4) {
|
|
808
896
|
const middlewareFn = async (err, request, response, next) => {
|
|
809
897
|
try {
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
898
|
+
await validate({
|
|
899
|
+
target,
|
|
900
|
+
fnName,
|
|
901
|
+
request
|
|
902
|
+
});
|
|
903
|
+
await handleResult({
|
|
904
|
+
result: fn.bind(controllerInstance)(err, request, response, next),
|
|
905
|
+
response,
|
|
906
|
+
target,
|
|
907
|
+
fnName,
|
|
908
|
+
method,
|
|
909
|
+
path: path instanceof RegExp ? path.source : fp,
|
|
910
|
+
isErrorMiddleware: true
|
|
911
|
+
});
|
|
819
912
|
} catch (e) {
|
|
820
913
|
console.error(e);
|
|
821
914
|
next(e);
|
|
@@ -825,35 +918,52 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
|
|
|
825
918
|
return;
|
|
826
919
|
}
|
|
827
920
|
router[methodName](fp, async (request, response, next) => {
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
}
|
|
841
|
-
const result = await fn.bind(controllerInstance)(request, response, next);
|
|
842
|
-
if (result instanceof ResponseEntity) {
|
|
843
|
-
const body = schemas && schemas.responseBody ? await _parseOrThrow(schemas.responseBody, result.getBody(), "resbody") : result.getBody();
|
|
844
|
-
response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(body));
|
|
845
|
-
return;
|
|
846
|
-
}
|
|
847
|
-
if (result instanceof RedirectView) {
|
|
848
|
-
response.redirect(result.getUrl());
|
|
849
|
-
return;
|
|
850
|
-
}
|
|
851
|
-
if (method !== "USE" && !response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${methodName.toUpperCase()} ${path instanceof RegExp ? path.source : fp}`));
|
|
921
|
+
await validate({
|
|
922
|
+
target,
|
|
923
|
+
fnName,
|
|
924
|
+
request
|
|
925
|
+
});
|
|
926
|
+
await handleResult({
|
|
927
|
+
result: await fn.bind(controllerInstance)(request, response, next),
|
|
928
|
+
response,
|
|
929
|
+
target,
|
|
930
|
+
fnName,
|
|
931
|
+
method,
|
|
932
|
+
path: path instanceof RegExp ? path.source : fp
|
|
933
|
+
});
|
|
852
934
|
});
|
|
853
935
|
}
|
|
854
936
|
_ControllerRegistry.set(targetClass, router);
|
|
855
937
|
};
|
|
856
938
|
}
|
|
939
|
+
async function handleResult({ result, target, fnName, response, method, path, isErrorMiddleware = false }) {
|
|
940
|
+
const schemas = _getValidatorSchema(target, fnName);
|
|
941
|
+
if (result instanceof ResponseEntity) {
|
|
942
|
+
const body = schemas && schemas.responseBody ? await _parseOrThrow(schemas.responseBody, result.getBody(), "resbody", fnName) : result.getBody();
|
|
943
|
+
response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(body));
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
if (result instanceof RedirectView) {
|
|
947
|
+
response.redirect(result.getUrl());
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
if (!isErrorMiddleware && method !== "USE" && !response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${method} ${path}`));
|
|
951
|
+
}
|
|
952
|
+
async function validate({ target, fnName, request }) {
|
|
953
|
+
const schemas = _getValidatorSchema(target, fnName);
|
|
954
|
+
if (schemas) {
|
|
955
|
+
if (schemas.requestBody) request.body = await _parseOrThrow(schemas.requestBody, request.body, "reqbody", fnName);
|
|
956
|
+
if (schemas.requestParam) request.params = await _parseOrThrow(schemas.requestParam, request.params, "reqparams", fnName);
|
|
957
|
+
if (schemas.requestQuery) {
|
|
958
|
+
const parsedQuery = await _parseOrThrow(schemas.requestQuery, request.query, "reqquery", fnName);
|
|
959
|
+
Object.defineProperty(request, "query", {
|
|
960
|
+
value: parsedQuery,
|
|
961
|
+
writable: true,
|
|
962
|
+
configurable: true
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
857
967
|
//#endregion
|
|
858
968
|
//#region src/annotation/middleware.ts
|
|
859
969
|
/**
|
|
@@ -888,7 +998,10 @@ DefaultBaseErrorMiddleware = __decorate([MiddlewareClass()], DefaultBaseErrorMid
|
|
|
888
998
|
//#region src/middleware/default/error/parse.ts
|
|
889
999
|
let DefaultParserErrorMiddleware = class DefaultParserErrorMiddleware {
|
|
890
1000
|
handle(err, _request, _response, next) {
|
|
891
|
-
if (err instanceof ParserError)
|
|
1001
|
+
if (err instanceof ParserError) {
|
|
1002
|
+
console.warn(err);
|
|
1003
|
+
return ResponseEntity.status(err.status).body({ message: err.message });
|
|
1004
|
+
}
|
|
892
1005
|
next(err);
|
|
893
1006
|
}
|
|
894
1007
|
};
|
|
@@ -915,17 +1028,48 @@ __decorate([GET(_settings.doc.openApiPath)], DefaultOpenApiMiddleware.prototype,
|
|
|
915
1028
|
DefaultOpenApiMiddleware = __decorate([MiddlewareClass()], DefaultOpenApiMiddleware);
|
|
916
1029
|
//#endregion
|
|
917
1030
|
//#region src/middleware/default/swagger/index.ts
|
|
1031
|
+
/**
|
|
1032
|
+
* Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
|
|
1033
|
+
*
|
|
1034
|
+
* Configure any middleware-specific settings with `Sapling.Extras.swaggerAndOpenApi`
|
|
1035
|
+
*
|
|
1036
|
+
* You must register `DefaultSwaggerMiddleware.Serve` & `DefaultSwaggerMiddleware.Setup` after `DefaultOpenApiMiddleware`
|
|
1037
|
+
*
|
|
1038
|
+
* ```ts
|
|
1039
|
+
* const middlewares = [
|
|
1040
|
+
* DefaultOpenApiMiddleware,
|
|
1041
|
+
* DefaultSwaggerMiddleware.Serve,
|
|
1042
|
+
* DefaultSwaggerMiddleware.Setup,
|
|
1043
|
+
* ];
|
|
1044
|
+
* middlewares.map(Sapling.resolve).forEach((r) => app.use(r));
|
|
1045
|
+
* ```
|
|
1046
|
+
*/
|
|
918
1047
|
let Serve = class Serve {
|
|
919
|
-
|
|
920
|
-
this.handlers = swagger.serve;
|
|
921
|
-
}
|
|
1048
|
+
handlers = swagger.serve;
|
|
922
1049
|
handle(request, response, next) {
|
|
923
1050
|
return Sapling.chainHandlers(this.handlers, request, response, next);
|
|
924
1051
|
}
|
|
925
1052
|
};
|
|
926
1053
|
__decorate([Middleware(_settings.doc.swaggerPath)], Serve.prototype, "handle", null);
|
|
927
1054
|
Serve = __decorate([MiddlewareClass()], Serve);
|
|
1055
|
+
/**
|
|
1056
|
+
* Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
|
|
1057
|
+
*
|
|
1058
|
+
* Configure any middleware-specific settings with `Sapling.Extras.swaggerAndOpenApi`
|
|
1059
|
+
*
|
|
1060
|
+
* You must register `DefaultSwaggerMiddleware.Serve` & `DefaultSwaggerMiddleware.Setup` after `DefaultOpenApiMiddleware`
|
|
1061
|
+
*
|
|
1062
|
+
* ```ts
|
|
1063
|
+
* const middlewares = [
|
|
1064
|
+
* DefaultOpenApiMiddleware,
|
|
1065
|
+
* DefaultSwaggerMiddleware.Serve,
|
|
1066
|
+
* DefaultSwaggerMiddleware.Setup,
|
|
1067
|
+
* ];
|
|
1068
|
+
* middlewares.map(Sapling.resolve).forEach((r) => app.use(r));
|
|
1069
|
+
* ```
|
|
1070
|
+
*/
|
|
928
1071
|
let Setup = class Setup {
|
|
1072
|
+
handler;
|
|
929
1073
|
constructor() {
|
|
930
1074
|
this.handler = swagger.setup(null, { swaggerOptions: { url: _settings.doc.openApiPath } });
|
|
931
1075
|
}
|
|
@@ -935,9 +1079,25 @@ let Setup = class Setup {
|
|
|
935
1079
|
};
|
|
936
1080
|
__decorate([Middleware(_settings.doc.swaggerPath)], Setup.prototype, "handle", null);
|
|
937
1081
|
Setup = __decorate([MiddlewareClass()], Setup);
|
|
1082
|
+
/**
|
|
1083
|
+
* Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
|
|
1084
|
+
*
|
|
1085
|
+
* Configure any middleware-specific settings with `Sapling.Extras.swaggerAndOpenApi`
|
|
1086
|
+
*
|
|
1087
|
+
* You must register `DefaultSwaggerMiddleware.Serve` & `DefaultSwaggerMiddleware.Setup` after `DefaultOpenApiMiddleware`
|
|
1088
|
+
*
|
|
1089
|
+
* ```ts
|
|
1090
|
+
* const middlewares = [
|
|
1091
|
+
* DefaultOpenApiMiddleware,
|
|
1092
|
+
* DefaultSwaggerMiddleware.Serve,
|
|
1093
|
+
* DefaultSwaggerMiddleware.Setup,
|
|
1094
|
+
* ];
|
|
1095
|
+
* middlewares.map(Sapling.resolve).forEach((r) => app.use(r));
|
|
1096
|
+
* ```
|
|
1097
|
+
*/
|
|
938
1098
|
const DefaultSwaggerMiddleware = {
|
|
939
1099
|
Serve,
|
|
940
1100
|
Setup
|
|
941
1101
|
};
|
|
942
1102
|
//#endregion
|
|
943
|
-
export { Controller, DELETE, DefaultBaseErrorMiddleware, DefaultOpenApiMiddleware, DefaultParserErrorMiddleware, DefaultResponseStatusErrorMiddleware, DefaultSwaggerMiddleware, GET, HEAD, Html404ErrorPage, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, ParserError, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseBody, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, Sapling, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _getOrCreateSchemaDefinition, _getRoutes, _getValidatorSchema, _parseOrThrow,
|
|
1103
|
+
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, _registerController, _resolve, _saveValidatorSchema, _setControllerSchema, _setRouteSchema, _settings, generateOpenApiSpec, methodResolve, openApiGenerator };
|