@tahminator/sapling 2.1.0-beta.e797371f → 2.1.1
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 +59 -3
- package/dist/index.cjs +93 -42
- package/dist/index.d.cts +67 -18
- package/dist/index.d.mts +67 -18
- package/dist/index.mjs +93 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,6 +26,10 @@ A lightweight Express.js dependency injection & route abstraction library.
|
|
|
26
26
|
+ [Automatic Spec Generation](#automatic-spec-generation)
|
|
27
27
|
+ [Adding Documentation with Decorators](#adding-documentation-with-decorators)
|
|
28
28
|
+ [Customizing Paths](#customizing-paths)
|
|
29
|
+
* [Health Checks](#health-checks)
|
|
30
|
+
+ [Liveness and Readiness Probes](#liveness-and-readiness-probes)
|
|
31
|
+
+ [Registering Custom Readiness Checks](#registering-custom-readiness-checks)
|
|
32
|
+
+ [Customizing Health Paths](#customizing-health-paths)
|
|
29
33
|
* [Custom Serialization](#custom-serialization)
|
|
30
34
|
- [Advanced Setup](#advanced-setup)
|
|
31
35
|
* [Automatically import controllers](#automatically-import-controllers)
|
|
@@ -97,9 +101,10 @@ class UserController {
|
|
|
97
101
|
}
|
|
98
102
|
}
|
|
99
103
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
// registerApp returns the app back to you - use the returned app instance
|
|
105
|
+
// so Sapling can inject some post-startup hooks into the app.
|
|
106
|
+
// BUT, you still have full access of app to do whatever you want!
|
|
107
|
+
const app = Sapling.registerApp(express());
|
|
103
108
|
|
|
104
109
|
// @MiddlewareClass should be registered first before @Controller and should be registered in order
|
|
105
110
|
// @Injectable classes will automatically be formed into singletons by Sapling behind the scenes!
|
|
@@ -581,6 +586,57 @@ Sapling.Extras.swaggerAndOpenApi.setOpenApiPath("/api-spec.json");
|
|
|
581
586
|
Sapling.Extras.swaggerAndOpenApi.setSwaggerPath("/api-docs");
|
|
582
587
|
```
|
|
583
588
|
|
|
589
|
+
### Health Checks
|
|
590
|
+
|
|
591
|
+
#### Liveness and Readiness Probes
|
|
592
|
+
|
|
593
|
+
Register `DefaultHealthMiddleware` to expose two health endpoints:
|
|
594
|
+
|
|
595
|
+
- `GET /live` — **liveness probe**: returns `{ up: true }` once the server has started listening. Use this to tell your orchestrator the process is alive.
|
|
596
|
+
- `GET /ready` — **readiness probe**: returns `{ up: true }` once the server is live *and* all registered readiness checks pass. Use this to gate traffic until your app is fully initialized.
|
|
597
|
+
|
|
598
|
+
```typescript
|
|
599
|
+
import { Sapling, DefaultHealthMiddleware } from "@tahminator/sapling";
|
|
600
|
+
|
|
601
|
+
const app = Sapling.registerApp(express());
|
|
602
|
+
|
|
603
|
+
app.use(Sapling.resolve(DefaultHealthMiddleware));
|
|
604
|
+
|
|
605
|
+
app.listen(3000); // _markLive() is called automatically once the server is listening
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
> [!IMPORTANT]
|
|
609
|
+
> `Sapling.registerApp` returns `express.App` - please use that as your `app` object inside of your server. There is a small post-startup hook applied via a proxy, in which the liveness probe is enabled from such.
|
|
610
|
+
|
|
611
|
+
#### Registering Custom Readiness Checks
|
|
612
|
+
|
|
613
|
+
Inject `HealthRegistrar` into any `@Injectable` or controller to add readiness checks. A check is any function returning `boolean | Promise<boolean>`. If any check returns `false` (or throws), `/ready` reports `{ up: false }`.
|
|
614
|
+
|
|
615
|
+
```typescript
|
|
616
|
+
import { Injectable, HealthRegistrar } from "@tahminator/sapling";
|
|
617
|
+
|
|
618
|
+
@Injectable([HealthRegistrar])
|
|
619
|
+
class DatabaseService {
|
|
620
|
+
constructor(
|
|
621
|
+
private readonly db: Database,
|
|
622
|
+
private readonly healthRegistrar: HealthRegistrar,
|
|
623
|
+
) {
|
|
624
|
+
healthRegistrar.add(async () => {
|
|
625
|
+
return this.db.isConnected();
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
#### Customizing Health Paths
|
|
632
|
+
|
|
633
|
+
The default paths are `/live` and `/ready`. Override them before registering `DefaultHealthMiddleware`:
|
|
634
|
+
|
|
635
|
+
```typescript
|
|
636
|
+
Sapling.Extras.health.setLivePath("/healthz/live");
|
|
637
|
+
Sapling.Extras.health.setReadyPath("/healthz/ready");
|
|
638
|
+
```
|
|
639
|
+
|
|
584
640
|
### Custom Serialization
|
|
585
641
|
|
|
586
642
|
By default, Sapling uses `JSON.stringify` and `JSON.parse` for serialization. You can override these with custom serializers like [superjson](https://github.com/flightcontrolhq/superjson#readme) to automatically handle Dates, BigInts, and more:
|
package/dist/index.cjs
CHANGED
|
@@ -401,20 +401,38 @@ function __decorate(decorators, target, key, desc) {
|
|
|
401
401
|
//#region src/middleware/health/registrar.ts
|
|
402
402
|
let HealthRegistrar = class HealthRegistrar {
|
|
403
403
|
_checks;
|
|
404
|
-
|
|
404
|
+
_live;
|
|
405
405
|
constructor() {
|
|
406
406
|
this._checks = [];
|
|
407
|
-
this.
|
|
407
|
+
this._live = false;
|
|
408
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* Add a health check.
|
|
411
|
+
*
|
|
412
|
+
* Health checks will be used to determine whether service can serve traffic or not (a.k.a `readiness`).
|
|
413
|
+
*/
|
|
409
414
|
add(healthCheck) {
|
|
410
415
|
this._checks.push(healthCheck);
|
|
411
416
|
}
|
|
412
|
-
|
|
413
|
-
|
|
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
|
+
_markLive() {
|
|
422
|
+
this._live = true;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* @internal
|
|
426
|
+
*/
|
|
427
|
+
async _liveness() {
|
|
428
|
+
return this._live;
|
|
414
429
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
430
|
+
/**
|
|
431
|
+
* @internal
|
|
432
|
+
*/
|
|
433
|
+
async _readiness() {
|
|
434
|
+
if (!this._live) return false;
|
|
435
|
+
return (await Promise.allSettled(this._checks.map((c) => c()))).every((r) => r.status === "fulfilled" && r.value);
|
|
418
436
|
}
|
|
419
437
|
};
|
|
420
438
|
HealthRegistrar = __decorate([Injectable()], HealthRegistrar);
|
|
@@ -423,7 +441,10 @@ HealthRegistrar = __decorate([Injectable()], HealthRegistrar);
|
|
|
423
441
|
const _settings = {
|
|
424
442
|
serialize: JSON.stringify,
|
|
425
443
|
deserialize: JSON.parse,
|
|
426
|
-
health: {
|
|
444
|
+
health: {
|
|
445
|
+
ready: { path: "/readyz" },
|
|
446
|
+
live: { path: "/livez" }
|
|
447
|
+
},
|
|
427
448
|
doc: {
|
|
428
449
|
openApiPath: "/openapi.json",
|
|
429
450
|
swaggerPath: "/swagger.html",
|
|
@@ -491,9 +512,8 @@ var Sapling = class Sapling {
|
|
|
491
512
|
* import { Sapling } from "@tahminator/sapling";
|
|
492
513
|
* import express from "express";
|
|
493
514
|
*
|
|
494
|
-
*
|
|
495
|
-
*
|
|
496
|
-
* app.registerApp(app);
|
|
515
|
+
* // returns the exact same `express.App` type back to you!
|
|
516
|
+
* const app = Sapling.registerApp(express());
|
|
497
517
|
* ```
|
|
498
518
|
*/
|
|
499
519
|
static registerApp(app) {
|
|
@@ -505,7 +525,7 @@ var Sapling = class Sapling {
|
|
|
505
525
|
return function(...args) {
|
|
506
526
|
const server = originalListen.apply(target, args);
|
|
507
527
|
server.once("listening", () => {
|
|
508
|
-
Sapling.
|
|
528
|
+
Sapling._onPostStartup();
|
|
509
529
|
console.log("Sapling successfully initialized post-startup hooks on server start");
|
|
510
530
|
});
|
|
511
531
|
return server;
|
|
@@ -514,8 +534,12 @@ var Sapling = class Sapling {
|
|
|
514
534
|
return Reflect.get(target, prop, receiver);
|
|
515
535
|
} });
|
|
516
536
|
}
|
|
517
|
-
|
|
518
|
-
|
|
537
|
+
/**
|
|
538
|
+
* @internal
|
|
539
|
+
* visible for testing
|
|
540
|
+
*/
|
|
541
|
+
static _onPostStartup() {
|
|
542
|
+
_InjectableRegistry.get(HealthRegistrar)?._markLive();
|
|
519
543
|
}
|
|
520
544
|
/**
|
|
521
545
|
* Serialize a value into a JSON string.
|
|
@@ -556,37 +580,59 @@ var Sapling = class Sapling {
|
|
|
556
580
|
/**
|
|
557
581
|
* Modify extra settings
|
|
558
582
|
*/
|
|
559
|
-
static Extras = {
|
|
560
|
-
/**
|
|
561
|
-
* Modify default settings applied to OpenAPI & Swagger
|
|
562
|
-
*/
|
|
563
|
-
swaggerAndOpenApi: {
|
|
583
|
+
static Extras = {
|
|
564
584
|
/**
|
|
565
|
-
*
|
|
566
|
-
*
|
|
567
|
-
* @default { title: "API", version: "1.0.0" }
|
|
585
|
+
* Modify default settings applied to OpenAPI & Swagger
|
|
568
586
|
*/
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
587
|
+
swaggerAndOpenApi: {
|
|
588
|
+
/**
|
|
589
|
+
* Set base OpenAPI metadata values.
|
|
590
|
+
*
|
|
591
|
+
* @default { title: "API", version: "1.0.0" }
|
|
592
|
+
*/
|
|
593
|
+
setMetadata(metadata) {
|
|
594
|
+
_settings.doc.metadata = metadata;
|
|
595
|
+
},
|
|
596
|
+
/**
|
|
597
|
+
* change default endpoint that will serve OpenAPI spec.
|
|
598
|
+
* Swagger will also load this endpoint on load.
|
|
599
|
+
*
|
|
600
|
+
* @default `/openapi.json`
|
|
601
|
+
*/
|
|
602
|
+
setOpenApiPath(path) {
|
|
603
|
+
_settings.doc.openApiPath = path;
|
|
604
|
+
},
|
|
605
|
+
/**
|
|
606
|
+
* change Swagger endpoint.
|
|
607
|
+
*
|
|
608
|
+
* @default `/swagger.html`
|
|
609
|
+
*/
|
|
610
|
+
setSwaggerPath(path) {
|
|
611
|
+
_settings.doc.swaggerPath = path;
|
|
612
|
+
}
|
|
580
613
|
},
|
|
581
614
|
/**
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
* @default `/swagger.html`
|
|
615
|
+
* Modify default settings applied to health / readiness / liveness.
|
|
585
616
|
*/
|
|
586
|
-
|
|
587
|
-
|
|
617
|
+
health: {
|
|
618
|
+
/**
|
|
619
|
+
* change default endpoint that ready endpoint will be served on.
|
|
620
|
+
*
|
|
621
|
+
* @default `/readyz`
|
|
622
|
+
*/
|
|
623
|
+
setReadyPath(path) {
|
|
624
|
+
_settings.health.ready.path = path;
|
|
625
|
+
},
|
|
626
|
+
/**
|
|
627
|
+
* change default endpoint that live endpoint will be served on.
|
|
628
|
+
*
|
|
629
|
+
* @default `/livez`
|
|
630
|
+
*/
|
|
631
|
+
setLivePath(path) {
|
|
632
|
+
_settings.health.live.path = path;
|
|
633
|
+
}
|
|
588
634
|
}
|
|
589
|
-
}
|
|
635
|
+
};
|
|
590
636
|
/**
|
|
591
637
|
* This method can be used in a `@MiddlewareClass` to register any libraries
|
|
592
638
|
* that expect you to register multiple registers at once. An example is `swagger-ui-express`
|
|
@@ -1179,12 +1225,17 @@ let DefaultHealthMiddleware = class DefaultHealthMiddleware {
|
|
|
1179
1225
|
constructor(healthRegistrar) {
|
|
1180
1226
|
this.healthRegistrar = healthRegistrar;
|
|
1181
1227
|
}
|
|
1182
|
-
async
|
|
1183
|
-
const up = await this.healthRegistrar.
|
|
1228
|
+
async readiness(_request, _response, _next) {
|
|
1229
|
+
const up = await this.healthRegistrar._readiness();
|
|
1230
|
+
return ResponseEntity.ok().body({ up });
|
|
1231
|
+
}
|
|
1232
|
+
async liveness(_request, _response, _next) {
|
|
1233
|
+
const up = await this.healthRegistrar._liveness();
|
|
1184
1234
|
return ResponseEntity.ok().body({ up });
|
|
1185
1235
|
}
|
|
1186
1236
|
};
|
|
1187
|
-
__decorate([GET(_settings.health.path)], DefaultHealthMiddleware.prototype, "
|
|
1237
|
+
__decorate([GET(_settings.health.ready.path)], DefaultHealthMiddleware.prototype, "readiness", null);
|
|
1238
|
+
__decorate([GET(_settings.health.live.path)], DefaultHealthMiddleware.prototype, "liveness", null);
|
|
1188
1239
|
DefaultHealthMiddleware = __decorate([MiddlewareClass({ deps: [HealthRegistrar] })], DefaultHealthMiddleware);
|
|
1189
1240
|
//#endregion
|
|
1190
1241
|
exports.Controller = Controller;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import e, { ErrorRequestHandler, NextFunction, Request
|
|
1
|
+
import e, { ErrorRequestHandler, NextFunction, Request, RequestHandler, Response as Response$1, Router } from "express";
|
|
2
2
|
|
|
3
3
|
//#region src/html/404.d.ts
|
|
4
4
|
/**
|
|
@@ -26,7 +26,7 @@ type RouteDefinition = {
|
|
|
26
26
|
};
|
|
27
27
|
type Class<T> = new (...args: any[]) => T;
|
|
28
28
|
type HttpHeaders = Record<string, string>;
|
|
29
|
-
type ExpressMiddlewareFn = ($1: Request
|
|
29
|
+
type ExpressMiddlewareFn = ($1: Request, $2: Response$1, $3: NextFunction) => void;
|
|
30
30
|
//#endregion
|
|
31
31
|
//#region src/annotation/controller.d.ts
|
|
32
32
|
declare const _ControllerRegistry: WeakMap<Function, Router | ErrorRequestHandler>;
|
|
@@ -795,7 +795,12 @@ type Settings = {
|
|
|
795
795
|
serialize: (value: any) => string;
|
|
796
796
|
deserialize: (value: string) => any;
|
|
797
797
|
health: {
|
|
798
|
-
|
|
798
|
+
ready: {
|
|
799
|
+
path: string;
|
|
800
|
+
};
|
|
801
|
+
live: {
|
|
802
|
+
path: string;
|
|
803
|
+
};
|
|
799
804
|
};
|
|
800
805
|
doc: {
|
|
801
806
|
openApiPath: string;
|
|
@@ -843,13 +848,16 @@ declare class Sapling {
|
|
|
843
848
|
* import { Sapling } from "@tahminator/sapling";
|
|
844
849
|
* import express from "express";
|
|
845
850
|
*
|
|
846
|
-
*
|
|
847
|
-
*
|
|
848
|
-
* app.registerApp(app);
|
|
851
|
+
* // returns the exact same `express.App` type back to you!
|
|
852
|
+
* const app = Sapling.registerApp(express());
|
|
849
853
|
* ```
|
|
850
854
|
*/
|
|
851
855
|
static registerApp(app: e.Express): e.Express;
|
|
852
|
-
|
|
856
|
+
/**
|
|
857
|
+
* @internal
|
|
858
|
+
* visible for testing
|
|
859
|
+
*/
|
|
860
|
+
static _onPostStartup(): void;
|
|
853
861
|
/**
|
|
854
862
|
* Serialize a value into a JSON string.
|
|
855
863
|
*
|
|
@@ -906,6 +914,23 @@ declare class Sapling {
|
|
|
906
914
|
*/
|
|
907
915
|
setSwaggerPath(this: void, path: string): void;
|
|
908
916
|
};
|
|
917
|
+
/**
|
|
918
|
+
* Modify default settings applied to health / readiness / liveness.
|
|
919
|
+
*/
|
|
920
|
+
health: {
|
|
921
|
+
/**
|
|
922
|
+
* change default endpoint that ready endpoint will be served on.
|
|
923
|
+
*
|
|
924
|
+
* @default `/readyz`
|
|
925
|
+
*/
|
|
926
|
+
setReadyPath(this: void, path: string): void;
|
|
927
|
+
/**
|
|
928
|
+
* change default endpoint that live endpoint will be served on.
|
|
929
|
+
*
|
|
930
|
+
* @default `/livez`
|
|
931
|
+
*/
|
|
932
|
+
setLivePath(this: void, path: string): void;
|
|
933
|
+
};
|
|
909
934
|
};
|
|
910
935
|
/**
|
|
911
936
|
* This method can be used in a `@MiddlewareClass` to register any libraries
|
|
@@ -926,7 +951,7 @@ declare class Sapling {
|
|
|
926
951
|
* }
|
|
927
952
|
* ```
|
|
928
953
|
*/
|
|
929
|
-
static chainHandlers(this: void, handlers: RequestHandler[], request: Request
|
|
954
|
+
static chainHandlers(this: void, handlers: RequestHandler[], request: Request, response: Response$1, next: NextFunction, index?: number): void;
|
|
930
955
|
}
|
|
931
956
|
//#endregion
|
|
932
957
|
//#region src/annotation/validator.d.ts
|
|
@@ -982,7 +1007,7 @@ declare function _getControllerSchema(ctor: Function): ControllerSchemaDefinitio
|
|
|
982
1007
|
* If the default is not suitable, you may also easily write your own.
|
|
983
1008
|
*/
|
|
984
1009
|
declare class DefaultBaseErrorMiddleware {
|
|
985
|
-
handle(err: unknown, _request: Request
|
|
1010
|
+
handle(err: unknown, _request: Request, _response: Response$1, _next: NextFunction): ResponseEntity<{
|
|
986
1011
|
message: string;
|
|
987
1012
|
}>;
|
|
988
1013
|
}
|
|
@@ -993,7 +1018,7 @@ declare class DefaultBaseErrorMiddleware {
|
|
|
993
1018
|
* If the default is not suitable, you may also easily write your own.
|
|
994
1019
|
*/
|
|
995
1020
|
declare class DefaultParserErrorMiddleware {
|
|
996
|
-
handle(err: unknown, _request: Request
|
|
1021
|
+
handle(err: unknown, _request: Request, _response: Response$1, next: NextFunction): ResponseEntity<{
|
|
997
1022
|
message: string;
|
|
998
1023
|
}> | undefined;
|
|
999
1024
|
}
|
|
@@ -1004,7 +1029,7 @@ declare class DefaultParserErrorMiddleware {
|
|
|
1004
1029
|
* If the default is not suitable, you may also easily write your own.
|
|
1005
1030
|
*/
|
|
1006
1031
|
declare class DefaultResponseStatusErrorMiddleware {
|
|
1007
|
-
handle(err: unknown, _request: Request
|
|
1032
|
+
handle(err: unknown, _request: Request, _response: Response$1, next: NextFunction): ResponseEntity<{
|
|
1008
1033
|
message: string;
|
|
1009
1034
|
}> | undefined;
|
|
1010
1035
|
}
|
|
@@ -1027,7 +1052,7 @@ declare class DefaultResponseStatusErrorMiddleware {
|
|
|
1027
1052
|
* ```
|
|
1028
1053
|
*/
|
|
1029
1054
|
declare class DefaultOpenApiMiddleware {
|
|
1030
|
-
handle(_request: Request
|
|
1055
|
+
handle(_request: Request, _response: Response$1, _next: NextFunction): ResponseEntity<OpenAPIV3.Document<{}>>;
|
|
1031
1056
|
}
|
|
1032
1057
|
//#endregion
|
|
1033
1058
|
//#region src/middleware/default/swagger/index.d.ts
|
|
@@ -1049,7 +1074,7 @@ declare class DefaultOpenApiMiddleware {
|
|
|
1049
1074
|
*/
|
|
1050
1075
|
declare class Serve {
|
|
1051
1076
|
private readonly handlers;
|
|
1052
|
-
handle(request: Request
|
|
1077
|
+
handle(request: Request, response: Response$1, next: NextFunction): void;
|
|
1053
1078
|
}
|
|
1054
1079
|
/**
|
|
1055
1080
|
* Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
|
|
@@ -1070,7 +1095,7 @@ declare class Serve {
|
|
|
1070
1095
|
declare class Setup {
|
|
1071
1096
|
private readonly handler;
|
|
1072
1097
|
constructor();
|
|
1073
|
-
handle(request: Request
|
|
1098
|
+
handle(request: Request, response: Response$1, next: NextFunction): unknown;
|
|
1074
1099
|
}
|
|
1075
1100
|
/**
|
|
1076
1101
|
* Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
|
|
@@ -1097,18 +1122,42 @@ declare const DefaultSwaggerMiddleware: {
|
|
|
1097
1122
|
type HealthCheck = () => boolean | Promise<boolean>;
|
|
1098
1123
|
declare class HealthRegistrar {
|
|
1099
1124
|
private _checks;
|
|
1100
|
-
private
|
|
1125
|
+
private _live;
|
|
1101
1126
|
constructor();
|
|
1127
|
+
/**
|
|
1128
|
+
* Add a health check.
|
|
1129
|
+
*
|
|
1130
|
+
* Health checks will be used to determine whether service can serve traffic or not (a.k.a `readiness`).
|
|
1131
|
+
*/
|
|
1102
1132
|
add(healthCheck: HealthCheck): void;
|
|
1103
|
-
|
|
1104
|
-
|
|
1133
|
+
/**
|
|
1134
|
+
* @internal used by Sapling library, used to determine once all
|
|
1135
|
+
* checks have been registered and server is, at the very least, alive.
|
|
1136
|
+
*/
|
|
1137
|
+
_markLive(): void;
|
|
1138
|
+
/**
|
|
1139
|
+
* @internal
|
|
1140
|
+
*/
|
|
1141
|
+
_liveness(): Promise<boolean>;
|
|
1142
|
+
/**
|
|
1143
|
+
* @internal
|
|
1144
|
+
*/
|
|
1145
|
+
_readiness(): Promise<boolean>;
|
|
1105
1146
|
}
|
|
1106
1147
|
//#endregion
|
|
1107
1148
|
//#region src/middleware/default/health/index.d.ts
|
|
1149
|
+
/**
|
|
1150
|
+
* Enable the serving of `ready` and `live` endpoints.
|
|
1151
|
+
*
|
|
1152
|
+
* Configure any middleware-specific settings with `Sapling.Extras.health`
|
|
1153
|
+
*/
|
|
1108
1154
|
declare class DefaultHealthMiddleware {
|
|
1109
1155
|
private readonly healthRegistrar;
|
|
1110
1156
|
constructor(healthRegistrar: HealthRegistrar);
|
|
1111
|
-
|
|
1157
|
+
readiness(_request: Request, _response: Response$1, _next: NextFunction): Promise<ResponseEntity<{
|
|
1158
|
+
up: boolean;
|
|
1159
|
+
}>>;
|
|
1160
|
+
liveness(_request: Request, _response: Response$1, _next: NextFunction): Promise<ResponseEntity<{
|
|
1112
1161
|
up: boolean;
|
|
1113
1162
|
}>>;
|
|
1114
1163
|
}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import e, { ErrorRequestHandler, NextFunction, Request
|
|
1
|
+
import e, { ErrorRequestHandler, NextFunction, Request, RequestHandler, Response as Response$1, Router } from "express";
|
|
2
2
|
|
|
3
3
|
//#region src/html/404.d.ts
|
|
4
4
|
/**
|
|
@@ -26,7 +26,7 @@ type RouteDefinition = {
|
|
|
26
26
|
};
|
|
27
27
|
type Class<T> = new (...args: any[]) => T;
|
|
28
28
|
type HttpHeaders = Record<string, string>;
|
|
29
|
-
type ExpressMiddlewareFn = ($1: Request
|
|
29
|
+
type ExpressMiddlewareFn = ($1: Request, $2: Response$1, $3: NextFunction) => void;
|
|
30
30
|
//#endregion
|
|
31
31
|
//#region src/annotation/controller.d.ts
|
|
32
32
|
declare const _ControllerRegistry: WeakMap<Function, Router | ErrorRequestHandler>;
|
|
@@ -795,7 +795,12 @@ type Settings = {
|
|
|
795
795
|
serialize: (value: any) => string;
|
|
796
796
|
deserialize: (value: string) => any;
|
|
797
797
|
health: {
|
|
798
|
-
|
|
798
|
+
ready: {
|
|
799
|
+
path: string;
|
|
800
|
+
};
|
|
801
|
+
live: {
|
|
802
|
+
path: string;
|
|
803
|
+
};
|
|
799
804
|
};
|
|
800
805
|
doc: {
|
|
801
806
|
openApiPath: string;
|
|
@@ -843,13 +848,16 @@ declare class Sapling {
|
|
|
843
848
|
* import { Sapling } from "@tahminator/sapling";
|
|
844
849
|
* import express from "express";
|
|
845
850
|
*
|
|
846
|
-
*
|
|
847
|
-
*
|
|
848
|
-
* app.registerApp(app);
|
|
851
|
+
* // returns the exact same `express.App` type back to you!
|
|
852
|
+
* const app = Sapling.registerApp(express());
|
|
849
853
|
* ```
|
|
850
854
|
*/
|
|
851
855
|
static registerApp(app: e.Express): e.Express;
|
|
852
|
-
|
|
856
|
+
/**
|
|
857
|
+
* @internal
|
|
858
|
+
* visible for testing
|
|
859
|
+
*/
|
|
860
|
+
static _onPostStartup(): void;
|
|
853
861
|
/**
|
|
854
862
|
* Serialize a value into a JSON string.
|
|
855
863
|
*
|
|
@@ -906,6 +914,23 @@ declare class Sapling {
|
|
|
906
914
|
*/
|
|
907
915
|
setSwaggerPath(this: void, path: string): void;
|
|
908
916
|
};
|
|
917
|
+
/**
|
|
918
|
+
* Modify default settings applied to health / readiness / liveness.
|
|
919
|
+
*/
|
|
920
|
+
health: {
|
|
921
|
+
/**
|
|
922
|
+
* change default endpoint that ready endpoint will be served on.
|
|
923
|
+
*
|
|
924
|
+
* @default `/readyz`
|
|
925
|
+
*/
|
|
926
|
+
setReadyPath(this: void, path: string): void;
|
|
927
|
+
/**
|
|
928
|
+
* change default endpoint that live endpoint will be served on.
|
|
929
|
+
*
|
|
930
|
+
* @default `/livez`
|
|
931
|
+
*/
|
|
932
|
+
setLivePath(this: void, path: string): void;
|
|
933
|
+
};
|
|
909
934
|
};
|
|
910
935
|
/**
|
|
911
936
|
* This method can be used in a `@MiddlewareClass` to register any libraries
|
|
@@ -926,7 +951,7 @@ declare class Sapling {
|
|
|
926
951
|
* }
|
|
927
952
|
* ```
|
|
928
953
|
*/
|
|
929
|
-
static chainHandlers(this: void, handlers: RequestHandler[], request: Request
|
|
954
|
+
static chainHandlers(this: void, handlers: RequestHandler[], request: Request, response: Response$1, next: NextFunction, index?: number): void;
|
|
930
955
|
}
|
|
931
956
|
//#endregion
|
|
932
957
|
//#region src/annotation/validator.d.ts
|
|
@@ -982,7 +1007,7 @@ declare function _getControllerSchema(ctor: Function): ControllerSchemaDefinitio
|
|
|
982
1007
|
* If the default is not suitable, you may also easily write your own.
|
|
983
1008
|
*/
|
|
984
1009
|
declare class DefaultBaseErrorMiddleware {
|
|
985
|
-
handle(err: unknown, _request: Request
|
|
1010
|
+
handle(err: unknown, _request: Request, _response: Response$1, _next: NextFunction): ResponseEntity<{
|
|
986
1011
|
message: string;
|
|
987
1012
|
}>;
|
|
988
1013
|
}
|
|
@@ -993,7 +1018,7 @@ declare class DefaultBaseErrorMiddleware {
|
|
|
993
1018
|
* If the default is not suitable, you may also easily write your own.
|
|
994
1019
|
*/
|
|
995
1020
|
declare class DefaultParserErrorMiddleware {
|
|
996
|
-
handle(err: unknown, _request: Request
|
|
1021
|
+
handle(err: unknown, _request: Request, _response: Response$1, next: NextFunction): ResponseEntity<{
|
|
997
1022
|
message: string;
|
|
998
1023
|
}> | undefined;
|
|
999
1024
|
}
|
|
@@ -1004,7 +1029,7 @@ declare class DefaultParserErrorMiddleware {
|
|
|
1004
1029
|
* If the default is not suitable, you may also easily write your own.
|
|
1005
1030
|
*/
|
|
1006
1031
|
declare class DefaultResponseStatusErrorMiddleware {
|
|
1007
|
-
handle(err: unknown, _request: Request
|
|
1032
|
+
handle(err: unknown, _request: Request, _response: Response$1, next: NextFunction): ResponseEntity<{
|
|
1008
1033
|
message: string;
|
|
1009
1034
|
}> | undefined;
|
|
1010
1035
|
}
|
|
@@ -1027,7 +1052,7 @@ declare class DefaultResponseStatusErrorMiddleware {
|
|
|
1027
1052
|
* ```
|
|
1028
1053
|
*/
|
|
1029
1054
|
declare class DefaultOpenApiMiddleware {
|
|
1030
|
-
handle(_request: Request
|
|
1055
|
+
handle(_request: Request, _response: Response$1, _next: NextFunction): ResponseEntity<OpenAPIV3.Document<{}>>;
|
|
1031
1056
|
}
|
|
1032
1057
|
//#endregion
|
|
1033
1058
|
//#region src/middleware/default/swagger/index.d.ts
|
|
@@ -1049,7 +1074,7 @@ declare class DefaultOpenApiMiddleware {
|
|
|
1049
1074
|
*/
|
|
1050
1075
|
declare class Serve {
|
|
1051
1076
|
private readonly handlers;
|
|
1052
|
-
handle(request: Request
|
|
1077
|
+
handle(request: Request, response: Response$1, next: NextFunction): void;
|
|
1053
1078
|
}
|
|
1054
1079
|
/**
|
|
1055
1080
|
* Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
|
|
@@ -1070,7 +1095,7 @@ declare class Serve {
|
|
|
1070
1095
|
declare class Setup {
|
|
1071
1096
|
private readonly handler;
|
|
1072
1097
|
constructor();
|
|
1073
|
-
handle(request: Request
|
|
1098
|
+
handle(request: Request, response: Response$1, next: NextFunction): unknown;
|
|
1074
1099
|
}
|
|
1075
1100
|
/**
|
|
1076
1101
|
* Enable the serving of the Swagger endpoint used to serve the OpenAPI spec generated by Sapling.
|
|
@@ -1097,18 +1122,42 @@ declare const DefaultSwaggerMiddleware: {
|
|
|
1097
1122
|
type HealthCheck = () => boolean | Promise<boolean>;
|
|
1098
1123
|
declare class HealthRegistrar {
|
|
1099
1124
|
private _checks;
|
|
1100
|
-
private
|
|
1125
|
+
private _live;
|
|
1101
1126
|
constructor();
|
|
1127
|
+
/**
|
|
1128
|
+
* Add a health check.
|
|
1129
|
+
*
|
|
1130
|
+
* Health checks will be used to determine whether service can serve traffic or not (a.k.a `readiness`).
|
|
1131
|
+
*/
|
|
1102
1132
|
add(healthCheck: HealthCheck): void;
|
|
1103
|
-
|
|
1104
|
-
|
|
1133
|
+
/**
|
|
1134
|
+
* @internal used by Sapling library, used to determine once all
|
|
1135
|
+
* checks have been registered and server is, at the very least, alive.
|
|
1136
|
+
*/
|
|
1137
|
+
_markLive(): void;
|
|
1138
|
+
/**
|
|
1139
|
+
* @internal
|
|
1140
|
+
*/
|
|
1141
|
+
_liveness(): Promise<boolean>;
|
|
1142
|
+
/**
|
|
1143
|
+
* @internal
|
|
1144
|
+
*/
|
|
1145
|
+
_readiness(): Promise<boolean>;
|
|
1105
1146
|
}
|
|
1106
1147
|
//#endregion
|
|
1107
1148
|
//#region src/middleware/default/health/index.d.ts
|
|
1149
|
+
/**
|
|
1150
|
+
* Enable the serving of `ready` and `live` endpoints.
|
|
1151
|
+
*
|
|
1152
|
+
* Configure any middleware-specific settings with `Sapling.Extras.health`
|
|
1153
|
+
*/
|
|
1108
1154
|
declare class DefaultHealthMiddleware {
|
|
1109
1155
|
private readonly healthRegistrar;
|
|
1110
1156
|
constructor(healthRegistrar: HealthRegistrar);
|
|
1111
|
-
|
|
1157
|
+
readiness(_request: Request, _response: Response$1, _next: NextFunction): Promise<ResponseEntity<{
|
|
1158
|
+
up: boolean;
|
|
1159
|
+
}>>;
|
|
1160
|
+
liveness(_request: Request, _response: Response$1, _next: NextFunction): Promise<ResponseEntity<{
|
|
1112
1161
|
up: boolean;
|
|
1113
1162
|
}>>;
|
|
1114
1163
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -376,20 +376,38 @@ function __decorate(decorators, target, key, desc) {
|
|
|
376
376
|
//#region src/middleware/health/registrar.ts
|
|
377
377
|
let HealthRegistrar = class HealthRegistrar {
|
|
378
378
|
_checks;
|
|
379
|
-
|
|
379
|
+
_live;
|
|
380
380
|
constructor() {
|
|
381
381
|
this._checks = [];
|
|
382
|
-
this.
|
|
382
|
+
this._live = false;
|
|
383
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* Add a health check.
|
|
386
|
+
*
|
|
387
|
+
* Health checks will be used to determine whether service can serve traffic or not (a.k.a `readiness`).
|
|
388
|
+
*/
|
|
384
389
|
add(healthCheck) {
|
|
385
390
|
this._checks.push(healthCheck);
|
|
386
391
|
}
|
|
387
|
-
|
|
388
|
-
|
|
392
|
+
/**
|
|
393
|
+
* @internal used by Sapling library, used to determine once all
|
|
394
|
+
* checks have been registered and server is, at the very least, alive.
|
|
395
|
+
*/
|
|
396
|
+
_markLive() {
|
|
397
|
+
this._live = true;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* @internal
|
|
401
|
+
*/
|
|
402
|
+
async _liveness() {
|
|
403
|
+
return this._live;
|
|
389
404
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
405
|
+
/**
|
|
406
|
+
* @internal
|
|
407
|
+
*/
|
|
408
|
+
async _readiness() {
|
|
409
|
+
if (!this._live) return false;
|
|
410
|
+
return (await Promise.allSettled(this._checks.map((c) => c()))).every((r) => r.status === "fulfilled" && r.value);
|
|
393
411
|
}
|
|
394
412
|
};
|
|
395
413
|
HealthRegistrar = __decorate([Injectable()], HealthRegistrar);
|
|
@@ -398,7 +416,10 @@ HealthRegistrar = __decorate([Injectable()], HealthRegistrar);
|
|
|
398
416
|
const _settings = {
|
|
399
417
|
serialize: JSON.stringify,
|
|
400
418
|
deserialize: JSON.parse,
|
|
401
|
-
health: {
|
|
419
|
+
health: {
|
|
420
|
+
ready: { path: "/readyz" },
|
|
421
|
+
live: { path: "/livez" }
|
|
422
|
+
},
|
|
402
423
|
doc: {
|
|
403
424
|
openApiPath: "/openapi.json",
|
|
404
425
|
swaggerPath: "/swagger.html",
|
|
@@ -466,9 +487,8 @@ var Sapling = class Sapling {
|
|
|
466
487
|
* import { Sapling } from "@tahminator/sapling";
|
|
467
488
|
* import express from "express";
|
|
468
489
|
*
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
* app.registerApp(app);
|
|
490
|
+
* // returns the exact same `express.App` type back to you!
|
|
491
|
+
* const app = Sapling.registerApp(express());
|
|
472
492
|
* ```
|
|
473
493
|
*/
|
|
474
494
|
static registerApp(app) {
|
|
@@ -480,7 +500,7 @@ var Sapling = class Sapling {
|
|
|
480
500
|
return function(...args) {
|
|
481
501
|
const server = originalListen.apply(target, args);
|
|
482
502
|
server.once("listening", () => {
|
|
483
|
-
Sapling.
|
|
503
|
+
Sapling._onPostStartup();
|
|
484
504
|
console.log("Sapling successfully initialized post-startup hooks on server start");
|
|
485
505
|
});
|
|
486
506
|
return server;
|
|
@@ -489,8 +509,12 @@ var Sapling = class Sapling {
|
|
|
489
509
|
return Reflect.get(target, prop, receiver);
|
|
490
510
|
} });
|
|
491
511
|
}
|
|
492
|
-
|
|
493
|
-
|
|
512
|
+
/**
|
|
513
|
+
* @internal
|
|
514
|
+
* visible for testing
|
|
515
|
+
*/
|
|
516
|
+
static _onPostStartup() {
|
|
517
|
+
_InjectableRegistry.get(HealthRegistrar)?._markLive();
|
|
494
518
|
}
|
|
495
519
|
/**
|
|
496
520
|
* Serialize a value into a JSON string.
|
|
@@ -531,37 +555,59 @@ var Sapling = class Sapling {
|
|
|
531
555
|
/**
|
|
532
556
|
* Modify extra settings
|
|
533
557
|
*/
|
|
534
|
-
static Extras = {
|
|
535
|
-
/**
|
|
536
|
-
* Modify default settings applied to OpenAPI & Swagger
|
|
537
|
-
*/
|
|
538
|
-
swaggerAndOpenApi: {
|
|
558
|
+
static Extras = {
|
|
539
559
|
/**
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
* @default { title: "API", version: "1.0.0" }
|
|
560
|
+
* Modify default settings applied to OpenAPI & Swagger
|
|
543
561
|
*/
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
562
|
+
swaggerAndOpenApi: {
|
|
563
|
+
/**
|
|
564
|
+
* Set base OpenAPI metadata values.
|
|
565
|
+
*
|
|
566
|
+
* @default { title: "API", version: "1.0.0" }
|
|
567
|
+
*/
|
|
568
|
+
setMetadata(metadata) {
|
|
569
|
+
_settings.doc.metadata = metadata;
|
|
570
|
+
},
|
|
571
|
+
/**
|
|
572
|
+
* change default endpoint that will serve OpenAPI spec.
|
|
573
|
+
* Swagger will also load this endpoint on load.
|
|
574
|
+
*
|
|
575
|
+
* @default `/openapi.json`
|
|
576
|
+
*/
|
|
577
|
+
setOpenApiPath(path) {
|
|
578
|
+
_settings.doc.openApiPath = path;
|
|
579
|
+
},
|
|
580
|
+
/**
|
|
581
|
+
* change Swagger endpoint.
|
|
582
|
+
*
|
|
583
|
+
* @default `/swagger.html`
|
|
584
|
+
*/
|
|
585
|
+
setSwaggerPath(path) {
|
|
586
|
+
_settings.doc.swaggerPath = path;
|
|
587
|
+
}
|
|
555
588
|
},
|
|
556
589
|
/**
|
|
557
|
-
*
|
|
558
|
-
*
|
|
559
|
-
* @default `/swagger.html`
|
|
590
|
+
* Modify default settings applied to health / readiness / liveness.
|
|
560
591
|
*/
|
|
561
|
-
|
|
562
|
-
|
|
592
|
+
health: {
|
|
593
|
+
/**
|
|
594
|
+
* change default endpoint that ready endpoint will be served on.
|
|
595
|
+
*
|
|
596
|
+
* @default `/readyz`
|
|
597
|
+
*/
|
|
598
|
+
setReadyPath(path) {
|
|
599
|
+
_settings.health.ready.path = path;
|
|
600
|
+
},
|
|
601
|
+
/**
|
|
602
|
+
* change default endpoint that live endpoint will be served on.
|
|
603
|
+
*
|
|
604
|
+
* @default `/livez`
|
|
605
|
+
*/
|
|
606
|
+
setLivePath(path) {
|
|
607
|
+
_settings.health.live.path = path;
|
|
608
|
+
}
|
|
563
609
|
}
|
|
564
|
-
}
|
|
610
|
+
};
|
|
565
611
|
/**
|
|
566
612
|
* This method can be used in a `@MiddlewareClass` to register any libraries
|
|
567
613
|
* that expect you to register multiple registers at once. An example is `swagger-ui-express`
|
|
@@ -1154,12 +1200,17 @@ let DefaultHealthMiddleware = class DefaultHealthMiddleware {
|
|
|
1154
1200
|
constructor(healthRegistrar) {
|
|
1155
1201
|
this.healthRegistrar = healthRegistrar;
|
|
1156
1202
|
}
|
|
1157
|
-
async
|
|
1158
|
-
const up = await this.healthRegistrar.
|
|
1203
|
+
async readiness(_request, _response, _next) {
|
|
1204
|
+
const up = await this.healthRegistrar._readiness();
|
|
1205
|
+
return ResponseEntity.ok().body({ up });
|
|
1206
|
+
}
|
|
1207
|
+
async liveness(_request, _response, _next) {
|
|
1208
|
+
const up = await this.healthRegistrar._liveness();
|
|
1159
1209
|
return ResponseEntity.ok().body({ up });
|
|
1160
1210
|
}
|
|
1161
1211
|
};
|
|
1162
|
-
__decorate([GET(_settings.health.path)], DefaultHealthMiddleware.prototype, "
|
|
1212
|
+
__decorate([GET(_settings.health.ready.path)], DefaultHealthMiddleware.prototype, "readiness", null);
|
|
1213
|
+
__decorate([GET(_settings.health.live.path)], DefaultHealthMiddleware.prototype, "liveness", null);
|
|
1163
1214
|
DefaultHealthMiddleware = __decorate([MiddlewareClass({ deps: [HealthRegistrar] })], DefaultHealthMiddleware);
|
|
1164
1215
|
//#endregion
|
|
1165
1216
|
export { Controller, ControllerSchema, DELETE, DefaultBaseErrorMiddleware, DefaultHealthMiddleware, DefaultOpenApiMiddleware, DefaultParserErrorMiddleware, DefaultResponseStatusErrorMiddleware, DefaultSwaggerMiddleware, GET, HEAD, HealthRegistrar, Html404ErrorPage, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, PATCH, POST, PUT, ParserError, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseBody, ResponseEntity, ResponseEntityBuilder, ResponseStatusError, RouteSchema, Sapling, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _clearOpenApiRegistry, _getControllerSchema, _getOrCreateSchemaDefinition, _getRouteSchema, _getRoutes, _getValidatorSchema, _parseOrThrow, _registerController, _resolve, _saveValidatorSchema, _setControllerSchema, _setRouteSchema, _settings, generateOpenApiSpec, methodResolve, openApiGenerator };
|