@dunx/http 3.6.0 → 3.8.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.
Files changed (40) hide show
  1. package/README.md +9 -0
  2. package/dist/chunk-08k9vq31.js +94 -0
  3. package/dist/{chunk-p9hdmkm6.js → chunk-1jt27yka.js} +8 -83
  4. package/dist/chunk-3eecdh6d.js +160 -0
  5. package/dist/chunk-cx4btdwe.js +56 -0
  6. package/dist/client/service.d.ts +13 -10
  7. package/dist/client/sse.d.ts +22 -8
  8. package/dist/client.d.ts +2 -0
  9. package/dist/client.js +72 -20
  10. package/dist/connect/middleware.d.ts +26 -0
  11. package/dist/connect/module.d.ts +29 -0
  12. package/dist/connect/options.d.ts +63 -0
  13. package/dist/connect/registry.d.ts +47 -0
  14. package/dist/connect.d.ts +12 -0
  15. package/dist/connect.js +213 -0
  16. package/dist/index.d.ts +4 -1
  17. package/dist/index.js +312 -189
  18. package/dist/internal.d.ts +8 -10
  19. package/dist/internal.js +7 -3
  20. package/dist/route/claims.d.ts +11 -0
  21. package/dist/route/metadata.d.ts +11 -0
  22. package/dist/route/prefix.d.ts +6 -0
  23. package/dist/server/application.d.ts +1 -1
  24. package/dist/server/binding.d.ts +4 -2
  25. package/dist/server/claimed-routes.d.ts +15 -0
  26. package/dist/server/cors.d.ts +2 -2
  27. package/dist/server/metrics.d.ts +2 -0
  28. package/dist/server/middleware.d.ts +19 -2
  29. package/dist/server/options-provider.d.ts +3 -0
  30. package/dist/server/options.d.ts +8 -0
  31. package/dist/server/routes.d.ts +7 -2
  32. package/dist/server/trace-context.d.ts +9 -11
  33. package/dist/sse/decorators.d.ts +28 -0
  34. package/dist/sse/event.d.ts +20 -0
  35. package/dist/sse/stream.d.ts +38 -0
  36. package/dist/static/files.d.ts +7 -0
  37. package/dist/static/options.d.ts +2 -2
  38. package/dist/throttle/guard.d.ts +3 -1
  39. package/package.json +19 -2
  40. package/dist/chunk-gmtwad7f.js +0 -71
package/dist/index.js CHANGED
@@ -12,6 +12,8 @@ import {
12
12
  PUBLIC2,
13
13
  HIDDEN2,
14
14
  UNMATCHED2,
15
+ STREAMS2,
16
+ REQUEST_SERVER,
15
17
  Roles2,
16
18
  Public2,
17
19
  ApiHidden2,
@@ -19,89 +21,32 @@ import {
19
21
  metaOf2,
20
22
  mergeMeta2,
21
23
  discoverRoutes2,
22
- RoutePrefix2,
24
+ RoutePrefix2
25
+ } from "./chunk-1jt27yka.js";
26
+ import {
23
27
  HandlerKind,
24
28
  markHandler,
25
29
  markGateway,
26
30
  discoverGateways,
27
- buildContext2
28
- } from "./chunk-p9hdmkm6.js";
31
+ buildContext2,
32
+ StaticOptions2,
33
+ StaticFiles2
34
+ } from "./chunk-3eecdh6d.js";
29
35
  import {
30
36
  TRACEPARENT_HEADER2,
31
37
  TRACESTATE_HEADER2,
32
38
  TRACERESPONSE_HEADER2,
33
39
  TraceContext2
34
- } from "./chunk-gmtwad7f.js";
35
- var __create = Object.create;
36
- var __defProp = Object.defineProperty;
37
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
38
- var __name = (target, name) => {
39
- Object.defineProperty(target, "name", {
40
- value: name,
41
- enumerable: false,
42
- configurable: true
43
- });
44
- return target;
45
- };
46
- var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
47
- var __typeError = (msg) => {
48
- throw TypeError(msg);
49
- };
50
- var __defNormalProp = (obj, key, value) => (key in obj) ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
51
- var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
52
- var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
53
- var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
54
- var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
55
- var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
56
- var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
57
- var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
58
- var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
59
- var __expectFn = (fn) => fn !== undefined && typeof fn !== "function" ? __typeError("Function expected") : fn;
60
- var __decoratorContext = (kind, name, done, metadata, fns) => ({
61
- kind: __decoratorStrings[kind],
62
- name,
63
- metadata,
64
- addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null))
65
- });
66
- var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
67
- var __runInitializers = (array, flags, self, value) => {
68
- for (var i = 0, fns = array[flags >> 1], n = fns && fns.length;i < n; i++)
69
- flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
70
- return value;
71
- };
72
- var __decorateElement = (array, flags, name, decorators, target, extra) => {
73
- var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
74
- var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
75
- var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
76
- var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : {
77
- get [name]() {
78
- return __privateGet(this, extra);
79
- },
80
- set [name](x) {
81
- __privateSet(this, extra, x);
82
- }
83
- }, name));
84
- k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
85
- for (var i = decorators.length - 1;i >= 0; i--) {
86
- ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
87
- if (k) {
88
- ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => (name in x) };
89
- if (k ^ 3)
90
- access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
91
- if (k > 2)
92
- access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
93
- }
94
- it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? undefined : { get: desc.get, set: desc.set } : target, ctx);
95
- done._ = 1;
96
- if (k ^ 4 || it === undefined)
97
- __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
98
- else if (typeof it !== "object" || it === null)
99
- __typeError("Object expected");
100
- else
101
- __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
102
- }
103
- return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
104
- };
40
+ } from "./chunk-cx4btdwe.js";
41
+ import {
42
+ __privateGet,
43
+ __privateAdd,
44
+ __decoratorStart,
45
+ __decoratorMetadata,
46
+ __runInitializers,
47
+ __decorateElement,
48
+ PathClaims
49
+ } from "./chunk-08k9vq31.js";
105
50
 
