@dunx/http 2.0.0 → 2.1.0

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/dist/index.js CHANGED
@@ -4,8 +4,10 @@ import {
4
4
  __decorateElement,
5
5
  __decoratorMetadata,
6
6
  __decoratorStart,
7
+ __privateAdd,
8
+ __privateGet,
7
9
  __runInitializers
8
- } from "./chunk-5z96f3gr.js";
10
+ } from "./chunk-sz4pvqxy.js";
9
11
 
10
12
  // src/route/marker.ts
11
13
  var ROUTE = Symbol.for("dunx.route");
@@ -37,6 +39,9 @@ var Post = verb("POST");
37
39
  var Put = verb("PUT");
38
40
  var Patch = verb("PATCH");
39
41
  var Delete = verb("DELETE");
42
+ // src/route/discover.ts
43
+ import { markedMethods } from "@dunx/core";
44
+
40
45
  // src/route/metadata.ts
41
46
  var META = Symbol.for("dunx.meta");
42
47
  var GUARDS = Symbol.for("dunx.guards");
@@ -92,31 +97,17 @@ var discoverRoutes = (instance) => {
92
97
  const prefix = prefixOf(klass);
93
98
  const classGuards = guardsOf(klass);
94
99
  const members = instance;
95
- const routes = [];
96
- const seen = new Set;
97
- for (let proto = Object.getPrototypeOf(instance);proto !== null && proto !== Object.prototype; proto = Object.getPrototypeOf(proto)) {
98
- for (const [name, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(proto))) {
99
- if (name === "constructor" || seen.has(name))
100
- continue;
101
- const meta2 = routeMetaOf(descriptor.value);
102
- if (!meta2)
103
- continue;
104
- seen.add(name);
105
- const marked = descriptor.value;
106
- routes.push({
107
- method: meta2.method,
108
- path: joinPath(prefix, resolvePath(meta2.path)),
109
- controller: klass.name,
110
- handlerName: name,
111
- handler: members[name].bind(instance),
112
- options: meta2.options,
113
- meta: mergeMeta(klass, marked),
114
- classMeta: metaOf(klass),
115
- guards: [...classGuards, ...guardsOf(marked)]
116
- });
117
- }
118
- }
119
- return routes;
100
+ return markedMethods(Object.getPrototypeOf(instance), routeMetaOf).map(({ name, meta: meta2, value: marked }) => ({
101
+ method: meta2.method,
102
+ path: joinPath(prefix, resolvePath(meta2.path)),
103
+ controller: klass.name,
104
+ handlerName: name,
105
+ handler: members[name].bind(instance),
106
+ options: meta2.options,
107
+ meta: mergeMeta(klass, marked),
108
+ classMeta: metaOf(klass),
109
+ guards: [...classGuards, ...guardsOf(marked)]
110
+ }));
120
111
  };
121
112
  // src/inspect.ts
