@tahminator/sapling 2.1.0 → 2.1.2

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 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
- // you still have full access of app to do whatever you want!
101
- const app = express();
102
- Sapling.registerApp(app);
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
@@ -270,10 +270,181 @@ var ParserError = class ParserError extends ResponseStatusError {
270
270
  }
271
271
  };
272
272
  //#endregion
273
+ //#region lib/weakmap.ts
274
+ /**
275
+ * WeakMap that is iterable.
276
+ */
277
+ var IterableWeakMap = class IterableWeakMap {
278
+ #weakMap = /* @__PURE__ */ new WeakMap();
279
+ #refSet = /* @__PURE__ */ new Set();
280
+ #finalizationGroup = new FinalizationRegistry(IterableWeakMap.#cleanup);
281
+ static #cleanup(heldValue) {
282
+ heldValue.set.delete(heldValue.ref);
283
+ }
284
+ constructor(iterable) {
285
+ if (iterable) for (const [key, value] of iterable) this.set(key, value);
286
+ }
287
+ set(key, value) {
288
+ const ref = new WeakRef(key);
289
+ this.#weakMap.set(key, {
290
+ value,
291
+ ref
292
+ });
293
+ this.#refSet.add(ref);
294
+ this.#finalizationGroup.register(key, {
295
+ set: this.#refSet,
296
+ ref
297
+ }, ref);
298
+ return this;
299
+ }
300
+ get(key) {
301
+ return this.#weakMap.get(key)?.value;
302
+ }
303
+ delete(key) {
304
+ const entry = this.#weakMap.get(key);
305
+ if (!entry) return false;
306
+ this.#weakMap.delete(key);
307
+ this.#refSet.delete(entry.ref);
308
+ this.#finalizationGroup.unregister(entry.ref);
309
+ return true;
310
+ }
311
+ *[Symbol.iterator]() {
312
+ for (const ref of this.#refSet) {
313
+ const key = ref.deref();
314
+ if (!key) continue;
315
+ const entry = this.#weakMap.get(key);
316
+ if (entry) yield [key, entry.value];
317
+ }
318
+ }
319
+ entries() {
320
+ return this[Symbol.iterator]();
321
+ }
322
+ *keys() {
323
+ for (const [key] of this) yield key;
324
+ }
325
+ *values() {
326
+ for (const [, value] of this) yield value;
327
+ }
328
+ forEach(callback, thisArg) {
329
+ for (const [key, value] of this) callback.call(thisArg, value, key, this);
330
+ }
331
+ };
332
+ //#endregion
333
+ //#region src/annotation/injectable.ts
334
+ const _InjectableRegistry = /* @__PURE__ */ new WeakMap();
335
+ const _InjectableDeps = new IterableWeakMap();
336
+ /**
337
+ * Mark the class as an injectable to be handled by Sapling. The class can now be
338
+ * be injected into other classes, as well as allow the class to inject other `@Injectable` classes.
339
+ *
340
+ * @argument deps - An optional array to define any dependencies that this class may require.
341
+ */
342
+ function Injectable(deps = []) {
343
+ return function(target) {
344
+ _InjectableRegistry.set(target, null);
345
+ _InjectableDeps.set(target, deps);
346
+ };
347
+ }
348
+ /**
349
+ * Resolves and instantiates a class along with all of it's transitive dependencies.
350
+ *
351
+ * Uses topological sort (Kahn's algorithm) to ensure that the dependency graph is created
352
+ * in a correct order.
353
+ *
354
+ * When `resolve` is first called (usually during controller registration),
355
+ * it will compute the dependency graph of all `@Injectable` classes and instantiates
356
+ * them in the correct order.
357
+ *
358
+ * Subsequent calls to dependencies that have already been resolved are cached, so they will
359
+ * re-use the created singletons instead of re-instantiation.
360
+ */
361
+ function _resolve(ctor) {
362
+ const inDegree = /* @__PURE__ */ new Map();
363
+ const graph = /* @__PURE__ */ new Map();
364
+ _InjectableDeps.forEach((deps, node) => {
365
+ inDegree.set(node, inDegree.get(node) || 0);
366
+ deps.forEach((dep) => {
367
+ if (dep === void 0) throw new Error(`There is an @Injectable (${node.name}) which has a dependency that cannot be found. This is likely caused by a circular dependency.`);
368
+ inDegree.set(dep, inDegree.get(dep) || 0);
369
+ inDegree.set(node, inDegree.get(node) + 1);
370
+ if (!graph.has(dep)) graph.set(dep, []);
371
+ graph.get(dep).push(node);
372
+ });
373
+ });
374
+ const queue = [];
375
+ inDegree.forEach((deg, node) => {
376
+ if (deg === 0) queue.push(node);
377
+ });
378
+ while (queue.length) {
379
+ const current = queue.shift();
380
+ if (!_InjectableRegistry.get(current)) {
381
+ const instance = new current(...(_InjectableDeps.get(current) || []).map((dep) => _InjectableRegistry.get(dep)));
382
+ _InjectableRegistry.set(current, instance);
383
+ }
384
+ (graph.get(current) || []).forEach((neighbor) => {
385
+ inDegree.set(neighbor, (inDegree.get(neighbor) ?? 0) - 1);
386
+ if (inDegree.get(neighbor) === 0) queue.push(neighbor);
387
+ });
388
+ }
389
+ if (!_InjectableRegistry.get(ctor)) throw new Error("Circular dependency detected or injectable not registered");
390
+ return _InjectableRegistry.get(ctor);
391
+ }
392
+ //#endregion
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;
399
+ }
400
+ //#endregion
401
+ //#region src/middleware/health/registrar.ts
402
+ let HealthRegistrar = class HealthRegistrar {
403
+ _checks;
404
+ _live;
405
+ constructor() {
406
+ this._checks = [];
407
+ this._live = false;
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
+ */
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
+ _markLive() {
422
+ this._live = true;
423
+ }
424
+ /**
425
+ * @internal
426
+ */
427
+ async _liveness() {
428
+ return this._live;
429
+ }
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);
436
+ }
437
+ };
438
+ HealthRegistrar = __decorate([Injectable()], HealthRegistrar);
439
+ //#endregion
273
440
  //#region src/helper/sapling.ts
