@tahminator/sapling 2.0.5 → 2.1.0-beta.39c06f1e
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 +729 -298
- package/dist/index.d.cts +653 -89
- package/dist/index.d.mts +653 -89
- package/dist/index.mjs +689 -298
- 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.
|
|
@@ -46,6 +48,7 @@ const Html404ErrorPage = (error) => `<!DOCTYPE html>
|
|
|
46
48
|
* You can either return `new RedirectView(url)` or `RedirectView.redirect(url)` inside of a controller method.
|
|
47
49
|
*/
|
|
48
50
|
var RedirectView = class RedirectView {
|
|
51
|
+
_url;
|
|
49
52
|
constructor(url) {
|
|
50
53
|
this._url = url;
|
|
51
54
|
}
|
|
@@ -141,8 +144,10 @@ let HttpStatus = /* @__PURE__ */ function(HttpStatus) {
|
|
|
141
144
|
* @typeParam T - the type of the response body
|
|
142
145
|
*/
|
|
143
146
|
var ResponseEntity = class {
|
|
147
|
+
_statusCode;
|
|
148
|
+
_headers = {};
|
|
149
|
+
_body;
|
|
144
150
|
constructor(body, headers = {}, statusCode = 200) {
|
|
145
|
-
this._headers = {};
|
|
146
151
|
this._body = body;
|
|
147
152
|
this._headers = headers;
|
|
148
153
|
this._statusCode = statusCode;
|
|
@@ -195,8 +200,9 @@ var ResponseEntity = class {
|
|
|
195
200
|
* ensuring type safety when constructing the response.
|
|
196
201
|
*/
|
|
197
202
|
var ResponseEntityBuilder = class {
|
|
203
|
+
_statusCode;
|
|
204
|
+
_headers = {};
|
|
198
205
|
constructor(statusCode) {
|
|
199
|
-
this._headers = {};
|
|
200
206
|
this._statusCode = statusCode;
|
|
201
207
|
}
|
|
202
208
|
/**
|
|
@@ -228,6 +234,7 @@ var ResponseEntityBuilder = class {
|
|
|
228
234
|
* @see {@link Sapling.loadResponseStatusErrorMiddleware}
|
|
229
235
|
*/
|
|
230
236
|
var ResponseStatusError = class ResponseStatusError extends Error {
|
|
237
|
+
status;
|
|
231
238
|
constructor(status, message) {
|
|
232
239
|
super(message ?? "Something went wrong.");
|
|
233
240
|
this.status = status;
|
|
@@ -239,150 +246,30 @@ var ResponseStatusError = class ResponseStatusError extends Error {
|
|
|
239
246
|
//#endregion
|
|
240
247
|
//#region src/helper/error/parse.ts
|
|
241
248
|
/**
|
|
242
|
-
* 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.
|
|
243
250
|
*/
|
|
244
251
|
var ParserError = class ParserError extends ResponseStatusError {
|
|
245
|
-
constructor(location, issues, vendor) {
|
|
246
|
-
super(400, ParserError.formatMessage(location, issues, vendor));
|
|
252
|
+
constructor(location, issues, vendor, functionName) {
|
|
253
|
+
super(400, ParserError.formatMessage(location, issues, vendor, functionName));
|
|
247
254
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
248
255
|
}
|
|
249
|
-
static formatMessage(location, issues, vendor) {
|
|
256
|
+
static formatMessage(location, issues, vendor, functionName) {
|
|
250
257
|
const formatted = issues.map((i) => {
|
|
251
258
|
const path = Array.isArray(i.path) ? i.path.map((seg) => typeof seg === "object" && seg ? String(seg.key) : String(seg)).join(".") : "";
|
|
252
259
|
return path ? `${path}: ${i.message}` : i.message;
|
|
253
260
|
}).join("; ");
|
|
254
|
-
return
|
|
255
|
-
switch (location) {
|
|
256
|
-
case "reqbody": return "request body";
|
|
257
|
-
case "reqparams": return "request params";
|
|
258
|
-
case "reqquery": return "request query";
|
|
259
|
-
}
|
|
260
|
-
})()}: ${formatted}`;
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
|
-
//#endregion
|
|
264
|
-
//#region src/helper/sapling.ts
|
|
265
|
-
const settings = {
|
|
266
|
-
serialize: JSON.stringify,
|
|
267
|
-
deserialize: JSON.parse
|
|
268
|
-
};
|
|
269
|
-
/**
|
|
270
|
-
* Collection of utility functions which are essential for Sapling to function.
|
|
271
|
-
*/
|
|
272
|
-
var Sapling = class Sapling {
|
|
273
|
-
/**
|
|
274
|
-
* If you would prefer to manually resolve your controllers instead, call resolve
|
|
275
|
-
* on the controller class.
|
|
276
|
-
*
|
|
277
|
-
* @example```ts
|
|
278
|
-
* import { Sapling } from "@tahminator/sapling";
|
|
279
|
-
* import TestController from "./path/to/test.controller";
|
|
280
|
-
*
|
|
281
|
-
* const app = express();
|
|
282
|
-
*
|
|
283
|
-
* const router = Sapling.resolve(TestController);
|
|
284
|
-
* app.use(router);
|
|
285
|
-
* ```
|
|
286
|
-
*/
|
|
287
|
-
static resolve(clazz) {
|
|
288
|
-
const router = _ControllerRegistry.get(clazz);
|
|
289
|
-
if (!router) throw new Error("Controller cannot be found");
|
|
290
|
-
return router;
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* Register this function as a middleware in order to utilize Sapling's `deserialize` function.
|
|
294
|
-
*
|
|
295
|
-
* @example```ts
|
|
296
|
-
* import { Sapling } from "@tahminator/sapling";
|
|
297
|
-
* import express from "express";
|
|
298
|
-
*
|
|
299
|
-
* const app = express();
|
|
300
|
-
*
|
|
301
|
-
* app.use(Sapling.json());
|
|
302
|
-
* ```
|
|
303
|
-
*/
|
|
304
|
-
static json() {
|
|
305
|
-
return (request, _response, next) => {
|
|
306
|
-
try {
|
|
307
|
-
if (!request.body) return next();
|
|
308
|
-
if (request.headers["content-type"] !== "application/json") return next();
|
|
309
|
-
if (typeof request.body === "string") request.body = Sapling.deserialize(request.body);
|
|
310
|
-
else if (typeof request.body === "object") {
|
|
311
|
-
const raw = JSON.stringify(request.body);
|
|
312
|
-
request.body = Sapling.deserialize(raw);
|
|
313
|
-
}
|
|
314
|
-
next();
|
|
315
|
-
} catch (err) {
|
|
316
|
-
next(err);
|
|
317
|
-
}
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
/**
|
|
321
|
-
* Register your application with all the necessary middlewares and logics for Sapling to function.
|
|
322
|
-
*
|
|
323
|
-
* @example```ts
|
|
324
|
-
* import { Sapling } from "@tahminator/sapling";
|
|
325
|
-
* import express from "express";
|
|
326
|
-
*
|
|
327
|
-
* const app = express();
|
|
328
|
-
*
|
|
329
|
-
* app.registerApp(app);
|
|
330
|
-
* ```
|
|
331
|
-
*/
|
|
332
|
-
static registerApp(app) {
|
|
333
|
-
app.use(express.default.text({ type: "application/json" }));
|
|
334
|
-
app.use(Sapling.json());
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* Serialize a value into a JSON string.
|
|
338
|
-
*
|
|
339
|
-
* This function is used in {@link ResponseEntity} to serialize the `body`.
|
|
340
|
-
*
|
|
341
|
-
* Use `setSerializeFn` to override underlying implementation.
|
|
342
|
-
*
|
|
343
|
-
* @defaultValue `JSON.stringify`
|
|
344
|
-
*/
|
|
345
|
-
static serialize(value) {
|
|
346
|
-
return settings.serialize(value);
|
|
261
|
+
return `Failed to parse ${this.getPrettyLocationString(location)} with ${vendor} on ${functionName}: ${formatted}`;
|
|
347
262
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
* De-serialize a JSON string back to a JavaScript object.
|
|
356
|
-
*
|
|
357
|
-
* This function is used to de-serialize a string into a `body`.
|
|
358
|
-
*
|
|
359
|
-
* Use `setDeserializeFn` to override underlying implementation.
|
|
360
|
-
*
|
|
361
|
-
* @defaultValue `JSON.parse`
|
|
362
|
-
*/
|
|
363
|
-
static deserialize(value) {
|
|
364
|
-
return settings.deserialize(value);
|
|
365
|
-
}
|
|
366
|
-
/**
|
|
367
|
-
* Replace the function used for `deserialize`
|
|
368
|
-
*/
|
|
369
|
-
static setDeserializeFn(fn) {
|
|
370
|
-
settings.deserialize = fn;
|
|
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
|
+
}
|
|
371
270
|
}
|
|
372
271
|
};
|
|
373
272
|
//#endregion
|
|
374
|
-
//#region src/types.ts
|
|
375
|
-
const methodResolve = {
|
|
376
|
-
GET: "get",
|
|
377
|
-
PUT: "put",
|
|
378
|
-
POST: "post",
|
|
379
|
-
DELETE: "delete",
|
|
380
|
-
OPTIONS: "options",
|
|
381
|
-
PATCH: "patch",
|
|
382
|
-
HEAD: "head",
|
|
383
|
-
USE: "use"
|
|
384
|
-
};
|
|
385
|
-
//#endregion
|
|
386
273
|
//#region lib/weakmap.ts
|
|
387
274
|
/**
|
|
388
275
|
* WeakMap that is iterable.
|
|
@@ -503,132 +390,269 @@ function _resolve(ctor) {
|
|
|
503
390
|
return _InjectableRegistry.get(ctor);
|
|
504
391
|
}
|
|
505
392
|
//#endregion
|
|
506
|
-
//#region
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
* You can then just simply cast `request.body` for your use
|
|
513
|
-
*
|
|
514
|
-
* @example
|
|
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
|
-
*/
|
|
533
|
-
function RequestBody(schema) {
|
|
534
|
-
return (target, propertyKey) => {
|
|
535
|
-
const ctor = target.constructor;
|
|
536
|
-
const fnName = String(propertyKey);
|
|
537
|
-
_setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "body", schema, fnName);
|
|
538
|
-
};
|
|
539
|
-
}
|
|
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
|
-
function RequestParam(schema) {
|
|
565
|
-
return (target, propertyKey) => {
|
|
566
|
-
const ctor = target.constructor;
|
|
567
|
-
const fnName = String(propertyKey);
|
|
568
|
-
_setOnce(_getOrCreateRequestSchemaDefinition(ctor, fnName), "param", schema, fnName);
|
|
569
|
-
};
|
|
393
|
+
//#region \0@oxc-project+runtime@0.127.0/helpers/decorate.js
|
|
394
|
+
function __decorate(decorators, target, key, desc) {
|
|
395
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
396
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
397
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
398
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
570
399
|
}
|
|
400
|
+
//#endregion
|
|
401
|
+
//#region src/middleware/health/registrar.ts
|
|
402
|
+
let HealthRegistrar = class HealthRegistrar {
|
|
403
|
+
_checks;
|
|
404
|
+
_ready;
|
|
405
|
+
constructor() {
|
|
406
|
+
this._checks = [];
|
|
407
|
+
this._ready = false;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Add a health check.
|
|
411
|
+
*
|
|
412
|
+
* Health checks will be used to determine whether service can service traffic or not (a.k.a `liveness`).
|
|
413
|
+
*/
|
|
414
|
+
add(healthCheck) {
|
|
415
|
+
this._checks.push(healthCheck);
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* @internal used by Sapling library, used to determine once all
|
|
419
|
+
* checks have been registered and server is, at the very least, alive.
|
|
420
|
+
*/
|
|
421
|
+
_markReady() {
|
|
422
|
+
this._ready = true;
|
|
423
|
+
}
|
|
424
|
+
async check() {
|
|
425
|
+
if (!this._ready) return false;
|
|
426
|
+
return (await Promise.all(this._checks.map((c) => c()))).every((c) => c === true);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
HealthRegistrar = __decorate([Injectable()], HealthRegistrar);
|
|
430
|
+
//#endregion
|
|
431
|
+
//#region src/helper/sapling.ts
|
|
432
|
+
const _settings = {
|
|
433
|
+
serialize: JSON.stringify,
|
|
434
|
+
deserialize: JSON.parse,
|
|
435
|
+
health: {
|
|
436
|
+
ready: { path: "/ready" },
|
|
437
|
+
live: { path: "/live" }
|
|
438
|
+
},
|
|
439
|
+
doc: {
|
|
440
|
+
openApiPath: "/openapi.json",
|
|
441
|
+
swaggerPath: "/swagger.html",
|
|
442
|
+
metadata: {
|
|
443
|
+
title: "API",
|
|
444
|
+
version: "1.0.0"
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
};
|
|
571
448
|
/**
|
|
572
|
-
*
|
|
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
|
-
* ```
|
|
449
|
+
* Collection of utility functions which are essential for Sapling to function.
|
|
595
450
|
*/
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
451
|
+
var Sapling = class Sapling {
|
|
452
|
+
/**
|
|
453
|
+
* If you would prefer to manually resolve your controllers instead, call resolve
|
|
454
|
+
* on the controller class.
|
|
455
|
+
*
|
|
456
|
+
* @example```ts
|
|
457
|
+
* import { Sapling } from "@tahminator/sapling";
|
|
458
|
+
* import TestController from "./path/to/test.controller";
|
|
459
|
+
*
|
|
460
|
+
* const app = express();
|
|
461
|
+
*
|
|
462
|
+
* const router = Sapling.resolve(TestController);
|
|
463
|
+
* app.use(router);
|
|
464
|
+
* ```
|
|
465
|
+
*/
|
|
466
|
+
static resolve(clazz) {
|
|
467
|
+
const router = _ControllerRegistry.get(clazz);
|
|
468
|
+
if (!router) throw new Error("Controller cannot be found");
|
|
469
|
+
return router;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Register this function as a middleware in order to utilize Sapling's `deserialize` function.
|
|
473
|
+
*
|
|
474
|
+
* @example```ts
|
|
475
|
+
* import { Sapling } from "@tahminator/sapling";
|
|
476
|
+
* import express from "express";
|
|
477
|
+
*
|
|
478
|
+
* const app = express();
|
|
479
|
+
*
|
|
480
|
+
* app.use(Sapling.json());
|
|
481
|
+
* ```
|
|
482
|
+
*/
|
|
483
|
+
static json() {
|
|
484
|
+
return (request, _response, next) => {
|
|
485
|
+
try {
|
|
486
|
+
if (!request.body) return next();
|
|
487
|
+
if (request.headers["content-type"] !== "application/json") return next();
|
|
488
|
+
if (typeof request.body === "string") request.body = Sapling.deserialize(request.body);
|
|
489
|
+
else if (typeof request.body === "object") {
|
|
490
|
+
const raw = JSON.stringify(request.body);
|
|
491
|
+
request.body = Sapling.deserialize(raw);
|
|
492
|
+
}
|
|
493
|
+
next();
|
|
494
|
+
} catch (err) {
|
|
495
|
+
next(err);
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Register your application with all the necessary middlewares and logics for Sapling to function.
|
|
501
|
+
*
|
|
502
|
+
* @example```ts
|
|
503
|
+
* import { Sapling } from "@tahminator/sapling";
|
|
504
|
+
* import express from "express";
|
|
505
|
+
*
|
|
506
|
+
* // returns the exact same `express.App` type back to you!
|
|
507
|
+
* const app = Sapling.registerApp(express());
|
|
508
|
+
* ```
|
|
509
|
+
*/
|
|
510
|
+
static registerApp(app) {
|
|
511
|
+
app.use(express.default.text({ type: "application/json" }));
|
|
512
|
+
app.use(Sapling.json());
|
|
513
|
+
return new Proxy(app, { get(target, prop, receiver) {
|
|
514
|
+
if (prop === "listen") {
|
|
515
|
+
const originalListen = target[prop];
|
|
516
|
+
return function(...args) {
|
|
517
|
+
const server = originalListen.apply(target, args);
|
|
518
|
+
server.once("listening", () => {
|
|
519
|
+
Sapling.onPostStartup();
|
|
520
|
+
console.log("Sapling successfully initialized post-startup hooks on server start");
|
|
521
|
+
});
|
|
522
|
+
return server;
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
return Reflect.get(target, prop, receiver);
|
|
526
|
+
} });
|
|
527
|
+
}
|
|
528
|
+
static onPostStartup() {
|
|
529
|
+
_InjectableRegistry.get(HealthRegistrar)?._markReady();
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Serialize a value into a JSON string.
|
|
533
|
+
*
|
|
534
|
+
* This function is used in {@link ResponseEntity} to serialize the `body`.
|
|
535
|
+
*
|
|
536
|
+
* Use `setSerializeFn` to override underlying implementation.
|
|
537
|
+
*
|
|
538
|
+
* @defaultValue `JSON.stringify`
|
|
539
|
+
*/
|
|
540
|
+
static serialize(value) {
|
|
541
|
+
return _settings.serialize(value);
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Replace the function used for `serialize`.
|
|
545
|
+
*/
|
|
546
|
+
static setSerializeFn(fn) {
|
|
547
|
+
_settings.serialize = fn;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* De-serialize a JSON string back to a JavaScript object.
|
|
551
|
+
*
|
|
552
|
+
* This function is used to de-serialize a string into a `body`.
|
|
553
|
+
*
|
|
554
|
+
* Use `setDeserializeFn` to override underlying implementation.
|
|
555
|
+
*
|
|
556
|
+
* @defaultValue `JSON.parse`
|
|
557
|
+
*/
|
|
558
|
+
static deserialize(value) {
|
|
559
|
+
return _settings.deserialize(value);
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Replace the function used for `deserialize`
|
|
563
|
+
*/
|
|
564
|
+
static setDeserializeFn(fn) {
|
|
565
|
+
_settings.deserialize = fn;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Modify extra settings
|
|
569
|
+
*/
|
|
570
|
+
static Extras = {
|
|
571
|
+
/**
|
|
572
|
+
* Modify default settings applied to OpenAPI & Swagger
|
|
573
|
+
*/
|
|
574
|
+
swaggerAndOpenApi: {
|
|
575
|
+
/**
|
|
576
|
+
* Set base OpenAPI metadata values.
|
|
577
|
+
*
|
|
578
|
+
* @default { title: "API", version: "1.0.0" }
|
|
579
|
+
*/
|
|
580
|
+
setMetadata(metadata) {
|
|
581
|
+
_settings.doc.metadata = metadata;
|
|
582
|
+
},
|
|
583
|
+
/**
|
|
584
|
+
* change default endpoint that will serve OpenAPI spec.
|
|
585
|
+
* Swagger will also load this endpoint on load.
|
|
586
|
+
*
|
|
587
|
+
* @default `/openapi.json`
|
|
588
|
+
*/
|
|
589
|
+
setOpenApiPath(path) {
|
|
590
|
+
_settings.doc.openApiPath = path;
|
|
591
|
+
},
|
|
592
|
+
/**
|
|
593
|
+
* change Swagger endpoint.
|
|
594
|
+
*
|
|
595
|
+
* @default `/swagger.html`
|
|
596
|
+
*/
|
|
597
|
+
setSwaggerPath(path) {
|
|
598
|
+
_settings.doc.swaggerPath = path;
|
|
599
|
+
}
|
|
600
|
+
},
|
|
601
|
+
/**
|
|
602
|
+
* Modify default settings applied to health / readiness / liveness.
|
|
603
|
+
*/
|
|
604
|
+
health: {
|
|
605
|
+
/**
|
|
606
|
+
* change default endpoint that ready endpoint will be served on.
|
|
607
|
+
*
|
|
608
|
+
* @default `/ready`
|
|
609
|
+
*/
|
|
610
|
+
setReadyPath(path) {
|
|
611
|
+
_settings.health.ready.path = path;
|
|
612
|
+
},
|
|
613
|
+
/**
|
|
614
|
+
* change default endpoint that live endpoint will be served on.
|
|
615
|
+
*
|
|
616
|
+
* @default `/live`
|
|
617
|
+
*/
|
|
618
|
+
setLivePath(path) {
|
|
619
|
+
_settings.health.live.path = path;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
601
622
|
};
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
623
|
+
/**
|
|
624
|
+
* This method can be used in a `@MiddlewareClass` to register any libraries
|
|
625
|
+
* that expect you to register multiple registers at once. An example is `swagger-ui-express`
|
|
626
|
+
*
|
|
627
|
+
* @example
|
|
628
|
+
* ```ts
|
|
629
|
+
* ⠀@MiddlewareClass()
|
|
630
|
+
* class Serve {
|
|
631
|
+
* // `swagger.serve` returns multiple Express handlers for all the assets and routes
|
|
632
|
+
* // that will be served
|
|
633
|
+
* private readonly handlers: RequestHandler[] = swagger.serve;
|
|
634
|
+
*
|
|
635
|
+
* ⠀@Middleware(_settings.doc.swaggerPath)
|
|
636
|
+
* handle(request: Request, response: Response, next: NextFunction) {
|
|
637
|
+
* return Sapling.chainHandlers(this.handlers, request, response, next);
|
|
638
|
+
* }
|
|
639
|
+
* }
|
|
640
|
+
* ```
|
|
641
|
+
*/
|
|
642
|
+
static chainHandlers(handlers, request, response, next, index = 0) {
|
|
643
|
+
if (index >= handlers.length) {
|
|
644
|
+
next();
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
handlers[index]?.(request, response, (err) => {
|
|
648
|
+
if (err) {
|
|
649
|
+
next(err);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
Sapling.chainHandlers(handlers, request, response, next, index + 1);
|
|
653
|
+
});
|
|
629
654
|
}
|
|
630
|
-
|
|
631
|
-
}
|
|
655
|
+
};
|
|
632
656
|
//#endregion
|
|
633
657
|
//#region src/annotation/route.ts
|
|
634
658
|
const _routeStore = /* @__PURE__ */ new WeakMap();
|
|
@@ -711,6 +735,245 @@ function _getRoutes(ctor) {
|
|
|
711
735
|
return _routeStore.get(ctor) ?? [];
|
|
712
736
|
}
|
|
713
737
|
//#endregion
|
|
738
|
+
//#region src/annotation/schema.ts
|
|
739
|
+
function ControllerSchema(options) {
|
|
740
|
+
return (target) => {
|
|
741
|
+
_setControllerSchema(target, options);
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
function RouteSchema(options) {
|
|
745
|
+
return (target, propertyKey) => {
|
|
746
|
+
const ctor = target.constructor;
|
|
747
|
+
_setRouteSchema(ctor, String(propertyKey), options);
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
const _routeSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
751
|
+
const _controllerSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
752
|
+
function getOrCreateRouteSchemaStore(store, ctor) {
|
|
753
|
+
const existing = store.get(ctor);
|
|
754
|
+
if (existing) return existing;
|
|
755
|
+
const created = /* @__PURE__ */ new Map();
|
|
756
|
+
store.set(ctor, created);
|
|
757
|
+
return created;
|
|
758
|
+
}
|
|
759
|
+
function _setRouteSchema(ctor, fnName, options) {
|
|
760
|
+
getOrCreateRouteSchemaStore(_routeSchemaStore, ctor).set(fnName, options);
|
|
761
|
+
}
|
|
762
|
+
function _setControllerSchema(ctor, options) {
|
|
763
|
+
_controllerSchemaStore.set(ctor, options);
|
|
764
|
+
}
|
|
765
|
+
function _getRouteSchema(ctor, fnName) {
|
|
766
|
+
return _routeSchemaStore.get(ctor)?.get(fnName);
|
|
767
|
+
}
|
|
768
|
+
function _getControllerSchema(ctor) {
|
|
769
|
+
return _controllerSchemaStore.get(ctor);
|
|
770
|
+
}
|
|
771
|
+
//#endregion
|
|
772
|
+
//#region src/annotation/validator.ts
|
|
773
|
+
const _validatorSchemaStore = /* @__PURE__ */ new WeakMap();
|
|
774
|
+
function ResponseBody(schema) {
|
|
775
|
+
return (target, propertyKey) => {
|
|
776
|
+
const ctor = target.constructor;
|
|
777
|
+
const fnName = String(propertyKey);
|
|
778
|
+
_saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "responseBody", schema, fnName);
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
function RequestBody(schema) {
|
|
782
|
+
return (target, propertyKey) => {
|
|
783
|
+
const ctor = target.constructor;
|
|
784
|
+
const fnName = String(propertyKey);
|
|
785
|
+
_saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "requestBody", schema, fnName);
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
function RequestParam(schema) {
|
|
789
|
+
return (target, propertyKey) => {
|
|
790
|
+
const ctor = target.constructor;
|
|
791
|
+
const fnName = String(propertyKey);
|
|
792
|
+
_saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "requestParam", schema, fnName);
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
function RequestQuery(schema) {
|
|
796
|
+
return (target, propertyKey) => {
|
|
797
|
+
const ctor = target.constructor;
|
|
798
|
+
const fnName = String(propertyKey);
|
|
799
|
+
_saveValidatorSchema(_getOrCreateSchemaDefinition(ctor, fnName), "requestQuery", schema, fnName);
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
function getOrCreateValidatorSchemaStore(store, ctor) {
|
|
803
|
+
const existing = store.get(ctor);
|
|
804
|
+
if (existing) return existing;
|
|
805
|
+
const created = /* @__PURE__ */ new Map();
|
|
806
|
+
store.set(ctor, created);
|
|
807
|
+
return created;
|
|
808
|
+
}
|
|
809
|
+
function _getOrCreateSchemaDefinition(ctor, fnName) {
|
|
810
|
+
const byFn = getOrCreateValidatorSchemaStore(_validatorSchemaStore, ctor);
|
|
811
|
+
const existing = byFn.get(fnName);
|
|
812
|
+
if (existing) return existing;
|
|
813
|
+
const created = {};
|
|
814
|
+
byFn.set(fnName, created);
|
|
815
|
+
return created;
|
|
816
|
+
}
|
|
817
|
+
async function _parseOrThrow(schema, input, location, fnName) {
|
|
818
|
+
const result = await schema["~standard"].validate(input);
|
|
819
|
+
if (result.issues) throw new ParserError(location, result.issues, schema["~standard"].vendor, fnName);
|
|
820
|
+
return result.value;
|
|
821
|
+
}
|
|
822
|
+
function _saveValidatorSchema(def, key, schema, fnName) {
|
|
823
|
+
if (def[key]) throw new Error(`Duplicate schema for "${String(key)}" on method "${fnName}"`);
|
|
824
|
+
def[key] = schema;
|
|
825
|
+
}
|
|
826
|
+
function _getValidatorSchema(ctor, fnName) {
|
|
827
|
+
return _validatorSchemaStore.get(ctor)?.get(fnName);
|
|
828
|
+
}
|
|
829
|
+
//#endregion
|
|
830
|
+
//#region src/helper/openapi.ts
|
|
831
|
+
var OpenAPIGenerator = class {
|
|
832
|
+
OPENAPI_VERSION = "3.0.0";
|
|
833
|
+
controllers = /* @__PURE__ */ new Set();
|
|
834
|
+
registerController(controllerClass, prefix) {
|
|
835
|
+
this.controllers.add({
|
|
836
|
+
class: controllerClass,
|
|
837
|
+
prefix
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* visible for testing
|
|
842
|
+
*/
|
|
843
|
+
_clearControllers() {
|
|
844
|
+
this.controllers.clear();
|
|
845
|
+
}
|
|
846
|
+
get metadata() {
|
|
847
|
+
return _settings.doc.metadata;
|
|
848
|
+
}
|
|
849
|
+
generateSpec() {
|
|
850
|
+
const metadata = this.metadata;
|
|
851
|
+
const paths = {};
|
|
852
|
+
const tags = [];
|
|
853
|
+
for (const { class: controllerClass, prefix } of this.controllers) {
|
|
854
|
+
const routes = _getRoutes(controllerClass);
|
|
855
|
+
const controllerSchema = _getControllerSchema(controllerClass);
|
|
856
|
+
if (controllerSchema?.title) tags.push({
|
|
857
|
+
name: controllerSchema.title,
|
|
858
|
+
description: controllerSchema.description
|
|
859
|
+
});
|
|
860
|
+
for (const route of routes) {
|
|
861
|
+
if (route.method === "USE") continue;
|
|
862
|
+
const schemas = _getValidatorSchema(controllerClass, route.fnName);
|
|
863
|
+
const routeSchema = _getRouteSchema(controllerClass, route.fnName);
|
|
864
|
+
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.`);
|
|
865
|
+
const openApiPath = (prefix + route.path).replace(/:([A-Za-z0-9_]+)/g, "{$1}");
|
|
866
|
+
if (!paths[openApiPath]) paths[openApiPath] = {};
|
|
867
|
+
const responses = {};
|
|
868
|
+
if (schemas?.responseBody) {
|
|
869
|
+
const responseSchema = this.toJsonSchema(schemas.responseBody, "output");
|
|
870
|
+
responses["200"] = {
|
|
871
|
+
description: responseSchema.description ?? "Successful response",
|
|
872
|
+
content: { "application/json": { schema: responseSchema } }
|
|
873
|
+
};
|
|
874
|
+
} else responses["200"] = { description: "Successful response" };
|
|
875
|
+
if (routeSchema?.responses) for (const resp of routeSchema.responses) {
|
|
876
|
+
const statusCode = String(resp.statusCode);
|
|
877
|
+
const existingResponse = responses[statusCode];
|
|
878
|
+
const existingSchema = existingResponse && "content" in existingResponse ? existingResponse.content?.["application/json"]?.schema : void 0;
|
|
879
|
+
const responseSchema = resp.schema ? this.toJsonSchema(resp.schema, "output") : statusCode === "200" ? existingSchema : void 0;
|
|
880
|
+
responses[statusCode] = {
|
|
881
|
+
...existingResponse,
|
|
882
|
+
description: resp.description ?? responseSchema?.description ?? existingResponse?.description ?? `Response ${resp.statusCode}`,
|
|
883
|
+
...responseSchema ? { content: { "application/json": { schema: responseSchema } } } : {}
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
const operation = {
|
|
887
|
+
responses,
|
|
888
|
+
summary: routeSchema?.summary,
|
|
889
|
+
description: routeSchema?.description,
|
|
890
|
+
tags: controllerSchema?.title ? [controllerSchema.title] : void 0
|
|
891
|
+
};
|
|
892
|
+
const parameters = [];
|
|
893
|
+
if (schemas?.requestParam) {
|
|
894
|
+
const paramSchema = this.toJsonSchema(schemas.requestParam, "input");
|
|
895
|
+
if (paramSchema.type === "object" && paramSchema.properties) for (const [name, schema] of Object.entries(paramSchema.properties)) {
|
|
896
|
+
const parameterSchema = schema;
|
|
897
|
+
parameters.push({
|
|
898
|
+
name,
|
|
899
|
+
in: "path",
|
|
900
|
+
required: true,
|
|
901
|
+
description: parameterSchema.description,
|
|
902
|
+
schema: parameterSchema
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
if (schemas?.requestQuery) {
|
|
907
|
+
const querySchema = this.toJsonSchema(schemas.requestQuery, "input");
|
|
908
|
+
if (querySchema.type === "object" && querySchema.properties) for (const [name, schema] of Object.entries(querySchema.properties)) {
|
|
909
|
+
const isRequired = Array.isArray(querySchema.required) && querySchema.required.includes(name);
|
|
910
|
+
const parameterSchema = schema;
|
|
911
|
+
parameters.push({
|
|
912
|
+
name,
|
|
913
|
+
in: "query",
|
|
914
|
+
required: isRequired,
|
|
915
|
+
description: parameterSchema.description,
|
|
916
|
+
schema: parameterSchema
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
if (parameters.length > 0) operation.parameters = parameters;
|
|
921
|
+
if (schemas?.requestBody) {
|
|
922
|
+
const requestSchema = this.toJsonSchema(schemas.requestBody, "input");
|
|
923
|
+
operation.requestBody = {
|
|
924
|
+
required: true,
|
|
925
|
+
description: requestSchema.description,
|
|
926
|
+
content: { "application/json": { schema: requestSchema } }
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
const method = route.method.toLowerCase();
|
|
930
|
+
paths[openApiPath][method] = operation;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return {
|
|
934
|
+
openapi: this.OPENAPI_VERSION,
|
|
935
|
+
info: {
|
|
936
|
+
title: metadata.title,
|
|
937
|
+
version: metadata.version,
|
|
938
|
+
description: metadata.description
|
|
939
|
+
},
|
|
940
|
+
tags: tags.length > 0 ? tags : void 0,
|
|
941
|
+
paths
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
toJsonSchema(schema, direction = "output") {
|
|
945
|
+
try {
|
|
946
|
+
const jsonSchema = schema["~standard"].jsonSchema;
|
|
947
|
+
return direction === "input" ? jsonSchema.input({ target: "openapi-3.0" }) : jsonSchema.output({ target: "openapi-3.0" });
|
|
948
|
+
} catch (e) {
|
|
949
|
+
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 });
|
|
950
|
+
throw e;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
const openApiGenerator = new OpenAPIGenerator();
|
|
955
|
+
function _registerController(controllerClass, prefix) {
|
|
956
|
+
openApiGenerator.registerController(controllerClass, prefix);
|
|
957
|
+
}
|
|
958
|
+
function generateOpenApiSpec() {
|
|
959
|
+
return openApiGenerator.generateSpec();
|
|
960
|
+
}
|
|
961
|
+
function _clearOpenApiRegistry() {
|
|
962
|
+
openApiGenerator._clearControllers();
|
|
963
|
+
}
|
|
964
|
+
//#endregion
|
|
965
|
+
//#region src/types.ts
|
|
966
|
+
const methodResolve = {
|
|
967
|
+
GET: "get",
|
|
968
|
+
PUT: "put",
|
|
969
|
+
POST: "post",
|
|
970
|
+
DELETE: "delete",
|
|
971
|
+
OPTIONS: "options",
|
|
972
|
+
PATCH: "patch",
|
|
973
|
+
HEAD: "head",
|
|
974
|
+
USE: "use"
|
|
975
|
+
};
|
|
976
|
+
//#endregion
|
|
714
977
|
//#region src/annotation/controller.ts
|
|
715
978
|
const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
|
|
716
979
|
/**
|
|
@@ -722,6 +985,7 @@ const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
|
|
|
722
985
|
function Controller({ prefix = "", deps = [] } = {}) {
|
|
723
986
|
return (target) => {
|
|
724
987
|
const targetClass = target;
|
|
988
|
+
_registerController(target, prefix);
|
|
725
989
|
const router = (0, express.Router)();
|
|
726
990
|
const routes = _getRoutes(target);
|
|
727
991
|
const usedRoutes = /* @__PURE__ */ new Set();
|
|
@@ -746,15 +1010,20 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
|
|
|
746
1010
|
if (method === "USE" && fn.length >= 4) {
|
|
747
1011
|
const middlewareFn = async (err, request, response, next) => {
|
|
748
1012
|
try {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
1013
|
+
await validate({
|
|
1014
|
+
target,
|
|
1015
|
+
fnName,
|
|
1016
|
+
request
|
|
1017
|
+
});
|
|
1018
|
+
await handleResult({
|
|
1019
|
+
result: fn.bind(controllerInstance)(err, request, response, next),
|
|
1020
|
+
response,
|
|
1021
|
+
target,
|
|
1022
|
+
fnName,
|
|
1023
|
+
method,
|
|
1024
|
+
path: path instanceof RegExp ? path.source : fp,
|
|
1025
|
+
isErrorMiddleware: true
|
|
1026
|
+
});
|
|
758
1027
|
} catch (e) {
|
|
759
1028
|
console.error(e);
|
|
760
1029
|
next(e);
|
|
@@ -764,34 +1033,52 @@ Split these into separate @MiddlewareClass classes, or merge the logic into a si
|
|
|
764
1033
|
return;
|
|
765
1034
|
}
|
|
766
1035
|
router[methodName](fp, async (request, response, next) => {
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
}
|
|
780
|
-
const result = await fn.bind(controllerInstance)(request, response, next);
|
|
781
|
-
if (result instanceof ResponseEntity) {
|
|
782
|
-
response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(result.getBody()));
|
|
783
|
-
return;
|
|
784
|
-
}
|
|
785
|
-
if (result instanceof RedirectView) {
|
|
786
|
-
response.redirect(result.getUrl());
|
|
787
|
-
return;
|
|
788
|
-
}
|
|
789
|
-
if (method !== "USE" && !response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${methodName.toUpperCase()} ${path instanceof RegExp ? path.source : fp}`));
|
|
1036
|
+
await validate({
|
|
1037
|
+
target,
|
|
1038
|
+
fnName,
|
|
1039
|
+
request
|
|
1040
|
+
});
|
|
1041
|
+
await handleResult({
|
|
1042
|
+
result: await fn.bind(controllerInstance)(request, response, next),
|
|
1043
|
+
response,
|
|
1044
|
+
target,
|
|
1045
|
+
fnName,
|
|
1046
|
+
method,
|
|
1047
|
+
path: path instanceof RegExp ? path.source : fp
|
|
1048
|
+
});
|
|
790
1049
|
});
|
|
791
1050
|
}
|
|
792
1051
|
_ControllerRegistry.set(targetClass, router);
|
|
793
1052
|
};
|
|
794
1053
|
}
|
|
1054
|
+
async function handleResult({ result, target, fnName, response, method, path, isErrorMiddleware = false }) {
|
|
1055
|
+
const schemas = _getValidatorSchema(target, fnName);
|
|
1056
|
+
if (result instanceof ResponseEntity) {
|
|
1057
|
+
const body = schemas && schemas.responseBody ? await _parseOrThrow(schemas.responseBody, result.getBody(), "resbody", fnName) : result.getBody();
|
|
1058
|
+
response.contentType("application/json").status(result.getStatusCode()).set(result.getHeaders()).send(Sapling.serialize(body));
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
if (result instanceof RedirectView) {
|
|
1062
|
+
response.redirect(result.getUrl());
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
if (!isErrorMiddleware && method !== "USE" && !response.writableEnded) response.status(404).send(Html404ErrorPage(`Cannot ${method} ${path}`));
|
|
1066
|
+
}
|
|
1067
|
+
async function validate({ target, fnName, request }) {
|
|
1068
|
+
const schemas = _getValidatorSchema(target, fnName);
|
|
1069
|
+
if (schemas) {
|
|
1070
|
+
if (schemas.requestBody) request.body = await _parseOrThrow(schemas.requestBody, request.body, "reqbody", fnName);
|
|
1071
|
+
if (schemas.requestParam) request.params = await _parseOrThrow(schemas.requestParam, request.params, "reqparams", fnName);
|
|
1072
|
+
if (schemas.requestQuery) {
|
|
1073
|
+
const parsedQuery = await _parseOrThrow(schemas.requestQuery, request.query, "reqquery", fnName);
|
|
1074
|
+
Object.defineProperty(request, "query", {
|
|
1075
|
+
value: parsedQuery,
|
|
1076
|
+
writable: true,
|
|
1077
|
+
configurable: true
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
795
1082
|
//#endregion
|
|
796
1083
|
//#region src/annotation/middleware.ts
|
|
797
1084
|
/**
|
|
@@ -805,15 +1092,7 @@ function MiddlewareClass(...args) {
|
|
|
805
1092
|
return Controller(...args);
|
|
806
1093
|
}
|
|
807
1094
|
//#endregion
|
|
808
|
-
//#region
|
|
809
|
-
function __decorate(decorators, target, key, desc) {
|
|
810
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
811
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
812
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
813
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
814
|
-
}
|
|
815
|
-
//#endregion
|
|
816
|
-
//#region src/middleware/default/base.ts
|
|
1095
|
+
//#region src/middleware/default/error/base.ts
|
|
817
1096
|
let DefaultBaseErrorMiddleware = class DefaultBaseErrorMiddleware {
|
|
818
1097
|
handle(err, _request, _response, _next) {
|
|
819
1098
|
console.error("[Error]", err);
|
|
@@ -823,7 +1102,20 @@ let DefaultBaseErrorMiddleware = class DefaultBaseErrorMiddleware {
|
|
|
823
1102
|
__decorate([Middleware()], DefaultBaseErrorMiddleware.prototype, "handle", null);
|
|
824
1103
|
DefaultBaseErrorMiddleware = __decorate([MiddlewareClass()], DefaultBaseErrorMiddleware);
|
|
825
1104
|
//#endregion
|
|
826
|
-
//#region src/middleware/default/
|
|
1105
|
+
//#region src/middleware/default/error/parse.ts
|
|
1106
|
+
let DefaultParserErrorMiddleware = class DefaultParserErrorMiddleware {
|
|
1107
|
+
handle(err, _request, _response, next) {
|
|
1108
|
+
if (err instanceof ParserError) {
|
|
1109
|
+
console.warn(err);
|
|
1110
|
+
return ResponseEntity.status(err.status).body({ message: err.message });
|
|
1111
|
+
}
|
|
1112
|
+
next(err);
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
__decorate([Middleware()], DefaultParserErrorMiddleware.prototype, "handle", null);
|
|
1116
|
+
DefaultParserErrorMiddleware = __decorate([MiddlewareClass()], DefaultParserErrorMiddleware);
|
|
1117
|
+
//#endregion
|
|
1118
|
+
//#region src/middleware/default/error/responsestatus.ts
|
|
827
1119
|
let DefaultResponseStatusErrorMiddleware = class DefaultResponseStatusErrorMiddleware {
|
|
828
1120
|
handle(err, _request, _response, next) {
|
|
829
1121
|
if (err instanceof ResponseStatusError) return ResponseEntity.status(err.status).body({ message: err.message });
|
|
@@ -833,7 +1125,108 @@ let DefaultResponseStatusErrorMiddleware = class DefaultResponseStatusErrorMiddl
|
|
|
833
1125
|
__decorate([Middleware()], DefaultResponseStatusErrorMiddleware.prototype, "handle", null);
|
|
834
1126
|
DefaultResponseStatusErrorMiddleware = __decorate([MiddlewareClass()], DefaultResponseStatusErrorMiddleware);
|
|
835
1127
|
//#endregion
|
|
1128
|
+
//#region src/middleware/default/openapi/index.ts
|
|
1129
|
+
let DefaultOpenApiMiddleware = class DefaultOpenApiMiddleware {
|
|
1130
|
+
handle(_request, _response, _next) {
|
|
1131
|
+
return ResponseEntity.ok().body(generateOpenApiSpec());
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
|
+
__decorate([GET(_settings.doc.openApiPath)], DefaultOpenApiMiddleware.prototype, "handle", null);
|
|
1135
|
+
DefaultOpenApiMiddleware = __decorate([MiddlewareClass()], DefaultOpenApiMiddleware);
|
|
1136
|
+
//#endregion
|
|
1137
|
+
//#region src/middleware/default/swagger/index.ts
|
|
1138
|
+
/**
|
|
1139
|
+
* Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
|
|
1140
|
+
*
|
|
1141
|
+
* Configure any middleware-specific settings with `Sapling.Extras.swaggerAndOpenApi`
|
|
1142
|
+
*
|
|
1143
|
+
* You must register `DefaultSwaggerMiddleware.Serve` & `DefaultSwaggerMiddleware.Setup` after `DefaultOpenApiMiddleware`
|
|
1144
|
+
*
|
|
1145
|
+
* ```ts
|
|
1146
|
+
* const middlewares = [
|
|
1147
|
+
* DefaultOpenApiMiddleware,
|
|
1148
|
+
* DefaultSwaggerMiddleware.Serve,
|
|
1149
|
+
* DefaultSwaggerMiddleware.Setup,
|
|
1150
|
+
* ];
|
|
1151
|
+
* middlewares.map(Sapling.resolve).forEach((r) => app.use(r));
|
|
1152
|
+
* ```
|
|
1153
|
+
*/
|
|
1154
|
+
let Serve = class Serve {
|
|
1155
|
+
handlers = swagger_ui_express.default.serve;
|
|
1156
|
+
handle(request, response, next) {
|
|
1157
|
+
return Sapling.chainHandlers(this.handlers, request, response, next);
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
__decorate([Middleware(_settings.doc.swaggerPath)], Serve.prototype, "handle", null);
|
|
1161
|
+
Serve = __decorate([MiddlewareClass()], Serve);
|
|
1162
|
+
/**
|
|
1163
|
+
* Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
|
|
1164
|
+
*
|
|
1165
|
+
* Configure any middleware-specific settings with `Sapling.Extras.swaggerAndOpenApi`
|
|
1166
|
+
*
|
|
1167
|
+
* You must register `DefaultSwaggerMiddleware.Serve` & `DefaultSwaggerMiddleware.Setup` after `DefaultOpenApiMiddleware`
|
|
1168
|
+
*
|
|
1169
|
+
* ```ts
|
|
1170
|
+
* const middlewares = [
|
|
1171
|
+
* DefaultOpenApiMiddleware,
|
|
1172
|
+
* DefaultSwaggerMiddleware.Serve,
|
|
1173
|
+
* DefaultSwaggerMiddleware.Setup,
|
|
1174
|
+
* ];
|
|
1175
|
+
* middlewares.map(Sapling.resolve).forEach((r) => app.use(r));
|
|
1176
|
+
* ```
|
|
1177
|
+
*/
|
|
1178
|
+
let Setup = class Setup {
|
|
1179
|
+
handler;
|
|
1180
|
+
constructor() {
|
|
1181
|
+
this.handler = swagger_ui_express.default.setup(null, { swaggerOptions: { url: _settings.doc.openApiPath } });
|
|
1182
|
+
}
|
|
1183
|
+
handle(request, response, next) {
|
|
1184
|
+
return this.handler(request, response, next);
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1187
|
+
__decorate([Middleware(_settings.doc.swaggerPath)], Setup.prototype, "handle", null);
|
|
1188
|
+
Setup = __decorate([MiddlewareClass()], Setup);
|
|
1189
|
+
/**
|
|
1190
|
+
* Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
|
|
1191
|
+
*
|
|
1192
|
+
* Configure any middleware-specific settings with `Sapling.Extras.swaggerAndOpenApi`
|
|
1193
|
+
*
|
|
1194
|
+
* You must register `DefaultSwaggerMiddleware.Serve` & `DefaultSwaggerMiddleware.Setup` after `DefaultOpenApiMiddleware`
|
|
1195
|
+
*
|
|
1196
|
+
* ```ts
|
|
1197
|
+
* const middlewares = [
|
|
1198
|
+
* DefaultOpenApiMiddleware,
|
|
1199
|
+
* DefaultSwaggerMiddleware.Serve,
|
|
1200
|
+
* DefaultSwaggerMiddleware.Setup,
|
|
1201
|
+
* ];
|
|
1202
|
+
* middlewares.map(Sapling.resolve).forEach((r) => app.use(r));
|
|
1203
|
+
* ```
|
|
1204
|
+
*/
|
|
1205
|
+
const DefaultSwaggerMiddleware = {
|
|
1206
|
+
Serve,
|
|
1207
|
+
Setup
|
|
1208
|
+
};
|
|
1209
|
+
//#endregion
|
|
1210
|
+
//#region src/middleware/default/health/index.ts
|
|
1211
|
+
let DefaultHealthMiddleware = class DefaultHealthMiddleware {
|
|
1212
|
+
constructor(healthRegistrar) {
|
|
1213
|
+
this.healthRegistrar = healthRegistrar;
|
|
1214
|
+
}
|
|
1215
|
+
async readiness(_request, _response, _next) {
|
|
1216
|
+
const up = await this.healthRegistrar.check();
|
|
1217
|
+
return ResponseEntity.ok().body({ up });
|
|
1218
|
+
}
|
|
1219
|
+
async liveness(_request, _response, _next) {
|
|
1220
|
+
const up = await this.healthRegistrar.check();
|
|
1221
|
+
return ResponseEntity.ok().body({ up });
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
__decorate([GET(_settings.health.ready.path)], DefaultHealthMiddleware.prototype, "readiness", null);
|
|
1225
|
+
__decorate([GET(_settings.health.live.path)], DefaultHealthMiddleware.prototype, "liveness", null);
|
|
1226
|
+
DefaultHealthMiddleware = __decorate([MiddlewareClass({ deps: [HealthRegistrar] })], DefaultHealthMiddleware);
|
|
1227
|
+
//#endregion
|
|
836
1228
|
exports.Controller = Controller;
|
|
1229
|
+
exports.ControllerSchema = ControllerSchema;
|
|
837
1230
|
exports.DELETE = DELETE;
|
|
838
1231
|
Object.defineProperty(exports, "DefaultBaseErrorMiddleware", {
|
|
839
1232
|
enumerable: true,
|
|
@@ -841,14 +1234,39 @@ Object.defineProperty(exports, "DefaultBaseErrorMiddleware", {
|
|
|
841
1234
|
return DefaultBaseErrorMiddleware;
|
|
842
1235
|
}
|
|
843
1236
|
});
|
|
1237
|
+
Object.defineProperty(exports, "DefaultHealthMiddleware", {
|
|
1238
|
+
enumerable: true,
|
|
1239
|
+
get: function() {
|
|
1240
|
+
return DefaultHealthMiddleware;
|
|
1241
|
+
}
|
|
1242
|
+
});
|
|
1243
|
+
Object.defineProperty(exports, "DefaultOpenApiMiddleware", {
|
|
1244
|
+
enumerable: true,
|
|
1245
|
+
get: function() {
|
|
1246
|
+
return DefaultOpenApiMiddleware;
|
|
1247
|
+
}
|
|
1248
|
+
});
|
|
1249
|
+
Object.defineProperty(exports, "DefaultParserErrorMiddleware", {
|
|
1250
|
+
enumerable: true,
|
|
1251
|
+
get: function() {
|
|
1252
|
+
return DefaultParserErrorMiddleware;
|
|
1253
|
+
}
|
|
1254
|
+
});
|
|
844
1255
|
Object.defineProperty(exports, "DefaultResponseStatusErrorMiddleware", {
|
|
845
1256
|
enumerable: true,
|
|
846
1257
|
get: function() {
|
|
847
1258
|
return DefaultResponseStatusErrorMiddleware;
|
|
848
1259
|
}
|
|
849
1260
|
});
|
|
1261
|
+
exports.DefaultSwaggerMiddleware = DefaultSwaggerMiddleware;
|
|
850
1262
|
exports.GET = GET;
|
|
851
1263
|
exports.HEAD = HEAD;
|
|
1264
|
+
Object.defineProperty(exports, "HealthRegistrar", {
|
|
1265
|
+
enumerable: true,
|
|
1266
|
+
get: function() {
|
|
1267
|
+
return HealthRegistrar;
|
|
1268
|
+
}
|
|
1269
|
+
});
|
|
852
1270
|
exports.Html404ErrorPage = Html404ErrorPage;
|
|
853
1271
|
exports.HttpStatus = HttpStatus;
|
|
854
1272
|
exports.Injectable = Injectable;
|
|
@@ -863,16 +1281,29 @@ exports.RedirectView = RedirectView;
|
|
|
863
1281
|
exports.RequestBody = RequestBody;
|
|
864
1282
|
exports.RequestParam = RequestParam;
|
|
865
1283
|
exports.RequestQuery = RequestQuery;
|
|
1284
|
+
exports.ResponseBody = ResponseBody;
|
|
866
1285
|
exports.ResponseEntity = ResponseEntity;
|
|
867
1286
|
exports.ResponseEntityBuilder = ResponseEntityBuilder;
|
|
868
1287
|
exports.ResponseStatusError = ResponseStatusError;
|
|
1288
|
+
exports.RouteSchema = RouteSchema;
|
|
869
1289
|
exports.Sapling = Sapling;
|
|
870
1290
|
exports._ControllerRegistry = _ControllerRegistry;
|
|
871
1291
|
exports._InjectableDeps = _InjectableDeps;
|
|
872
1292
|
exports._InjectableRegistry = _InjectableRegistry;
|
|
873
1293
|
exports._Route = _Route;
|
|
874
|
-
exports.
|
|
1294
|
+
exports._clearOpenApiRegistry = _clearOpenApiRegistry;
|
|
1295
|
+
exports._getControllerSchema = _getControllerSchema;
|
|
1296
|
+
exports._getOrCreateSchemaDefinition = _getOrCreateSchemaDefinition;
|
|
1297
|
+
exports._getRouteSchema = _getRouteSchema;
|
|
875
1298
|
exports._getRoutes = _getRoutes;
|
|
1299
|
+
exports._getValidatorSchema = _getValidatorSchema;
|
|
876
1300
|
exports._parseOrThrow = _parseOrThrow;
|
|
1301
|
+
exports._registerController = _registerController;
|
|
877
1302
|
exports._resolve = _resolve;
|
|
1303
|
+
exports._saveValidatorSchema = _saveValidatorSchema;
|
|
1304
|
+
exports._setControllerSchema = _setControllerSchema;
|
|
1305
|
+
exports._setRouteSchema = _setRouteSchema;
|
|
1306
|
+
exports._settings = _settings;
|
|
1307
|
+
exports.generateOpenApiSpec = generateOpenApiSpec;
|
|
878
1308
|
exports.methodResolve = methodResolve;
|
|
1309
|
+
exports.openApiGenerator = openApiGenerator;
|