122
113
  import {
@@ -127,7 +118,9 @@ import {
127
118
 
128
119
  // src/ws/discover.ts
129
120
  import {
130
- AppError
121
+ AppError,
122
+ classOf,
123
+ markedMethods as markedMethods2
131
124
  } from "@dunx/core";
132
125
 
133
126
  // src/ws/marker.ts
@@ -157,29 +150,14 @@ var normalizePath = (path) => {
157
150
  const joined = `/${path}`.replace(/\/{2,}/g, "/");
158
151
  return joined.length > 1 ? joined.replace(/\/$/, "") : "/";
159
152
  };
160
- var eachHandler = (start) => {
161
- const found = [];
162
- const seen = new Set;
163
- for (let proto = start;proto !== null && proto !== Object.prototype; proto = Object.getPrototypeOf(proto)) {
164
- for (const [name, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(proto))) {
165
- if (name === "constructor" || seen.has(name))
166
- continue;
167
- const meta2 = handlerMetaOf(descriptor.value);
168
- if (!meta2)
169
- continue;
170
- seen.add(name);
171
- found.push([name, meta2]);
172
- }
173
- }
174
- return found;
175
- };
153
+ var eachHandler = (start) => markedMethods2(start, handlerMetaOf);
176
154
  var discoverGateway = (instance) => {
177
155
  const klass = instance.constructor;
178
156
  const members = instance;
179
157
  return {
180
158
  name: klass.name,
181
159
  path: normalizePath(gatewayPathOf(klass)),
182
- handlers: eachHandler(Object.getPrototypeOf(instance)).map(([name, meta2]) => ({
160
+ handlers: eachHandler(Object.getPrototypeOf(instance)).map(({ name, meta: meta2 }) => ({
183
161
  kind: meta2.kind,
184
162
  event: meta2.event,
185
163
  method: name,
@@ -187,12 +165,7 @@ var discoverGateway = (instance) => {
187
165
  }))
188
166
  };
189
167
  };
190
- var findHandlerMethod = (ctor) => eachHandler(ctor.prototype)[0]?.[0];
191
- var classOf = (entry) => {
192
- if (typeof entry === "function")
193
- return { token: entry, ctor: entry };
194
- return entry.provider.kind === "class" ? { token: entry.token, ctor: entry.provider.ctor } : undefined;
195
- };
168
+ var findHandlerMethod = (ctor) => eachHandler(ctor.prototype)[0]?.name;
196
169
  var discoverGateways = (modules, resolve) => {
197
170
  const discovered = [];
198
171
  for (const module of modules) {
@@ -271,6 +244,13 @@ var classOf2 = (entry) => {
271
244
  var gatewaysOf = (root) => collectModules(root).flatMap((module) => (module.options.providers ?? []).map(classOf2).filter((ctor) => ctor !== undefined).filter(isGateway).map((ctor) => gatewayFor(ctor, module.name)));
272
245
  // src/server/client-address.ts
273
246
  import { AppError as AppError2 } from "@dunx/core";
247
+ var trustedHops = (setting) => {
248
+ if (setting === true)
249
+ return 1;
250
+ if (setting === false)
251
+ return 0;
252
+ return Number.isFinite(setting) ? Math.max(0, Math.trunc(setting)) : 0;
253
+ };
274
254
  var sources = new WeakMap;
275
255
 
276
256
  class ClientAddress {
@@ -279,10 +259,12 @@ class ClientAddress {
279
259
  if (!source) {
280
260
  throw new AppError2("ClientAddress has no server yet. The address comes from the live Bun " + "server, so it is only available once listen() has run.");
281
261
  }
282
- if (source.trustProxy) {
283
- const forwarded = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim();
284
- if (forwarded)
285
- return forwarded;
262
+ const hops = trustedHops(source.trustProxy);
263
+ if (hops > 0) {
264
+ const entries = (req.headers.get("x-forwarded-for") ?? "").split(",").map((entry2) => entry2.trim()).filter((entry2) => entry2.length > 0);
265
+ const entry = entries[Math.max(0, entries.length - hops)];
266
+ if (entry)
267
+ return entry;
286
268
  }
287
269
  return source.server.requestIP(req)?.address;
288
270
  }
@@ -761,6 +743,7 @@ class PubSub {
761
743
  import {
762
744
  AppError as AppError7,
763
745
  Logger as Logger2,
746
+ runtimeInfo,
764
747
  ShutdownHooks
765
748
  } from "@dunx/core";
766
749
 
@@ -1305,6 +1288,7 @@ class HttpApplication {
1305
1288
  ...gateways.length === 0 ? [] : [`${gateways.length} gateway(s)`]
1306
1289
  ].join(" and ");
1307
1290
  this.#app.get(Logger2).info(`Serving ${subject}`, {
1291
+ ...runtimeInfo(),
1308
1292
  routes: routes.map((route) => `${route.method} ${route.path}`),
1309
1293
  ...gateways.length === 0 ? {} : {
1310
1294
  gateways: gateways.map((gateway) => ({
@@ -1315,8 +1299,12 @@ class HttpApplication {
1315
1299
  }
1316
1300
  });
1317
1301
  }
1302
+ drain() {
1303
+ return this.#app.drain();
1304
+ }
1318
1305
  async shutdown() {
1319
1306
  this.#shuttingDown ??= (async () => {
1307
+ await this.#app.drain();
1320
1308
  await this.#server?.stop(this.#websocket !== undefined);
1321
1309
  this.#server = undefined;
1322
1310
  await this.#app.get(PubSub).close();
@@ -1626,6 +1614,355 @@ class RedisRelay {
1626
1614
  Object.defineProperty(RedisRelay, Symbol.for("dunx.deps"), {
1627
1615
  value: () => [{ unresolved: "options: RedisRelayOptions = {}" }]
1628
1616
  });
1617
+ // src/health/contracts.ts
1618
+ class HealthIndicator {
1619
+ critical = true;
1620
+ }
1621
+
1622
+ class PingProbe {
1623
+ }
1624
+
1625
+ class QueryProbe {
1626
+ }
1627
+ // src/health/controller.ts
1628
+ import { inject } from "@dunx/core";
1629
+
1630
+ // src/health/registry.ts
1631
+ var bounded = async (indicator, timeoutMs) => {
1632
+ let timer;
1633
+ const timeout = new Promise((resolve2) => {
1634
+ timer = setTimeout(() => resolve2({ state: "unknown", detail: `no answer in ${timeoutMs} ms` }), timeoutMs);
1635
+ timer.unref?.();
1636
+ });
1637
+ try {
1638
+ return await Promise.race([
1639
+ Promise.resolve().then(() => indicator.check()).catch((error) => ({
1640
+ state: "down",
1641
+ detail: error instanceof Error ? error.message : String(error)
1642
+ })),
1643
+ timeout
1644
+ ]);
1645
+ } finally {
1646
+ if (timer)
1647
+ clearTimeout(timer);
1648
+ }
1649
+ };
1650
+ var worst = (checks) => {
1651
+ const critical = checks.filter((check) => check.critical);
1652
+ if (critical.some((check) => check.state === "down"))
1653
+ return "down";
1654
+ if (critical.some((check) => check.state === "unknown"))
1655
+ return "unknown";
1656
+ return "up";
1657
+ };
1658
+
1659
+ class HealthOptions {
1660
+ liveness;
1661
+ readiness;
1662
+ timeoutMs;
1663
+ routes;
1664
+ drainDelayMs;
1665
+ constructor(init = {}) {
1666
+ this.liveness = init.liveness ?? [];
1667
+ this.readiness = init.readiness ?? [];
1668
+ this.timeoutMs = init.timeoutMs ?? 2000;
1669
+ this.routes = init.routes ?? true;
1670
+ this.drainDelayMs = Math.max(0, init.drainDelayMs ?? 0);
1671
+ }
1672
+ }
1673
+ Object.defineProperty(HealthOptions, Symbol.for("dunx.deps"), {
1674
+ value: () => [{ unresolved: "init: HealthOptionsInit = {}" }]
1675
+ });
1676
+
1677
+ class HealthRegistry {
1678
+ options;
1679
+ readiness_;
1680
+ #startedAt = Date.now();
1681
+ constructor(options, readiness_) {
1682
+ this.options = options;
1683
+ this.readiness_ = readiness_;
1684
+ }
1685
+ async report(indicators) {
1686
+ const checks = await Promise.all(indicators.map(async (indicator) => {
1687
+ const started = performance.now();
1688
+ const result = await bounded(indicator, this.options.timeoutMs);
1689
+ return {
1690
+ name: indicator.name,
1691
+ state: result.state,
1692
+ critical: indicator.critical,
1693
+ ms: Math.round(performance.now() - started),
1694
+ ...result.detail === undefined ? {} : { detail: result.detail }
1695
+ };
1696
+ }));
1697
+ return {
1698
+ status: worst(checks),
1699
+ draining: this.readiness_.draining,
1700
+ uptimeMs: Date.now() - this.#startedAt,
1701
+ checks
1702
+ };
1703
+ }
1704
+ liveness() {
1705
+ return this.report(this.options.liveness);
1706
+ }
1707
+ async readiness() {
1708
+ const report = await this.report(this.options.readiness);
1709
+ if (!this.readiness_.draining)
1710
+ return report;
1711
+ return {
1712
+ ...report,
1713
+ status: "down",
1714
+ checks: [
1715
+ {
1716
+ name: "readiness",
1717
+ state: "down",
1718
+ critical: true,
1719
+ ms: 0,
1720
+ detail: this.readiness_.reason ?? "not accepting traffic"
1721
+ },
1722
+ ...report.checks
1723
+ ]
1724
+ };
1725
+ }
1726
+ }
1727
+ Object.defineProperty(HealthRegistry, Symbol.for("dunx.deps"), {
1728
+ value: () => [HealthOptions, { unresolved: "private readonly readiness_: Readiness", typeOnly: "Readiness" }]
1729
+ });
1730
+
1731
+ // src/health/controller.ts
1732
+ var answer = (report) => Response.json(report, { status: report.status === "up" ? 200 : 503 });
1733
+ var _dec = [
1734
+ Controller("health"),
1735
+ ApiHidden()
1736
+ ];
1737
+ var _dec2 = [
1738
+ Public(),
1739
+ Get("/live")
1740
+ ];
1741
+ var _dec3 = [
1742
+ Public(),
1743
+ Get("/ready")
1744
+ ];
1745
+ var _health = new WeakMap;
1746
+ var _init = __decoratorStart(undefined);
1747
+
1748
+ class HealthController {
1749
+ constructor() {
1750
+ __privateAdd(this, _health, inject(HealthRegistry));
1751
+ __runInitializers(_init, 5, this);
1752
+ }
1753
+ async live() {
1754
+ return answer(await __privateGet(this, _health).liveness());
1755
+ }
1756
+ async ready() {
1757
+ return answer(await __privateGet(this, _health).readiness());
1758
+ }
1759
+ }
1760
+ __decorateElement(_init, 1, "live", _dec2, HealthController);
1761
+ __decorateElement(_init, 1, "ready", _dec3, HealthController);
1762
+ HealthController = __decorateElement(_init, 0, "HealthController", _dec, HealthController);
1763
+ __runInitializers(_init, 1, HealthController);
1764
+ __decoratorMetadata(_init, HealthController);
1765
+ let _HealthController = HealthController;
1766
+ // src/health/indicators.ts
1767
+ import { statfs } from "fs/promises";
1768
+ var ms = (started) => Math.round(performance.now() - started);
1769
+
1770
+ class RedisIndicator extends HealthIndicator {
1771
+ redis;
1772
+ name = "redis";
1773
+ constructor(redis) {
1774
+ super();
1775
+ this.redis = redis;
1776
+ }
1777
+ async check() {
1778
+ const started = performance.now();
1779
+ await this.redis.ping();
1780
+ return { state: "up", detail: `${ms(started)} ms` };
1781
+ }
1782
+ }
1783
+ Object.defineProperty(RedisIndicator, Symbol.for("dunx.deps"), {
1784
+ value: () => [{ unresolved: "private readonly redis: PingProbe", typeOnly: "PingProbe" }]
1785
+ });
1786
+
1787
+ class DatabaseIndicator extends HealthIndicator {
1788
+ db;
1789
+ name = "database";
1790
+ constructor(db) {
1791
+ super();
1792
+ this.db = db;
1793
+ }
1794
+ async check() {
1795
+ const started = performance.now();
1796
+ await this.db.ping();
1797
+ return { state: "up", detail: `${ms(started)} ms` };
1798
+ }
1799
+ }
1800
+ Object.defineProperty(DatabaseIndicator, Symbol.for("dunx.deps"), {
1801
+ value: () => [{ unresolved: "private readonly db: QueryProbe", typeOnly: "QueryProbe" }]
1802
+ });
1803
+
1804
+ class MemoryOptions {
1805
+ maxRssBytes;
1806
+ constructor(init) {
1807
+ this.maxRssBytes = init.maxRssBytes;
1808
+ }
1809
+ }
1810
+ Object.defineProperty(MemoryOptions, Symbol.for("dunx.deps"), {
1811
+ value: () => [{ unresolved: "init: MemoryOptionsInit" }]
1812
+ });
1813
+ var MIB = 1024 * 1024;
1814
+ var mib = (bytes) => `${Math.round(bytes / MIB)} MiB`;
1815
+
1816
+ class MemoryIndicator extends HealthIndicator {
1817
+ options;
1818
+ name = "memory";
1819
+ critical = false;
1820
+ constructor(options) {
1821
+ super();
1822
+ this.options = options;
1823
+ }
1824
+ check() {
1825
+ const { rss } = process.memoryUsage();
1826
+ const detail = `${mib(rss)} of ${mib(this.options.maxRssBytes)}`;
1827
+ return rss > this.options.maxRssBytes ? { state: "down", detail } : { state: "up", detail };
1828
+ }
1829
+ }
1830
+ Object.defineProperty(MemoryIndicator, Symbol.for("dunx.deps"), {
1831
+ value: () => [MemoryOptions]
1832
+ });
1833
+
1834
+ class DiskOptions {
1835
+ path;
1836
+ maxUsedFraction;
1837
+ constructor(init) {
1838
+ this.path = init.path;
1839
+ this.maxUsedFraction = init.maxUsedFraction;
1840
+ }
1841
+ }
1842
+ Object.defineProperty(DiskOptions, Symbol.for("dunx.deps"), {
1843
+ value: () => [{ unresolved: "init: DiskOptionsInit" }]
1844
+ });
1845
+
1846
+ class DiskIndicator extends HealthIndicator {
1847
+ options;
1848
+ name = "disk";
1849
+ critical = false;
1850
+ constructor(options) {
1851
+ super();
1852
+ this.options = options;
1853
+ }
1854
+ async check() {
1855
+ const stats = await statfs(this.options.path);
1856
+ const total = Number(stats.blocks) * Number(stats.bsize);
1857
+ const free = Number(stats.bavail) * Number(stats.bsize);
1858
+ if (total <= 0)
1859
+ return { state: "unknown", detail: "no size reported" };
1860
+ const used = (total - free) / total;
1861
+ const detail = `${Math.round(used * 100)}% of ${mib(total)} used`;
1862
+ return used > this.options.maxUsedFraction ? { state: "down", detail } : { state: "up", detail };
1863
+ }
1864
+ }
1865
+ Object.defineProperty(DiskIndicator, Symbol.for("dunx.deps"), {
1866
+ value: () => [DiskOptions]
1867
+ });
1868
+ // src/health/module.ts
1869
+ import {
1870
+ Module as Module2,
1871
+ provide as provide3
1872
+ } from "@dunx/core";
1873
+
1874
+ // src/health/readiness.ts
1875
+ class ReadinessOptions {
1876
+ drainDelayMs;
1877
+ constructor(init = {}) {
1878
+ this.drainDelayMs = Math.max(0, init.drainDelayMs ?? 0);
1879
+ }
1880
+ }
1881
+ Object.defineProperty(ReadinessOptions, Symbol.for("dunx.deps"), {
1882
+ value: () => [{ unresolved: "init: ReadinessOptionsInit = {}" }]
1883
+ });
1884
+
1885
+ class Readiness {
1886
+ options;
1887
+ #reason;
1888
+ #draining = false;
1889
+ constructor(options) {
1890
+ this.options = options;
1891
+ }
1892
+ get draining() {
1893
+ return this.#draining || this.#reason !== undefined;
1894
+ }
1895
+ get reason() {
1896
+ return this.#draining ? this.#reason ?? "shutting down" : this.#reason;
1897
+ }
1898
+ hold(reason) {
1899
+ this.#reason = reason;
1900
+ }
1901
+ release() {
1902
+ this.#reason = undefined;
1903
+ }
1904
+ async onDrain() {
1905
+ this.#draining = true;
1906
+ if (this.options.drainDelayMs > 0) {
1907
+ await Bun.sleep(this.options.drainDelayMs);
1908
+ }
1909
+ }
1910
+ }
1911
+ Object.defineProperty(Readiness, Symbol.for("dunx.deps"), {
1912
+ value: () => [ReadinessOptions]
1913
+ });
1914
+
1915
+ // src/health/module.ts
1916
+ var wiring = (options) => [
1917
+ ...options,
1918
+ provide3(ReadinessOptions, {
1919
+ useFactory: (opts) => new ReadinessOptions({ drainDelayMs: opts.drainDelayMs }),
1920
+ inject: [HealthOptions]
1921
+ }),
1922
+ provide3(Readiness, {
1923
+ useFactory: (opts) => new Readiness(opts),
1924
+ inject: [ReadinessOptions]
1925
+ }),
1926
+ provide3(HealthRegistry, {
1927
+ useFactory: (opts, readiness) => new HealthRegistry(opts, readiness),
1928
+ inject: [HealthOptions, Readiness]
1929
+ })
1930
+ ];
1931
+ var surface = [HealthOptions, HealthRegistry, Readiness];
1932
+ var _dec = [
1933
+ Module2({})
1934
+ ];
1935
+ var _init = __decoratorStart(undefined);
1936
+
1937
+ class HealthModule {
1938
+ static forRoot(init = {}) {
1939
+ const options = new HealthOptions(init);
1940
+ return {
1941
+ module: HealthModule,
1942
+ ...options.routes ? { controllers: [HealthController] } : {},
1943
+ exports: surface,
1944
+ providers: wiring([provide3(HealthOptions, { useValue: options })])
1945
+ };
1946
+ }
1947
+ static forRootAsync(config) {
1948
+ return {
1949
+ module: HealthModule,
1950
+ ...config.imports ? { imports: config.imports } : {},
1951
+ ...config.routes ?? true ? { controllers: [HealthController] } : {},
1952
+ exports: surface,
1953
+ providers: wiring([
1954
+ provide3(HealthOptions, {
1955
+ useFactory: async (...deps) => new HealthOptions(await config.useFactory(...deps)),
1956
+ inject: config.inject ?? []
1957
+ })
1958
+ ])
1959
+ };
1960
+ }
1961
+ }
1962
+ HealthModule = __decorateElement(_init, 0, "HealthModule", _dec, HealthModule);
1963
+ __runInitializers(_init, 1, HealthModule);
1964
+ __decoratorMetadata(_init, HealthModule);
1965
+ let _HealthModule = HealthModule;
1629
1966
  export {
1630
1967
  withUpgradeRoutes,
1631
1968
  withCors,
@@ -1670,12 +2007,17 @@ export {
1670
2007
  Roles,
1671
2008
  RequestLoggingMiddleware,
1672
2009
  RedisRelay,
2010
+ RedisIndicator,
2011
+ ReadinessOptions,
2012
+ Readiness,
1673
2013
  ROLES,
1674
2014
  REQUEST_ID_HEADER,
2015
+ QueryProbe,
1675
2016
  Put,
1676
2017
  Public,
1677
2018
  PubSub,
1678
2019
  Post,
2020
+ PingProbe,
1679
2021
  Patch,
1680
2022
  PUBLIC,
1681
2023
  OnUpgrade,
@@ -1685,20 +2027,30 @@ export {
1685
2027
  OnMessage,
1686
2028
  OnDrain,
1687
2029
  OnClose,
2030
+ MemoryOptions,
2031
+ MemoryIndicator,
1688
2032
  HttpStatusCode,
1689
2033
  HttpFactory,
1690
2034
  HttpError,
2035
+ HealthRegistry,
2036
+ HealthOptions,
2037
+ HealthModule,
2038
+ HealthIndicator,
2039
+ HealthController,
1691
2040
  HandlerKind,
1692
2041
  HIDDEN,
1693
2042
  Get,
1694
2043
  Gateway,
1695
2044
  ErrorFilter,
2045
+ DiskOptions,
2046
+ DiskIndicator,
1696
2047
  Delete,
2048
+ DatabaseIndicator,
1697
2049
  DEFAULT_RELAY_CHANNEL,
1698
2050
  Controller,
1699
2051
  ClientAddress,
1700
2052
  ApiHidden
1701
2053
  };
1702
2054
 
1703
- //# debugId=BA2C19D6241E4DE564756E2164756E21
2055
+ //# debugId=4FEC45E6E771085064756E2164756E21
1704
2056
  //# sourceMappingURL=index.js.map