274
441
  const _settings = {
275
442
  serialize: JSON.stringify,
276
443
  deserialize: JSON.parse,
444
+ health: {
445
+ ready: { path: "/readyz" },
446
+ live: { path: "/livez" }
447
+ },
277
448
  doc: {
278
449
  openApiPath: "/openapi.json",
279
450
  swaggerPath: "/swagger.html",
@@ -341,14 +512,34 @@ var Sapling = class Sapling {
341
512
  * import { Sapling } from "@tahminator/sapling";
342
513
  * import express from "express";
343
514
  *
344
- * const app = express();
345
- *
346
- * app.registerApp(app);
515
+ * // returns the exact same `express.App` type back to you!
516
+ * const app = Sapling.registerApp(express());
347
517
  * ```
348
518
  */
349
519
  static registerApp(app) {
350
520
  app.use(express.default.text({ type: "application/json" }));
351
521
  app.use(Sapling.json());
522
+ return new Proxy(app, { get(target, prop, receiver) {
523
+ if (prop === "listen") {
524
+ const originalListen = target[prop];
525
+ return function(...args) {
526
+ const server = originalListen.apply(target, args);
527
+ server.once("listening", () => {
528
+ Sapling._onPostStartup();
529
+ console.log("Sapling successfully initialized post-startup hooks on server start");
530
+ });
531
+ return server;
532
+ };
533
+ }
534
+ return Reflect.get(target, prop, receiver);
535
+ } });
536
+ }
537
+ /**
538
+ * @internal
539
+ * visible for testing
540
+ */
541
+ static _onPostStartup() {
542
+ _InjectableRegistry.get(HealthRegistrar)?._markLive();
352
543
  }
353
544
  /**
354
545
  * Serialize a value into a JSON string.
@@ -389,37 +580,59 @@ var Sapling = class Sapling {
389
580
  /**
390
581
  * Modify extra settings
391
582
  */
392
- static Extras = {
393
- /**
394
- * Modify default settings applied to OpenAPI & Swagger
395
- */
396
- swaggerAndOpenApi: {
397
- /**
398
- * Set base OpenAPI metadata values.
399
- *
400
- * @default { title: "API", version: "1.0.0" }
401
- */
402
- setMetadata(metadata) {
403
- _settings.doc.metadata = metadata;
404
- },
583
+ static Extras = {
405
584
  /**
406
- * change default endpoint that will serve OpenAPI spec.
407
- * Swagger will also load this endpoint on load.
408
- *
409
- * @default `/openapi.json`
585
+ * Modify default settings applied to OpenAPI & Swagger
410
586
  */
411
- setOpenApiPath(path) {
412
- _settings.doc.openApiPath = path;
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
+ }
413
613
  },
414
614
  /**
415
- * change Swagger endpoint.
416
- *
417
- * @default `/swagger.html`
615
+ * Modify default settings applied to health / readiness / liveness.
418
616
  */
419
- setSwaggerPath(path) {
420
- _settings.doc.swaggerPath = path;
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
+ }
421
634
  }
422
- } };
635
+ };
423
636
  /**
424
637
  * This method can be used in a `@MiddlewareClass` to register any libraries
425
638
  * that expect you to register multiple registers at once. An example is `swagger-ui-express`
@@ -774,126 +987,6 @@ const methodResolve = {
774
987
  USE: "use"
775
988
  };
776
989
  //#endregion
777
- //#region lib/weakmap.ts
778
- /**
779
- * WeakMap that is iterable.
780
- */
781
- var IterableWeakMap = class IterableWeakMap {
782
- #weakMap = /* @__PURE__ */ new WeakMap();
783
- #refSet = /* @__PURE__ */ new Set();
784
- #finalizationGroup = new FinalizationRegistry(IterableWeakMap.#cleanup);
785
- static #cleanup(heldValue) {
786
- heldValue.set.delete(heldValue.ref);
787
- }
788
- constructor(iterable) {
789
- if (iterable) for (const [key, value] of iterable) this.set(key, value);
790
- }
791
- set(key, value) {
792
- const ref = new WeakRef(key);
793
- this.#weakMap.set(key, {
794
- value,
795
- ref
796
- });
797
- this.#refSet.add(ref);
798
- this.#finalizationGroup.register(key, {
799
- set: this.#refSet,
800
- ref
801
- }, ref);
802
- return this;
803
- }
804
- get(key) {
805
- return this.#weakMap.get(key)?.value;
806
- }
807
- delete(key) {
808
- const entry = this.#weakMap.get(key);
809
- if (!entry) return false;
810
- this.#weakMap.delete(key);
811
- this.#refSet.delete(entry.ref);
812
- this.#finalizationGroup.unregister(entry.ref);
813
- return true;
814
- }
815
- *[Symbol.iterator]() {
816
- for (const ref of this.#refSet) {
817
- const key = ref.deref();
818
- if (!key) continue;
819
- const entry = this.#weakMap.get(key);
820
- if (entry) yield [key, entry.value];
821
- }
822
- }
823
- entries() {
824
- return this[Symbol.iterator]();
825
- }
826
- *keys() {
827
- for (const [key] of this) yield key;
828
- }
829
- *values() {
830
- for (const [, value] of this) yield value;
831
- }
832
- forEach(callback, thisArg) {
833
- for (const [key, value] of this) callback.call(thisArg, value, key, this);
834
- }
835
- };
836
- //#endregion
837
- //#region src/annotation/injectable.ts
838
- const _InjectableRegistry = /* @__PURE__ */ new WeakMap();
839
- const _InjectableDeps = new IterableWeakMap();
840
- /**
841
- * Mark the class as an injectable to be handled by Sapling. The class can now be
842
- * be injected into other classes, as well as allow the class to inject other `@Injectable` classes.
843
- *
844
- * @argument deps - An optional array to define any dependencies that this class may require.
845
- */
846
- function Injectable(deps = []) {
847
- return function(target) {
848
- _InjectableRegistry.set(target, null);
849
- _InjectableDeps.set(target, deps);
850
- };
851
- }
852
- /**
853
- * Resolves and instantiates a class along with all of it's transitive dependencies.
854
- *
855
- * Uses topological sort (Kahn's algorithm) to ensure that the dependency graph is created
856
- * in a correct order.
857
- *
858
- * When `resolve` is first called (usually during controller registration),
859
- * it will compute the dependency graph of all `@Injectable` classes and instantiates
860
- * them in the correct order.
861
- *
862
- * Subsequent calls to dependencies that have already been resolved are cached, so they will
863
- * re-use the created singletons instead of re-instantiation.
864
- */
865
- function _resolve(ctor) {
866
- const inDegree = /* @__PURE__ */ new Map();
867
- const graph = /* @__PURE__ */ new Map();
868
- _InjectableDeps.forEach((deps, node) => {
869
- inDegree.set(node, inDegree.get(node) || 0);
870
- deps.forEach((dep) => {
871
- if (dep === void 0) throw new Error(`There is an @Injectable (${node.name}) which has a dependency that cannot be found. This is likely caused by a circular dependency.`);
872
- inDegree.set(dep, inDegree.get(dep) || 0);
873
- inDegree.set(node, inDegree.get(node) + 1);
874
- if (!graph.has(dep)) graph.set(dep, []);
875
- graph.get(dep).push(node);
876
- });
877
- });
878
- const queue = [];
879
- inDegree.forEach((deg, node) => {
880
- if (deg === 0) queue.push(node);
881
- });
882
- while (queue.length) {
883
- const current = queue.shift();
884
- if (!_InjectableRegistry.get(current)) {
885
- const instance = new current(...(_InjectableDeps.get(current) || []).map((dep) => _InjectableRegistry.get(dep)));
886
- _InjectableRegistry.set(current, instance);
887
- }
888
- (graph.get(current) || []).forEach((neighbor) => {
889
- inDegree.set(neighbor, (inDegree.get(neighbor) ?? 0) - 1);
890
- if (inDegree.get(neighbor) === 0) queue.push(neighbor);
891
- });
892
- }
893
- if (!_InjectableRegistry.get(ctor)) throw new Error("Circular dependency detected or injectable not registered");
894
- return _InjectableRegistry.get(ctor);
895
- }
896
- //#endregion
897
990
  //#region src/annotation/controller.ts
898
991
  const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
899
992
  /**
@@ -1012,14 +1105,6 @@ function MiddlewareClass(...args) {
1012
1105
  return Controller(...args);
1013
1106
  }
1014
1107
  //#endregion
1015
- //#region \0@oxc-project+runtime@0.127.0/helpers/decorate.js
1016
- function __decorate(decorators, target, key, desc) {
1017
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1018
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1019
- 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;
1020
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1021
- }
1022
- //#endregion
1023
1108
  //#region src/middleware/default/error/base.ts
1024
1109
  let DefaultBaseErrorMiddleware = class DefaultBaseErrorMiddleware {
1025
1110
  handle(err, _request, _response, _next) {
@@ -1135,6 +1220,24 @@ const DefaultSwaggerMiddleware = {
1135
1220
  Setup
1136
1221
  };
1137
1222
  //#endregion
1223
+ //#region src/middleware/default/health/index.ts
1224
+ let DefaultHealthMiddleware = class DefaultHealthMiddleware {
1225
+ constructor(healthRegistrar) {
1226
+ this.healthRegistrar = healthRegistrar;
1227
+ }
1228
+ async readiness(_request, _response, _next) {
1229
+ const up = await this.healthRegistrar._readiness();
1230
+ return ResponseEntity.status(up ? 200 : 503).body({ up });
1231
+ }
1232
+ async liveness(_request, _response, _next) {
1233
+ const up = await this.healthRegistrar._liveness();
1234
+ return ResponseEntity.status(up ? 200 : 503).body({ up });
1235
+ }
1236
+ };
1237
+ __decorate([GET(_settings.health.ready.path)], DefaultHealthMiddleware.prototype, "readiness", null);
1238
+ __decorate([GET(_settings.health.live.path)], DefaultHealthMiddleware.prototype, "liveness", null);
1239
+ DefaultHealthMiddleware = __decorate([MiddlewareClass({ deps: [HealthRegistrar] })], DefaultHealthMiddleware);
1240
+ //#endregion
1138
1241
  exports.Controller = Controller;
1139
1242
  exports.ControllerSchema = ControllerSchema;
1140
1243
  exports.DELETE = DELETE;
@@ -1144,6 +1247,12 @@ Object.defineProperty(exports, "DefaultBaseErrorMiddleware", {
1144
1247
  return DefaultBaseErrorMiddleware;
1145
1248
  }
1146
1249
  });
1250
+ Object.defineProperty(exports, "DefaultHealthMiddleware", {
1251
+ enumerable: true,
1252
+ get: function() {
1253
+ return DefaultHealthMiddleware;
1254
+ }
1255
+ });
1147
1256
  Object.defineProperty(exports, "DefaultOpenApiMiddleware", {
1148
1257
  enumerable: true,
1149
1258
  get: function() {
@@ -1165,6 +1274,12 @@ Object.defineProperty(exports, "DefaultResponseStatusErrorMiddleware", {
1165
1274
  exports.DefaultSwaggerMiddleware = DefaultSwaggerMiddleware;
1166
1275
  exports.GET = GET;
1167
1276
  exports.HEAD = HEAD;
1277
+ Object.defineProperty(exports, "HealthRegistrar", {
1278
+ enumerable: true,
1279
+ get: function() {
1280
+ return HealthRegistrar;
1281
+ }
1282
+ });
1168
1283
  exports.Html404ErrorPage = Html404ErrorPage;
1169
1284
  exports.HttpStatus = HttpStatus;
1170
1285
  exports.Injectable = Injectable;
package/dist/index.d.cts CHANGED
@@ -794,6 +794,14 @@ declare function _clearOpenApiRegistry(): void;
794
794
  type Settings = {
795
795
  serialize: (value: any) => string;
796
796
  deserialize: (value: string) => any;
797
+ health: {
798
+ ready: {
799
+ path: string;
800
+ };
801
+ live: {
802
+ path: string;
803
+ };
804
+ };
797
805
  doc: {
798
806
  openApiPath: string;
799
807
  swaggerPath: string;
@@ -840,12 +848,16 @@ declare class Sapling {
840
848
  * import { Sapling } from "@tahminator/sapling";
841
849
  * import express from "express";
842
850
  *
843
- * const app = express();
844
- *
845
- * app.registerApp(app);
851
+ * // returns the exact same `express.App` type back to you!
852
+ * const app = Sapling.registerApp(express());
846
853
  * ```
847
854
  */
848
- static registerApp(app: e.Express): void;
855
+ static registerApp(app: e.Express): e.Express;
856
+ /**
857
+ * @internal
858
+ * visible for testing
859
+ */
860
+ static _onPostStartup(): void;
849
861
  /**
850
862
  * Serialize a value into a JSON string.
851
863
  *
@@ -902,6 +914,23 @@ declare class Sapling {
902
914
  */
903
915
  setSwaggerPath(this: void, path: string): void;
904
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
+ };
905
934
  };
906
935
  /**
907
936
  * This method can be used in a `@MiddlewareClass` to register any libraries
@@ -1089,4 +1118,48 @@ declare const DefaultSwaggerMiddleware: {
1089
1118
  Setup: typeof Setup;
1090
1119
  };
1091
1120
  //#endregion
1092
- export { Class, Controller, ControllerSchema, ControllerSchemaDefinition, DELETE, DefaultBaseErrorMiddleware, DefaultOpenApiMiddleware, DefaultParserErrorMiddleware, DefaultResponseStatusErrorMiddleware, DefaultSwaggerMiddleware, ExpressMiddlewareFn, ExpressRouterMethodKey, ExpressRouterMethods, GET, HEAD, Html404ErrorPage, HttpHeaders, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, OpenAPIMetadata, PATCH, POST, PUT, ParserError, ParserErrorLocation, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseBody, ResponseEntity, ResponseEntityBuilder, ResponseSchema, ResponseStatusError, RouteDefinition, RouteSchema, RouteSchemaDefinition, Sapling, ValidatorSchema, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _clearOpenApiRegistry, _getControllerSchema, _getOrCreateSchemaDefinition, _getRouteSchema, _getRoutes, _getValidatorSchema, _parseOrThrow, _registerController, _resolve, _saveValidatorSchema, _setControllerSchema, _setRouteSchema, _settings, generateOpenApiSpec, methodResolve, openApiGenerator };
1121
+ //#region src/middleware/health/registrar.d.ts
1122
+ type HealthCheck = () => boolean | Promise<boolean>;
1123
+ declare class HealthRegistrar {
1124
+ private _checks;
1125
+ private _live;
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
+ */
1132
+ add(healthCheck: HealthCheck): void;
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>;
1146
+ }
1147
+ //#endregion
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
+ */
1154
+ declare class DefaultHealthMiddleware {
1155
+ private readonly healthRegistrar;
1156
+ constructor(healthRegistrar: HealthRegistrar);
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<{
1161
+ up: boolean;
1162
+ }>>;
1163
+ }
1164
+ //#endregion
1165
+ export { Class, Controller, ControllerSchema, ControllerSchemaDefinition, DELETE, DefaultBaseErrorMiddleware, DefaultHealthMiddleware, DefaultOpenApiMiddleware, DefaultParserErrorMiddleware, DefaultResponseStatusErrorMiddleware, DefaultSwaggerMiddleware, ExpressMiddlewareFn, ExpressRouterMethodKey, ExpressRouterMethods, GET, HEAD, HealthCheck, HealthRegistrar, Html404ErrorPage, HttpHeaders, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, OpenAPIMetadata, PATCH, POST, PUT, ParserError, ParserErrorLocation, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseBody, ResponseEntity, ResponseEntityBuilder, ResponseSchema, ResponseStatusError, RouteDefinition, RouteSchema, RouteSchemaDefinition, Sapling, ValidatorSchema, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _clearOpenApiRegistry, _getControllerSchema, _getOrCreateSchemaDefinition, _getRouteSchema, _getRoutes, _getValidatorSchema, _parseOrThrow, _registerController, _resolve, _saveValidatorSchema, _setControllerSchema, _setRouteSchema, _settings, generateOpenApiSpec, methodResolve, openApiGenerator };
package/dist/index.d.mts CHANGED
@@ -794,6 +794,14 @@ declare function _clearOpenApiRegistry(): void;
794
794
  type Settings = {
795
795
  serialize: (value: any) => string;
796
796
  deserialize: (value: string) => any;
797
+ health: {
798
+ ready: {
799
+ path: string;
800
+ };
801
+ live: {
802
+ path: string;
803
+ };
804
+ };
797
805
  doc: {
798
806
  openApiPath: string;
799
807
  swaggerPath: string;
@@ -840,12 +848,16 @@ declare class Sapling {
840
848
  * import { Sapling } from "@tahminator/sapling";
841
849
  * import express from "express";
842
850
  *
843
- * const app = express();
844
- *
845
- * app.registerApp(app);
851
+ * // returns the exact same `express.App` type back to you!
852
+ * const app = Sapling.registerApp(express());
846
853
  * ```
847
854
  */
848
- static registerApp(app: e.Express): void;
855
+ static registerApp(app: e.Express): e.Express;
856
+ /**
857
+ * @internal
858
+ * visible for testing
859
+ */
860
+ static _onPostStartup(): void;
849
861
  /**
850
862
  * Serialize a value into a JSON string.
851
863
  *
@@ -902,6 +914,23 @@ declare class Sapling {
902
914
  */
903
915
  setSwaggerPath(this: void, path: string): void;
904
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
+ };
905
934
  };
906
935
  /**
907
936
  * This method can be used in a `@MiddlewareClass` to register any libraries
@@ -1089,4 +1118,48 @@ declare const DefaultSwaggerMiddleware: {
1089
1118
  Setup: typeof Setup;
1090
1119
  };
1091
1120
  //#endregion
1092
- export { Class, Controller, ControllerSchema, ControllerSchemaDefinition, DELETE, DefaultBaseErrorMiddleware, DefaultOpenApiMiddleware, DefaultParserErrorMiddleware, DefaultResponseStatusErrorMiddleware, DefaultSwaggerMiddleware, ExpressMiddlewareFn, ExpressRouterMethodKey, ExpressRouterMethods, GET, HEAD, Html404ErrorPage, HttpHeaders, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, OpenAPIMetadata, PATCH, POST, PUT, ParserError, ParserErrorLocation, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseBody, ResponseEntity, ResponseEntityBuilder, ResponseSchema, ResponseStatusError, RouteDefinition, RouteSchema, RouteSchemaDefinition, Sapling, ValidatorSchema, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _clearOpenApiRegistry, _getControllerSchema, _getOrCreateSchemaDefinition, _getRouteSchema, _getRoutes, _getValidatorSchema, _parseOrThrow, _registerController, _resolve, _saveValidatorSchema, _setControllerSchema, _setRouteSchema, _settings, generateOpenApiSpec, methodResolve, openApiGenerator };
1121
+ //#region src/middleware/health/registrar.d.ts
1122
+ type HealthCheck = () => boolean | Promise<boolean>;
1123
+ declare class HealthRegistrar {
1124
+ private _checks;
1125
+ private _live;
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
+ */
1132
+ add(healthCheck: HealthCheck): void;
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>;
1146
+ }
1147
+ //#endregion
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
+ */
1154
+ declare class DefaultHealthMiddleware {
1155
+ private readonly healthRegistrar;
1156
+ constructor(healthRegistrar: HealthRegistrar);
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<{
1161
+ up: boolean;
1162
+ }>>;
1163
+ }
1164
+ //#endregion
1165
+ export { Class, Controller, ControllerSchema, ControllerSchemaDefinition, DELETE, DefaultBaseErrorMiddleware, DefaultHealthMiddleware, DefaultOpenApiMiddleware, DefaultParserErrorMiddleware, DefaultResponseStatusErrorMiddleware, DefaultSwaggerMiddleware, ExpressMiddlewareFn, ExpressRouterMethodKey, ExpressRouterMethods, GET, HEAD, HealthCheck, HealthRegistrar, Html404ErrorPage, HttpHeaders, HttpStatus, Injectable, Middleware, MiddlewareClass, OPTIONS, OpenAPIMetadata, PATCH, POST, PUT, ParserError, ParserErrorLocation, RedirectView, RequestBody, RequestParam, RequestQuery, ResponseBody, ResponseEntity, ResponseEntityBuilder, ResponseSchema, ResponseStatusError, RouteDefinition, RouteSchema, RouteSchemaDefinition, Sapling, ValidatorSchema, _ControllerRegistry, _InjectableDeps, _InjectableRegistry, _Route, _clearOpenApiRegistry, _getControllerSchema, _getOrCreateSchemaDefinition, _getRouteSchema, _getRoutes, _getValidatorSchema, _parseOrThrow, _registerController, _resolve, _saveValidatorSchema, _setControllerSchema, _setRouteSchema, _settings, generateOpenApiSpec, methodResolve, openApiGenerator };
package/dist/index.mjs CHANGED
@@ -245,10 +245,181 @@ var ParserError = class ParserError extends ResponseStatusError {
245
245
  }
246
246
  };
247
247
  //#endregion
248
+ //#region lib/weakmap.ts
249
+ /**
250
+ * WeakMap that is iterable.
251
+ */
252
+ var IterableWeakMap = class IterableWeakMap {
253
+ #weakMap = /* @__PURE__ */ new WeakMap();
254
+ #refSet = /* @__PURE__ */ new Set();
255
+ #finalizationGroup = new FinalizationRegistry(IterableWeakMap.#cleanup);
256
+ static #cleanup(heldValue) {
257
+ heldValue.set.delete(heldValue.ref);
258
+ }
259
+ constructor(iterable) {
260
+ if (iterable) for (const [key, value] of iterable) this.set(key, value);
261
+ }
262
+ set(key, value) {
263
+ const ref = new WeakRef(key);
264
+ this.#weakMap.set(key, {
265
+ value,
266
+ ref
267
+ });
268
+ this.#refSet.add(ref);
269
+ this.#finalizationGroup.register(key, {
270
+ set: this.#refSet,
271
+ ref
272
+ }, ref);
273
+ return this;
274
+ }
275
+ get(key) {
276
+ return this.#weakMap.get(key)?.value;
277
+ }
278
+ delete(key) {
279
+ const entry = this.#weakMap.get(key);
280
+ if (!entry) return false;
281
+ this.#weakMap.delete(key);
282
+ this.#refSet.delete(entry.ref);
283
+ this.#finalizationGroup.unregister(entry.ref);
284
+ return true;
285
+ }
286
+ *[Symbol.iterator]() {
287
+ for (const ref of this.#refSet) {
288
+ const key = ref.deref();
289
+ if (!key) continue;
290
+ const entry = this.#weakMap.get(key);
291
+ if (entry) yield [key, entry.value];
292
+ }
293
+ }
294
+ entries() {
295
+ return this[Symbol.iterator]();
296
+ }
297
+ *keys() {
298
+ for (const [key] of this) yield key;
299
+ }
300
+ *values() {
301
+ for (const [, value] of this) yield value;
302
+ }
303
+ forEach(callback, thisArg) {
304
+ for (const [key, value] of this) callback.call(thisArg, value, key, this);
305
+ }
306
+ };
307
+ //#endregion
308
+ //#region src/annotation/injectable.ts
309
+ const _InjectableRegistry = /* @__PURE__ */ new WeakMap();
310
+ const _InjectableDeps = new IterableWeakMap();
311
+ /**
312
+ * Mark the class as an injectable to be handled by Sapling. The class can now be
313
+ * be injected into other classes, as well as allow the class to inject other `@Injectable` classes.
314
+ *
315
+ * @argument deps - An optional array to define any dependencies that this class may require.
316
+ */
317
+ function Injectable(deps = []) {
318
+ return function(target) {
319
+ _InjectableRegistry.set(target, null);
320
+ _InjectableDeps.set(target, deps);
321
+ };
322
+ }
323
+ /**
324
+ * Resolves and instantiates a class along with all of it's transitive dependencies.
325
+ *
326
+ * Uses topological sort (Kahn's algorithm) to ensure that the dependency graph is created
327
+ * in a correct order.
328
+ *
329
+ * When `resolve` is first called (usually during controller registration),
330
+ * it will compute the dependency graph of all `@Injectable` classes and instantiates
331
+ * them in the correct order.
332
+ *
333
+ * Subsequent calls to dependencies that have already been resolved are cached, so they will
334
+ * re-use the created singletons instead of re-instantiation.
335
+ */
336
+ function _resolve(ctor) {
337
+ const inDegree = /* @__PURE__ */ new Map();
338
+ const graph = /* @__PURE__ */ new Map();
339
+ _InjectableDeps.forEach((deps, node) => {
340
+ inDegree.set(node, inDegree.get(node) || 0);
341
+ deps.forEach((dep) => {
342
+ if (dep === void 0) throw new Error(`There is an @Injectable (${node.name}) which has a dependency that cannot be found. This is likely caused by a circular dependency.`);
343
+ inDegree.set(dep, inDegree.get(dep) || 0);
344
+ inDegree.set(node, inDegree.get(node) + 1);
345
+ if (!graph.has(dep)) graph.set(dep, []);
346
+ graph.get(dep).push(node);
347
+ });
348
+ });
349
+ const queue = [];
350
+ inDegree.forEach((deg, node) => {
351
+ if (deg === 0) queue.push(node);
352
+ });
353
+ while (queue.length) {
354
+ const current = queue.shift();
355
+ if (!_InjectableRegistry.get(current)) {
356
+ const instance = new current(...(_InjectableDeps.get(current) || []).map((dep) => _InjectableRegistry.get(dep)));
357
+ _InjectableRegistry.set(current, instance);
358
+ }
359
+ (graph.get(current) || []).forEach((neighbor) => {
360
+ inDegree.set(neighbor, (inDegree.get(neighbor) ?? 0) - 1);
361
+ if (inDegree.get(neighbor) === 0) queue.push(neighbor);
362
+ });
363
+ }
364
+ if (!_InjectableRegistry.get(ctor)) throw new Error("Circular dependency detected or injectable not registered");
365
+ return _InjectableRegistry.get(ctor);
366
+ }
367
+ //#endregion
368
+ //#region \0@oxc-project+runtime@0.127.0/helpers/decorate.js
369
+ function __decorate(decorators, target, key, desc) {
370
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
371
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
372
+ 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;
373
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
374
+ }
375
+ //#endregion
376
+ //#region src/middleware/health/registrar.ts
377
+ let HealthRegistrar = class HealthRegistrar {
378
+ _checks;
379
+ _live;
380
+ constructor() {
381
+ this._checks = [];
382
+ this._live = false;
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
+ */
389
+ add(healthCheck) {
390
+ this._checks.push(healthCheck);
391
+ }
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;
404
+ }
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);
411
+ }
412
+ };
413
+ HealthRegistrar = __decorate([Injectable()], HealthRegistrar);
414
+ //#endregion
248
415
  //#region src/helper/sapling.ts
249
416
  const _settings = {
250
417
  serialize: JSON.stringify,
251
418
  deserialize: JSON.parse,
419
+ health: {
420
+ ready: { path: "/readyz" },
421
+ live: { path: "/livez" }
422
+ },
252
423
  doc: {
253
424
  openApiPath: "/openapi.json",
254
425
  swaggerPath: "/swagger.html",
@@ -316,14 +487,34 @@ var Sapling = class Sapling {
316
487
  * import { Sapling } from "@tahminator/sapling";
317
488
  * import express from "express";
318
489
  *
319
- * const app = express();
320
- *
321
- * app.registerApp(app);
490
+ * // returns the exact same `express.App` type back to you!
491
+ * const app = Sapling.registerApp(express());
322
492
  * ```
323
493
  */
324
494
  static registerApp(app) {
325
495
  app.use(e.text({ type: "application/json" }));
326
496
  app.use(Sapling.json());
497
+ return new Proxy(app, { get(target, prop, receiver) {
498
+ if (prop === "listen") {
499
+ const originalListen = target[prop];
500
+ return function(...args) {
501
+ const server = originalListen.apply(target, args);
502
+ server.once("listening", () => {
503
+ Sapling._onPostStartup();
504
+ console.log("Sapling successfully initialized post-startup hooks on server start");
505
+ });
506
+ return server;
507
+ };
508
+ }
509
+ return Reflect.get(target, prop, receiver);
510
+ } });
511
+ }
512
+ /**
513
+ * @internal
514
+ * visible for testing
515
+ */
516
+ static _onPostStartup() {
517
+ _InjectableRegistry.get(HealthRegistrar)?._markLive();
327
518
  }
328
519
  /**
329
520
  * Serialize a value into a JSON string.
@@ -364,37 +555,59 @@ var Sapling = class Sapling {
364
555
  /**
365
556
  * Modify extra settings
366
557
  */
367
- static Extras = {
368
- /**
369
- * Modify default settings applied to OpenAPI & Swagger
370
- */
371
- swaggerAndOpenApi: {
558
+ static Extras = {
372
559
  /**
373
- * Set base OpenAPI metadata values.
374
- *
375
- * @default { title: "API", version: "1.0.0" }
560
+ * Modify default settings applied to OpenAPI & Swagger
376
561
  */
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;
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
+ }
388
588
  },
389
589
  /**
390
- * change Swagger endpoint.
391
- *
392
- * @default `/swagger.html`
590
+ * Modify default settings applied to health / readiness / liveness.
393
591
  */
394
- setSwaggerPath(path) {
395
- _settings.doc.swaggerPath = path;
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
+ }
396
609
  }
397
- } };
610
+ };
398
611
  /**
399
612
  * This method can be used in a `@MiddlewareClass` to register any libraries
400
613
  * that expect you to register multiple registers at once. An example is `swagger-ui-express`
@@ -749,126 +962,6 @@ const methodResolve = {
749
962
  USE: "use"
750
963
  };
751
964
  //#endregion
752
- //#region lib/weakmap.ts
753
- /**
754
- * WeakMap that is iterable.
755
- */
756
- var IterableWeakMap = class IterableWeakMap {
757
- #weakMap = /* @__PURE__ */ new WeakMap();
758
- #refSet = /* @__PURE__ */ new Set();
759
- #finalizationGroup = new FinalizationRegistry(IterableWeakMap.#cleanup);
760
- static #cleanup(heldValue) {
761
- heldValue.set.delete(heldValue.ref);
762
- }
763
- constructor(iterable) {
764
- if (iterable) for (const [key, value] of iterable) this.set(key, value);
765
- }
766
- set(key, value) {
767
- const ref = new WeakRef(key);
768
- this.#weakMap.set(key, {
769
- value,
770
- ref
771
- });
772
- this.#refSet.add(ref);
773
- this.#finalizationGroup.register(key, {
774
- set: this.#refSet,
775
- ref
776
- }, ref);
777
- return this;
778
- }
779
- get(key) {
780
- return this.#weakMap.get(key)?.value;
781
- }
782
- delete(key) {
783
- const entry = this.#weakMap.get(key);
784
- if (!entry) return false;
785
- this.#weakMap.delete(key);
786
- this.#refSet.delete(entry.ref);
787
- this.#finalizationGroup.unregister(entry.ref);
788
- return true;
789
- }
790
- *[Symbol.iterator]() {
791
- for (const ref of this.#refSet) {
792
- const key = ref.deref();
793
- if (!key) continue;
794
- const entry = this.#weakMap.get(key);
795
- if (entry) yield [key, entry.value];
796
- }
797
- }
798
- entries() {
799
- return this[Symbol.iterator]();
800
- }
801
- *keys() {
802
- for (const [key] of this) yield key;
803
- }
804
- *values() {
805
- for (const [, value] of this) yield value;
806
- }
807
- forEach(callback, thisArg) {
808
- for (const [key, value] of this) callback.call(thisArg, value, key, this);
809
- }
810
- };
811
- //#endregion
812
- //#region src/annotation/injectable.ts
813
- const _InjectableRegistry = /* @__PURE__ */ new WeakMap();
814
- const _InjectableDeps = new IterableWeakMap();
815
- /**
816
- * Mark the class as an injectable to be handled by Sapling. The class can now be
817
- * be injected into other classes, as well as allow the class to inject other `@Injectable` classes.
818
- *
819
- * @argument deps - An optional array to define any dependencies that this class may require.
820
- */
821
- function Injectable(deps = []) {
822
- return function(target) {
823
- _InjectableRegistry.set(target, null);
824
- _InjectableDeps.set(target, deps);
825
- };
826
- }
827
- /**
828
- * Resolves and instantiates a class along with all of it's transitive dependencies.
829
- *
830
- * Uses topological sort (Kahn's algorithm) to ensure that the dependency graph is created
831
- * in a correct order.
832
- *
833
- * When `resolve` is first called (usually during controller registration),
834
- * it will compute the dependency graph of all `@Injectable` classes and instantiates
835
- * them in the correct order.
836
- *
837
- * Subsequent calls to dependencies that have already been resolved are cached, so they will
838
- * re-use the created singletons instead of re-instantiation.
839
- */
840
- function _resolve(ctor) {
841
- const inDegree = /* @__PURE__ */ new Map();
842
- const graph = /* @__PURE__ */ new Map();
843
- _InjectableDeps.forEach((deps, node) => {
844
- inDegree.set(node, inDegree.get(node) || 0);
845
- deps.forEach((dep) => {
846
- if (dep === void 0) throw new Error(`There is an @Injectable (${node.name}) which has a dependency that cannot be found. This is likely caused by a circular dependency.`);
847
- inDegree.set(dep, inDegree.get(dep) || 0);
848
- inDegree.set(node, inDegree.get(node) + 1);
849
- if (!graph.has(dep)) graph.set(dep, []);
850
- graph.get(dep).push(node);
851
- });
852
- });
853
- const queue = [];
854
- inDegree.forEach((deg, node) => {
855
- if (deg === 0) queue.push(node);
856
- });
857
- while (queue.length) {
858
- const current = queue.shift();
859
- if (!_InjectableRegistry.get(current)) {
860
- const instance = new current(...(_InjectableDeps.get(current) || []).map((dep) => _InjectableRegistry.get(dep)));
861
- _InjectableRegistry.set(current, instance);
862
- }
863
- (graph.get(current) || []).forEach((neighbor) => {
864
- inDegree.set(neighbor, (inDegree.get(neighbor) ?? 0) - 1);
865
- if (inDegree.get(neighbor) === 0) queue.push(neighbor);
866
- });
867
- }
868
- if (!_InjectableRegistry.get(ctor)) throw new Error("Circular dependency detected or injectable not registered");
869
- return _InjectableRegistry.get(ctor);
870
- }
871
- //#endregion
872
965
  //#region src/annotation/controller.ts