106
51
  // src/route/decorators.ts
107
52
  var Controller = (prefix = "") => (target) => {
@@ -217,6 +162,17 @@ import {
217
162
  RequestContext as RequestContext3
218
163
  } from "@dunx/core";
219
164
 
165
+ // src/server/claimed-routes.ts
166
+ class ClaimedRoutes {
167
+ #paths = new Set;
168
+ attach(paths) {
169
+ this.#paths = new Set(paths);
170
+ }
171
+ has(path) {
172
+ return this.#paths.has(path);
173
+ }
174
+ }
175
+
220
176
  // src/server/metrics.ts
221
177
  import { Durations } from "@dunx/core";
222
178
  var UNMATCHED_ROUTE = "(unmatched)";
@@ -233,16 +189,21 @@ var seriesFor = (route, method) => ({
233
189
  class RequestMetrics {
234
190
  #series = new Map;
235
191
  #unmatched = new Map;
192
+ #claimed = new Map;
193
+ #claimedPaths = new Set;
236
194
  #since = new Date;
237
195
  #server;
238
196
  observe(ctx, status, durationNs, traceId) {
239
197
  let series = this.#series.get(ctx);
240
198
  if (series === undefined) {
241
199
  if (ctx.get(UNMATCHED2) === true) {
242
- series = this.#unmatched.get(ctx.method);
200
+ const claimed = this.#claimedPaths.has(ctx.path);
201
+ const key = claimed ? `${ctx.method} ${ctx.path}` : ctx.method;
202
+ const bucket = claimed ? this.#claimed : this.#unmatched;
203
+ series = bucket.get(key);
243
204
  if (series === undefined) {
244
- series = seriesFor(UNMATCHED_ROUTE, ctx.method);
245
- this.#unmatched.set(ctx.method, series);
205
+ series = seriesFor(claimed ? ctx.path : UNMATCHED_ROUTE, ctx.method);
206
+ bucket.set(key, series);
246
207
  }
247
208
  } else {
248
209
  series = seriesFor(ctx.path, ctx.method);
@@ -262,6 +223,7 @@ class RequestMetrics {
262
223
  const routes = [];
263
224
  for (const series of [
264
225
  ...this.#series.values(),
226
+ ...this.#claimed.values(),
265
227
  ...this.#unmatched.values()
266
228
  ]) {
267
229
  routes.push({
@@ -282,9 +244,13 @@ class RequestMetrics {
282
244
  }
283
245
  reset() {
284
246
  this.#series.clear();
247
+ this.#claimed.clear();
285
248
  this.#unmatched.clear();
286
249
  this.#since = new Date;
287
250
  }
251
+ claim(paths) {
252
+ this.#claimedPaths = new Set(paths);
253
+ }
288
254
  attach(server) {
289
255
  this.#server = server;
290
256
  }
@@ -922,6 +888,10 @@ import {
922
888
  teardownFailures as toFailures
923
889
  } from "@dunx/core";
924
890
 
891
+ // src/server/middleware.ts
892
+ var compose = (middleware, ctx, handler) => middleware.reduceRight((next, current) => (req) => current.handle(req, ctx, () => next(req)), handler);
893
+ var hasClaimedPaths = (value) => typeof value.claimedPaths === "function";
894
+
925
895
  // src/server/request-logging.ts
926
896
  import {
927
897
  Logger as Logger2,
@@ -1213,7 +1183,7 @@ var applyCors = (options, req, response) => {
1213
1183
  return response;
1214
1184
  };
1215
1185
  var withCors = (options, handler) => {
1216
- return async (req) => applyCors(options, req, await handler(req));
1186
+ return async (req, server) => applyCors(options, req, await handler(req, server));
1217
1187
  };
1218
1188
  var preflight = (options, methods) => {
1219
1189
  const allowMethods = (options.methods ?? methods).join(", ");
@@ -1336,9 +1306,6 @@ var buildInputReader = (options) => {
1336
1306
  return (req) => fill({ req });
1337
1307
  };
1338
1308
 
1339
- // src/server/middleware.ts
1340
- var compose = (middleware, ctx, handler) => middleware.reduceRight((next, current) => (req) => current.handle(req, ctx, () => next(req)), handler);
1341
-
1342
1309
  // src/server/routes.ts
1343
1310
  var construct = (guard) => new guard;
1344
1311
  var toResponse = (value, status) => {
@@ -1351,15 +1318,9 @@ var toResponse = (value, status) => {
1351
1318
  };
1352
1319
  var statusFor = (route) => route.options?.status ?? defaultStatusFor2(route.method);
1353
1320
  var assertNoCollisions = (discovered) => {
1354
- const owners = new Map;
1321
+ const claims = new PathClaims("Route", "Bun would keep only one of them.");
1355
1322
  for (const route of discovered) {
1356
- const key = `${route.method} ${route.path}`;
1357
- const owner = `${route.controller}.${route.handlerName}`;
1358
- const existing = owners.get(key);
1359
- if (existing !== undefined) {
1360
- throw new AppError6(`Route collision: ${key} is declared by ${existing} and by ${owner}. ` + "Bun would keep only one of them.");
1361
- }
1362
- owners.set(key, owner);
1323
+ claims.claim(`${route.method} ${route.path}`, `${route.controller}.${route.handlerName}`);
1363
1324
  }
1364
1325
  };
1365
1326
  var assertNoGatewayCollisions = (discovered, gatewayPaths) => {
@@ -1376,7 +1337,7 @@ var withUpgradeRoutes = (routes, gateways) => {
1376
1337
  merged[path] = { GET: upgrade };
1377
1338
  return merged;
1378
1339
  };
1379
- var unmatchedContext = (req, isPublic) => Object.freeze({
1340
+ var unmatchedContext = (req, isPublic, server) => Object.freeze({
1380
1341
  controller: "(unmatched)",
1381
1342
  handler: "(none)",
1382
1343
  method: req.method,
@@ -1387,16 +1348,52 @@ var unmatchedContext = (req, isPublic) => Object.freeze({
1387
1348
  return true;
1388
1349
  if (key.id === PUBLIC2.id && isPublic)
1389
1350
  return true;
1351
+ if (key.id === REQUEST_SERVER.id)
1352
+ return server;
1390
1353
  return;
1391
1354
  }
1392
1355
  });
1356
+ var assertNoShadowedClaims = (discovered, middleware) => {
1357
+ const routed = new Map;
1358
+ for (const route of discovered) {
1359
+ if (!routed.has(route.path)) {
1360
+ routed.set(route.path, `${route.controller}.${route.handlerName}`);
1361
+ }
1362
+ }
1363
+ for (const entry of middleware) {
1364
+ if (!hasClaimedPaths(entry))
1365
+ continue;
1366
+ const owner = entry.constructor.name;
1367
+ for (const path of entry.claimedPaths()) {
1368
+ const taken = routed.get(path);
1369
+ if (taken !== undefined) {
1370
+ throw new AppError6(`Path collision: ${path} is declared by ${taken} and claimed by ` + `${owner}. A route wins, so the middleware would never see it.`);
1371
+ }
1372
+ }
1373
+ }
1374
+ };
1393
1375
  var buildFallback = (middleware = [], onError = defaultErrorMapper, cors, notFound = "guarded") => {
1394
1376
  const miss = () => {
1395
1377
  throw new HttpError(HttpStatusCode2.NOT_FOUND, "NOT_FOUND");
1396
1378
  };
1397
- const run = async (req) => {
1379
+ const claimedPreflight = new Map;
1380
+ if (cors) {
1381
+ for (const entry of middleware) {
1382
+ if (!hasClaimedPaths(entry))
1383
+ continue;
1384
+ const answer = preflight(cors, entry.claimedMethods());
1385
+ for (const path of entry.claimedPaths())
1386
+ claimedPreflight.set(path, answer);
1387
+ }
1388
+ }
1389
+ const run = async (req, server) => {
1390
+ if (req.method === "OPTIONS") {
1391
+ const answer = claimedPreflight.get(new URL(req.url).pathname);
1392
+ if (answer !== undefined)
1393
+ return answer(req);
1394
+ }
1398
1395
  try {
1399
- return await compose(middleware, unmatchedContext(req, notFound === "public"), miss)(req);
1396
+ return await compose(middleware, unmatchedContext(req, notFound === "public", server), miss)(req);
1400
1397
  } catch (error) {
1401
1398
  return TraceContext2.stamp(onError(error, req), req);
1402
1399
  }
@@ -1450,7 +1447,8 @@ var buildRoutes = (discovered, middleware = [], onError = defaultErrorMapper, co
1450
1447
  ...(route.moduleMiddleware ?? []).map((entry) => guardOf(entry, route.module)),
1451
1448
  ...(route.guards ?? []).map((guard) => guardOf(guard, route.module))
1452
1449
  ];
1453
- const chained = compose(chain, buildContext2(route), async (req) => toResponse(await route.handler(await read(req)), status));
1450
+ const context = buildContext2(route);
1451
+ const chained = compose(chain, context, async (req) => toResponse(await route.handler(await read(req)), status));
1454
1452
  const guarded = async (req) => {
1455
1453
  try {
1456
1454
  return await chained(req);
@@ -1458,8 +1456,12 @@ var buildRoutes = (discovered, middleware = [], onError = defaultErrorMapper, co
1458
1456
  return TraceContext2.stamp(onError(error, req), req);
1459
1457
  }
1460
1458
  };
1459
+ const served = cors ? withCors(cors, guarded) : directOr(guarded, route, read, status, onError, chain.length === 0);
1461
1460
  const byMethod = routes[route.path] ??= {};
1462
- byMethod[route.method] = cors ? withCors(cors, guarded) : directOr(guarded, route, read, status, onError, chain.length === 0);
1461
+ byMethod[route.method] = context.get(STREAMS2) === true ? (req, server) => {
1462
+ server?.timeout(req, 0);
1463
+ return served(req, server);
1464
+ } : served;
1463
1465
  }
1464
1466
  if (cors) {
1465
1467
  for (const byMethod of Object.values(routes)) {
@@ -1488,7 +1490,7 @@ class ServerBinding {
1488
1490
  }
1489
1491
  bind(plan) {
1490
1492
  const { websocket: ws } = plan;
1491
- const { http2, http1, gatewayPort } = this.#protocols;
1493
+ const { http2, http1, gatewayPort, idleTimeout } = this.#protocols;
1492
1494
  const split = ws !== undefined && gatewayPort !== undefined;
1493
1495
  const table = ws && !split ? {
1494
1496
  routes: withUpgradeRoutes(plan.routes, ws.routes),
@@ -1499,6 +1501,7 @@ class ServerBinding {
1499
1501
  fetch: plan.fetch,
1500
1502
  ...http2 !== undefined && { http2 },
1501
1503
  ...http1 !== undefined && { http1 },
1504
+ ...idleTimeout !== undefined && { idleTimeout },
1502
1505
  ...table
1503
1506
  });
1504
1507
  if (split) {
@@ -1506,6 +1509,7 @@ class ServerBinding {
1506
1509
  this.#gateways = Bun.serve({
1507
1510
  port: gatewayPort,
1508
1511
  fetch: plan.fetch,
1512
+ ...idleTimeout !== undefined && { idleTimeout },
1509
1513
  routes: withUpgradeRoutes({}, ws.routes),
1510
1514
  websocket: ws.websocket
1511
1515
  });
@@ -1598,7 +1602,8 @@ class HttpApplication extends ShutdownAware {
1598
1602
  this.#binding = new ServerBinding({
1599
1603
  http2: options.http2,
1600
1604
  http1: options.http1,
1601
- gatewayPort: options.gatewayPort
1605
+ gatewayPort: options.gatewayPort,
1606
+ idleTimeout: options.idleTimeout
1602
1607
  });
1603
1608
  this.#split = options.gatewayPort !== undefined && websocket !== undefined;
1604
1609
  this.gatewayPaths = websocket?.paths ?? [];
@@ -1664,6 +1669,10 @@ class HttpApplication extends ShutdownAware {
1664
1669
  const ws = this.#websocket;
1665
1670
  if (ws && !this.#split)
1666
1671
  assertNoGatewayCollisions(prefixed, ws.paths);
1672
+ assertNoShadowedClaims(prefixed, middleware);
1673
+ const claimed = middleware.flatMap((entry) => hasClaimedPaths(entry) ? [...entry.claimedPaths()] : []);
1674
+ this.#app.get(ClaimedRoutes).attach(claimed);
1675
+ this.#app.get(RequestMetrics).claim(claimed);
1667
1676
  const fetch = buildFallback(middleware, this.#onError, this.#cors, this.#notFound);
1668
1677
  const bound = this.#binding.bind({ port, routes, fetch, websocket: ws });
1669
1678
  attachAddressSource(this.#app.get(ClientAddress), {
@@ -1805,6 +1814,9 @@ class HttpOptionsProvider {
1805
1814
  get gatewayPort() {
1806
1815
  return;
1807
1816
  }
1817
+ get idleTimeout() {
1818
+ return;
1819
+ }
1808
1820
  }
1809
1821
 
1810
1822
  class DefaultHttpOptions extends HttpOptionsProvider {
@@ -1831,7 +1843,8 @@ function resolveHttpOptions(settings, given) {
1831
1843
  relayResubscribe: settings.relayResubscribe,
1832
1844
  http2: settings.http2,
1833
1845
  http1: settings.http1,
1834
- gatewayPort: settings.gatewayPort
1846
+ gatewayPort: settings.gatewayPort,
1847
+ idleTimeout: settings.idleTimeout
1835
1848
  };
1836
1849
  for (const key of Object.keys(given)) {
1837
1850
  merged[key] = given[key];
@@ -1866,7 +1879,13 @@ class HttpFactory {
1866
1879
  useFactory: (logger, context, settings) => new SocketLoggingMiddleware(logger, context, pick(options.socketLogging, settings.socketLogging)),
1867
1880
  inject: [Logger4, RequestContext3, HttpOptionsProvider]
1868
1881
  });
1869
- const services = [PubSub, ClientAddress, RequestMetrics, RoutePrefix2];
1882
+ const services = [
1883
+ PubSub,
1884
+ ClientAddress,
1885
+ RequestMetrics,
1886
+ RoutePrefix2,
1887
+ ClaimedRoutes
1888
+ ];
1870
1889
  const providers = [...services, logging, metricsMiddleware, socketLogging];
1871
1890
  const scope = {
1872
1891
  module: HttpModule,
@@ -1921,89 +1940,14 @@ class HttpFactory {
1921
1940
  return entries.map((entry) => app.get(entry, root));
1922
1941
  }
1923
1942
  }
1924
- // src/static/files.ts
1925
- import { join, normalize, resolve } from "path";
1926
-
1927
- // src/static/options.ts
1928
- class StaticOptions {
1929
- root;
1930
- path;
1931
- maxAge;
1932
- immutable;
1933
- constructor(init) {
1934
- this.root = init.root;
1935
- this.path = normalizePrefix(init.path ?? "/");
1936
- this.maxAge = init.maxAge ?? 60;
1937
- this.immutable = init.immutable ?? (() => false);
1938
- }
1939
- }
1940
- Object.defineProperty(StaticOptions, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "init: StaticOptionsInit" }] });
1941
- var normalizePrefix = (path) => {
1942
- const trimmed = path.split("/").filter(Boolean).join("/");
1943
- return trimmed === "" ? "/" : `/${trimmed}`;
1944
- };
1945
-
1946
- // src/static/files.ts
1947
- class StaticFiles {
1948
- #options;
1949
- #root;
1950
- #prefix;
1951
- constructor(options) {
1952
- this.#options = options;
1953
- this.#root = resolve(options.root);
1954
- this.#prefix = options.path === "/" ? "/" : `${options.path}/`;
1955
- }
1956
- resolvePath(pathname) {
1957
- const relative = pathname.startsWith(this.#prefix) ? pathname.slice(this.#prefix.length) : pathname.slice(this.#options.path.length);
1958
- let decoded;
1959
- try {
1960
- decoded = decodeURIComponent(relative);
1961
- } catch {
1962
- return;
1963
- }
1964
- if (decoded.includes("\x00"))
1965
- return;
1966
- const candidate = resolve(join(this.#root, normalize(decoded)));
1967
- if (candidate !== this.#root && !candidate.startsWith(`${this.#root}/`)) {
1968
- return;
1969
- }
1970
- return candidate;
1971
- }
1972
- #cacheControl(pathname) {
1973
- const { immutable, maxAge } = this.#options;
1974
- return immutable(pathname) ? "public, max-age=31536000, immutable" : `public, max-age=${maxAge}`;
1975
- }
1976
- async handle(req, _ctx, next) {
1977
- const { pathname } = new URL(req.url);
1978
- if (pathname !== this.#options.path && !pathname.startsWith(this.#prefix)) {
1979
- return next();
1980
- }
1981
- if (req.method !== "GET" && req.method !== "HEAD")
1982
- return next();
1983
- const path = this.resolvePath(pathname);
1984
- if (path === undefined)
1985
- return next();
1986
- const file = Bun.file(path);
1987
- if (!await file.exists())
1988
- return next();
1989
- return new Response(file, {
1990
- headers: {
1991
- "cache-control": this.#cacheControl(pathname),
1992
- ...file.type === "" ? { "content-type": "application/octet-stream" } : {},
1993
- "x-content-type-options": "nosniff"
1994
- }
1995
- });
1996
- }
1997
- }
1998
- Object.defineProperty(StaticFiles, Symbol.for("dunx.deps"), { value: () => [StaticOptions] });
1999
1943
  // src/static/module.ts
2000
1944
  import {
2001
1945
  Module,
2002
1946
  provide as provide2
2003
1947
  } from "@dunx/core";
2004
- var files = () => provide2(StaticFiles, {
2005
- useFactory: (options) => new StaticFiles(options),
2006
- inject: [StaticOptions]
1948
+ var files = () => provide2(StaticFiles2, {
1949
+ useFactory: (options) => new StaticFiles2(options),
1950
+ inject: [StaticOptions2]
2007
1951
  });
2008
1952
  var _dec = [
2009
1953
  Module({})
@@ -2014,9 +1958,9 @@ class StaticModule {
2014
1958
  static forRoot(init) {
2015
1959
  return {
2016
1960
  module: StaticModule,
2017
- exports: [StaticOptions, StaticFiles],
1961
+ exports: [StaticOptions2, StaticFiles2],
2018
1962
  providers: [
2019
- provide2(StaticOptions, { useValue: new StaticOptions(init) }),
1963
+ provide2(StaticOptions2, { useValue: new StaticOptions2(init) }),
2020
1964
  files()
2021
1965
  ]
2022
1966
  };
@@ -2025,10 +1969,10 @@ class StaticModule {
2025
1969
  return {
2026
1970
  module: StaticModule,
2027
1971
  ...config.imports && { imports: config.imports },
2028
- exports: [StaticOptions, StaticFiles],
1972
+ exports: [StaticOptions2, StaticFiles2],
2029
1973
  providers: [
2030
- provide2(StaticOptions, {
2031
- useFactory: async (...deps) => new StaticOptions(await config.useFactory(...deps)),
1974
+ provide2(StaticOptions2, {
1975
+ useFactory: async (...deps) => new StaticOptions2(await config.useFactory(...deps)),
2032
1976
  inject: config.inject ?? []
2033
1977
  }),
2034
1978
  files()
@@ -2092,10 +2036,13 @@ var COMPRESSIBLE = new Set([
2092
2036
  "application/xml",
2093
2037
  "image/svg+xml"
2094
2038
  ]);
2039
+ var ENDLESS = new Set(["text/event-stream"]);
2095
2040
  var isCompressibleType = (contentType) => {
2096
2041
  if (contentType === null)
2097
2042
  return false;
2098
2043
  const type = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
2044
+ if (ENDLESS.has(type))
2045
+ return false;
2099
2046
  if (type.startsWith("text/"))
2100
2047
  return true;
2101
2048
  if (type.endsWith("+json") || type.endsWith("+xml"))
@@ -2304,6 +2251,169 @@ CompressionModule = __decorateElement(_init, 0, "CompressionModule", _dec, Compr
2304
2251
  __runInitializers(_init, 1, CompressionModule);
2305
2252
  __decoratorMetadata(_init, CompressionModule);
2306
2253
  let _CompressionModule = CompressionModule;
2254
+ // src/sse/event.ts
2255
+ var lines = (value) => value.split(/\r\n|\r|\n/);
2256
+ var oneLine = (value) => lines(value)[0] ?? "";
2257
+ var frameEvent = (event) => {
2258
+ const fields = [];
2259
+ if (event.event !== undefined)
2260
+ fields.push(`event: ${oneLine(event.event)}`);
2261
+ if (event.id !== undefined)
2262
+ fields.push(`id: ${oneLine(event.id)}`);
2263
+ if (event.retry !== undefined) {
2264
+ fields.push(`retry: ${Math.trunc(event.retry)}`);
2265
+ }
2266
+ if (event.data !== undefined) {
2267
+ const data = typeof event.data === "string" ? event.data : JSON.stringify(event.data);
2268
+ for (const line of lines(data ?? ""))
2269
+ fields.push(`data: ${line}`);
2270
+ } else if (event.event !== undefined || event.id !== undefined) {
2271
+ fields.push("data: ");
2272
+ }
2273
+ return `${fields.join(`
2274
+ `)}
2275
+
2276
+ `;
2277
+ };
2278
+ var frameComment = (text) => `${lines(text).map((line) => `:${line}`).join(`
2279
+ `)}
2280
+
2281
+ `;
2282
+
2283
+ // src/sse/stream.ts
2284
+ var DEFAULT_HEARTBEAT_MS = 15000;
2285
+ var encoder = new TextEncoder;
2286
+ var SSE_HEADERS = Object.freeze({
2287
+ "content-type": "text/event-stream",
2288
+ "cache-control": "no-cache, no-transform",
2289
+ connection: "keep-alive"
2290
+ });
2291
+
2292
+ class SseStream {
2293
+ #body;
2294
+ #controller;
2295
+ #timer;
2296
+ #lastEventId;
2297
+ #closed = false;
2298
+ #gone = new AbortController;
2299
+ #drained;
2300
+ constructor(options = {}) {
2301
+ this.#body = new ReadableStream({
2302
+ start: (controller) => {
2303
+ this.#controller = controller;
2304
+ },
2305
+ pull: () => {
2306
+ this.#drained?.();
2307
+ this.#drained = undefined;
2308
+ },
2309
+ cancel: () => {
2310
+ this.#stop();
2311
+ }
2312
+ });
2313
+ this.comment();
2314
+ const heartbeatMs = options.heartbeatMs ?? DEFAULT_HEARTBEAT_MS;
2315
+ if (heartbeatMs > 0) {
2316
+ this.#timer = setInterval(() => {
2317
+ this.comment();
2318
+ }, heartbeatMs);
2319
+ this.#timer.unref();
2320
+ }
2321
+ }
2322
+ static from(events, options = {}) {
2323
+ const stream = new SseStream(options);
2324
+ (async () => {
2325
+ try {
2326
+ for await (const event of events) {
2327
+ if (stream.#closed)
2328
+ break;
2329
+ stream.send(event);
2330
+ await stream.#backpressure();
2331
+ }
2332
+ stream.close();
2333
+ } catch (error) {
2334
+ stream.#fail(error);
2335
+ }
2336
+ })();
2337
+ return stream;
2338
+ }
2339
+ get signal() {
2340
+ return this.#gone.signal;
2341
+ }
2342
+ get lastEventId() {
2343
+ return this.#lastEventId;
2344
+ }
2345
+ get closed() {
2346
+ return this.#closed;
2347
+ }
2348
+ send(event) {
2349
+ if (event.id !== undefined)
2350
+ this.#lastEventId = event.id;
2351
+ this.#write(frameEvent(event));
2352
+ }
2353
+ comment(text = "") {
2354
+ this.#write(frameComment(text));
2355
+ }
2356
+ close() {
2357
+ if (this.#closed)
2358
+ return;
2359
+ this.#stop();
2360
+ this.#controller?.close();
2361
+ }
2362
+ toResponse(headers = {}) {
2363
+ const merged = new Headers(headers);
2364
+ for (const [name, value] of Object.entries(SSE_HEADERS)) {
2365
+ merged.set(name, value);
2366
+ }
2367
+ return new Response(this.#body, { headers: merged });
2368
+ }
2369
+ #write(text) {
2370
+ if (this.#closed)
2371
+ return;
2372
+ this.#controller?.enqueue(encoder.encode(text));
2373
+ }
2374
+ #fail(error) {
2375
+ if (this.#closed)
2376
+ return;
2377
+ this.#stop();
2378
+ this.#controller?.error(error);
2379
+ }
2380
+ #backpressure() {
2381
+ const wanted = this.#controller?.desiredSize;
2382
+ if (this.#closed || wanted === undefined || wanted === null || wanted > 0) {
2383
+ return Promise.resolve();
2384
+ }
2385
+ return new Promise((resolve) => {
2386
+ this.#drained = resolve;
2387
+ });
2388
+ }
2389
+ #stop() {
2390
+ this.#closed = true;
2391
+ this.#gone.abort();
2392
+ this.#drained?.();
2393
+ this.#drained = undefined;
2394
+ if (this.#timer !== undefined)
2395
+ clearInterval(this.#timer);
2396
+ this.#timer = undefined;
2397
+ }
2398
+ }
2399
+ Object.defineProperty(SseStream, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "options: SseStreamOptions = {}", optional: true }] });
2400
+
2401
+ // src/sse/decorators.ts
2402
+ var LAST_EVENT_ID = "last-event-id";
2403
+ var respond = (result) => (result instanceof SseStream ? result : SseStream.from(result)).toResponse();
2404
+ var Sse = (path = "/", options) => (value, _context) => {
2405
+ const handler = value;
2406
+ function served(input) {
2407
+ const result = handler.call(this, {
2408
+ ...input,
2409
+ lastEventId: input.req.headers.get(LAST_EVENT_ID) ?? undefined
2410
+ });
2411
+ return result instanceof Promise ? result.then((settled) => respond(settled)) : respond(result);
2412
+ }
2413
+ markRoute(served, { method: "GET", path, options });
2414
+ meta2(STREAMS2, true)(served);
2415
+ return served;
2416
+ };
2307
2417
  // src/throttle/decorators.ts
2308
2418
  var THROTTLE = metaKey2("throttle");
2309
2419
  var SKIP_THROTTLE = metaKey2("skip-throttle");
@@ -2415,16 +2525,19 @@ class ThrottleGuard {
2415
2525
  store;
2416
2526
  address;
2417
2527
  logger;
2528
+ claimed;
2418
2529
  #warned = false;
2419
- constructor(options, store, address, logger) {
2530
+ constructor(options, store, address, logger, claimed) {
2420
2531
  this.options = options;
2421
2532
  this.store = store;
2422
2533
  this.address = address;
2423
2534
  this.logger = logger;
2535
+ this.claimed = claimed;
2424
2536
  }
2425
2537
  async handle(req, ctx, next) {
2426
- if (ctx.get(UNMATCHED2) === true)
2538
+ if (ctx.get(UNMATCHED2) === true && !this.claimed.has(ctx.path)) {
2427
2539
  return next();
2540
+ }
2428
2541
  if (ctx.get(SKIP_THROTTLE) === true)
2429
2542
  return next();
2430
2543
  const limit = ctx.get(THROTTLE) ?? this.options;
@@ -2452,7 +2565,8 @@ class ThrottleGuard {
2452
2565
  }
2453
2566
  #key(req, ctx) {
2454
2567
  const subject = (this.options.subject ?? ((request) => this.address.of(request)))(req, ctx) ?? "anonymous";
2455
- return `${this.options.prefix}:throttle:${ctx.controller}:${ctx.handler}:${subject}`;
2568
+ const scope = ctx.get(UNMATCHED2) === true ? ctx.path : `${ctx.controller}:${ctx.handler}`;
2569
+ return `${this.options.prefix}:throttle:${scope}:${subject}`;
2456
2570
  }
2457
2571
  async#hit(key, windowSeconds) {
2458
2572
  try {
@@ -2477,7 +2591,7 @@ class ThrottleGuard {
2477
2591
  this.logger.warn("The rate limiter is unreachable, so requests are not being counted.", { reason: error.message });
2478
2592
  }
2479
2593
  }
2480
- Object.defineProperty(ThrottleGuard, Symbol.for("dunx.deps"), { value: () => [ThrottleOptions, ThrottleStore, ClientAddress, Logger5] });
2594
+ Object.defineProperty(ThrottleGuard, Symbol.for("dunx.deps"), { value: () => [ThrottleOptions, ThrottleStore, ClientAddress, Logger5, ClaimedRoutes] });
2481
2595
  // src/throttle/module.ts
2482
2596
  import {
2483
2597
  Logger as Logger6,
@@ -2486,8 +2600,14 @@ import {
2486
2600
  } from "@dunx/core";
2487
2601
  var EXPORTS = [ThrottleOptions, ThrottleStore, ThrottleGuard];
2488
2602
  var guard = () => provide4(ThrottleGuard, {
2489
- useFactory: (options, store, address, logger) => new ThrottleGuard(options, store, address, logger),
2490
- inject: [ThrottleOptions, ThrottleStore, ClientAddress, Logger6]
2603
+ useFactory: (options, store, address, logger, claimed) => new ThrottleGuard(options, store, address, logger, claimed),
2604
+ inject: [
2605
+ ThrottleOptions,
2606
+ ThrottleStore,
2607
+ ClientAddress,
2608
+ Logger6,
2609
+ ClaimedRoutes
2610
+ ]
2491
2611
  });
2492
2612
  var store = () => provide4(ThrottleStore, {
2493
2613
  useFactory: (options) => options.store ?? new MemoryThrottleStore,
@@ -3259,11 +3379,14 @@ export {
3259
3379
  RequestMetrics,
3260
3380
  Roles2 as Roles,
3261
3381
  SKIP_THROTTLE,
3382
+ STREAMS2 as STREAMS,
3262
3383
  SkipThrottle,
3263
3384
  SocketLoggingMiddleware,
3264
- StaticFiles,
3385
+ Sse,
3386
+ SseStream,
3387
+ StaticFiles2 as StaticFiles,
3265
3388
  StaticModule,
3266
- StaticOptions,
3389
+ StaticOptions2 as StaticOptions,
3267
3390
  THROTTLE,
3268
3391
  TRACEPARENT_HEADER2 as TRACEPARENT_HEADER,
3269
3392
  TRACERESPONSE_HEADER2 as TRACERESPONSE_HEADER,