873
966
  const _ControllerRegistry = /* @__PURE__ */ new WeakMap();
874
967
  /**
@@ -987,14 +1080,6 @@ function MiddlewareClass(...args) {
987
1080
  return Controller(...args);
988
1081
  }
989
1082
  //#endregion
990
- //#region \0@oxc-project+runtime@0.127.0/helpers/decorate.js
991
- function __decorate(decorators, target, key, desc) {
992
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
993
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
994
- 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;
995
- return c > 3 && r && Object.defineProperty(target, key, r), r;
996
- }
997
- //#endregion
998
1083
  //#region src/middleware/default/error/base.ts
999
1084
  let DefaultBaseErrorMiddleware = class DefaultBaseErrorMiddleware {
1000
1085
  handle(err, _request, _response, _next) {
@@ -1110,4 +1195,22 @@ const DefaultSwaggerMiddleware = {
1110
1195
  Setup
1111
1196
  };
1112
1197
  //#endregion
1113
- 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, _clearOpenApiRegistry, _getControllerSchema, _getOrCreateSchemaDefinition, _getRouteSchema, _getRoutes, _getValidatorSchema, _parseOrThrow, _registerController, _resolve, _saveValidatorSchema, _setControllerSchema, _setRouteSchema, _settings, generateOpenApiSpec, methodResolve, openApiGenerator };
1198
+ //#region src/middleware/default/health/index.ts
1199
+ let DefaultHealthMiddleware = class DefaultHealthMiddleware {
1200
+ constructor(healthRegistrar) {
1201
+ this.healthRegistrar = healthRegistrar;
1202
+ }
1203
+ async readiness(_request, _response, _next) {
1204
+ const up = await this.healthRegistrar._readiness();
1205
+ return ResponseEntity.status(up ? 200 : 503).body({ up });
1206
+ }
1207
+ async liveness(_request, _response, _next) {
1208
+ const up = await this.healthRegistrar._liveness();
1209
+ return ResponseEntity.status(up ? 200 : 503).body({ up });
1210
+ }
1211
+ };
1212
+ __decorate([GET(_settings.health.ready.path)], DefaultHealthMiddleware.prototype, "readiness", null);
1213
+ __decorate([GET(_settings.health.live.path)], DefaultHealthMiddleware.prototype, "liveness", null);
1214
+ DefaultHealthMiddleware = __decorate([MiddlewareClass({ deps: [HealthRegistrar] })], DefaultHealthMiddleware);
1215
+ //#endregion
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tahminator/sapling",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "author": "Tahmid Ahmed",
5
5
  "description": "A library to help you write cleaner Express.js code",
6
6
  "repository": {