@dunx/http 3.6.0 → 3.7.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
@@ -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
40
  } 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
- };
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)";
@@ -922,6 +878,10 @@ import {
922
878
  teardownFailures as toFailures
923
879
  } from "@dunx/core";
924
880
 
881
+ // src/server/middleware.ts
882
+ var compose = (middleware, ctx, handler) => middleware.reduceRight((next, current) => (req) => current.handle(req, ctx, () => next(req)), handler);
883
+ var hasClaimedPaths = (value) => typeof value.claimedPaths === "function";
884
+
925
885
  // src/server/request-logging.ts
926
886
  import {
927
887
  Logger as Logger2,
@@ -1213,7 +1173,7 @@ var applyCors = (options, req, response) => {
1213
1173
  return response;
1214
1174
  };
1215
1175
  var withCors = (options, handler) => {
1216
- return async (req) => applyCors(options, req, await handler(req));
1176
+ return async (req, server) => applyCors(options, req, await handler(req, server));
1217
1177
  };
1218
1178
  var preflight = (options, methods) => {
1219
1179
  const allowMethods = (options.methods ?? methods).join(", ");
@@ -1336,9 +1296,6 @@ var buildInputReader = (options) => {
1336
1296
  return (req) => fill({ req });
1337
1297
  };
1338
1298
 
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
1299
  // src/server/routes.ts
1343
1300
  var construct = (guard) => new guard;
1344
1301
  var toResponse = (value, status) => {
@@ -1351,15 +1308,9 @@ var toResponse = (value, status) => {
1351
1308
  };
1352
1309
  var statusFor = (route) => route.options?.status ?? defaultStatusFor2(route.method);
1353
1310
  var assertNoCollisions = (discovered) => {
1354
- const owners = new Map;
1311
+ const claims = new PathClaims("Route", "Bun would keep only one of them.");
1355
1312
  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);
1313
+ claims.claim(`${route.method} ${route.path}`, `${route.controller}.${route.handlerName}`);
1363
1314
  }
1364
1315
  };
1365
1316
  var assertNoGatewayCollisions = (discovered, gatewayPaths) => {
@@ -1376,7 +1327,7 @@ var withUpgradeRoutes = (routes, gateways) => {
1376
1327
  merged[path] = { GET: upgrade };
1377
1328
  return merged;
1378
1329
  };
1379
- var unmatchedContext = (req, isPublic) => Object.freeze({
1330
+ var unmatchedContext = (req, isPublic, server) => Object.freeze({
1380
1331
  controller: "(unmatched)",
1381
1332
  handler: "(none)",
1382
1333
  method: req.method,
@@ -1387,16 +1338,37 @@ var unmatchedContext = (req, isPublic) => Object.freeze({
1387
1338
  return true;
1388
1339
  if (key.id === PUBLIC2.id && isPublic)
1389
1340
  return true;
1341
+ if (key.id === REQUEST_SERVER.id)
1342
+ return server;
1390
1343
  return;
1391
1344
  }
1392
1345
  });
1346
+ var assertNoShadowedClaims = (discovered, middleware) => {
1347
+ const routed = new Map;
1348
+ for (const route of discovered) {
1349
+ if (!routed.has(route.path)) {
1350
+ routed.set(route.path, `${route.controller}.${route.handlerName}`);
1351
+ }
1352
+ }
1353
+ for (const entry of middleware) {
1354
+ if (!hasClaimedPaths(entry))
1355
+ continue;
1356
+ const owner = entry.constructor.name;
1357
+ for (const path of entry.claimedPaths()) {
1358
+ const taken = routed.get(path);
1359
+ if (taken !== undefined) {
1360
+ throw new AppError6(`Path collision: ${path} is declared by ${taken} and claimed by ` + `${owner}. A route wins, so the middleware would never see it.`);
1361
+ }
1362
+ }
1363
+ }
1364
+ };
1393
1365
  var buildFallback = (middleware = [], onError = defaultErrorMapper, cors, notFound = "guarded") => {
1394
1366
  const miss = () => {
1395
1367
  throw new HttpError(HttpStatusCode2.NOT_FOUND, "NOT_FOUND");
1396
1368
  };
1397
- const run = async (req) => {
1369
+ const run = async (req, server) => {
1398
1370
  try {
1399
- return await compose(middleware, unmatchedContext(req, notFound === "public"), miss)(req);
1371
+ return await compose(middleware, unmatchedContext(req, notFound === "public", server), miss)(req);
1400
1372
  } catch (error) {
1401
1373
  return TraceContext2.stamp(onError(error, req), req);
1402
1374
  }
@@ -1450,7 +1422,8 @@ var buildRoutes = (discovered, middleware = [], onError = defaultErrorMapper, co
1450
1422
  ...(route.moduleMiddleware ?? []).map((entry) => guardOf(entry, route.module)),
1451
1423
  ...(route.guards ?? []).map((guard) => guardOf(guard, route.module))
1452
1424
  ];
1453
- const chained = compose(chain, buildContext2(route), async (req) => toResponse(await route.handler(await read(req)), status));
1425
+ const context = buildContext2(route);
1426
+ const chained = compose(chain, context, async (req) => toResponse(await route.handler(await read(req)), status));
1454
1427
  const guarded = async (req) => {
1455
1428
  try {
1456
1429
  return await chained(req);
@@ -1458,8 +1431,12 @@ var buildRoutes = (discovered, middleware = [], onError = defaultErrorMapper, co
1458
1431
  return TraceContext2.stamp(onError(error, req), req);
1459
1432
  }
1460
1433
  };
1434
+ const served = cors ? withCors(cors, guarded) : directOr(guarded, route, read, status, onError, chain.length === 0);
1461
1435
  const byMethod = routes[route.path] ??= {};
1462
- byMethod[route.method] = cors ? withCors(cors, guarded) : directOr(guarded, route, read, status, onError, chain.length === 0);
1436
+ byMethod[route.method] = context.get(STREAMS2) === true ? (req, server) => {
1437
+ server?.timeout(req, 0);
1438
+ return served(req, server);
1439
+ } : served;
1463
1440
  }
1464
1441
  if (cors) {
1465
1442
  for (const byMethod of Object.values(routes)) {
@@ -1488,7 +1465,7 @@ class ServerBinding {
1488
1465
  }
1489
1466
  bind(plan) {
1490
1467
  const { websocket: ws } = plan;
1491
- const { http2, http1, gatewayPort } = this.#protocols;
1468
+ const { http2, http1, gatewayPort, idleTimeout } = this.#protocols;
1492
1469
  const split = ws !== undefined && gatewayPort !== undefined;
1493
1470
  const table = ws && !split ? {
1494
1471
  routes: withUpgradeRoutes(plan.routes, ws.routes),
@@ -1499,6 +1476,7 @@ class ServerBinding {
1499
1476
  fetch: plan.fetch,
1500
1477
  ...http2 !== undefined && { http2 },
1501
1478
  ...http1 !== undefined && { http1 },
1479
+ ...idleTimeout !== undefined && { idleTimeout },
1502
1480
  ...table
1503
1481
  });
1504
1482
  if (split) {
@@ -1506,6 +1484,7 @@ class ServerBinding {
1506
1484
  this.#gateways = Bun.serve({
1507
1485
  port: gatewayPort,
1508
1486
  fetch: plan.fetch,
1487
+ ...idleTimeout !== undefined && { idleTimeout },
1509
1488
  routes: withUpgradeRoutes({}, ws.routes),
1510
1489
  websocket: ws.websocket
1511
1490
  });
@@ -1598,7 +1577,8 @@ class HttpApplication extends ShutdownAware {
1598
1577
  this.#binding = new ServerBinding({
1599
1578
  http2: options.http2,
1600
1579
  http1: options.http1,
1601
- gatewayPort: options.gatewayPort
1580
+ gatewayPort: options.gatewayPort,
1581
+ idleTimeout: options.idleTimeout
1602
1582
  });
1603
1583
  this.#split = options.gatewayPort !== undefined && websocket !== undefined;
1604
1584
  this.gatewayPaths = websocket?.paths ?? [];
@@ -1664,6 +1644,8 @@ class HttpApplication extends ShutdownAware {
1664
1644
  const ws = this.#websocket;
1665
1645
  if (ws && !this.#split)
1666
1646
  assertNoGatewayCollisions(prefixed, ws.paths);
1647
+ assertNoShadowedClaims(prefixed, middleware);
1648
+ this.#app.get(ClaimedRoutes).attach(middleware.flatMap((entry) => hasClaimedPaths(entry) ? [...entry.claimedPaths()] : []));
1667
1649
  const fetch = buildFallback(middleware, this.#onError, this.#cors, this.#notFound);
1668
1650
  const bound = this.#binding.bind({ port, routes, fetch, websocket: ws });
1669
1651
  attachAddressSource(this.#app.get(ClientAddress), {
@@ -1805,6 +1787,9 @@ class HttpOptionsProvider {
1805
1787
  get gatewayPort() {
1806
1788
  return;
1807
1789
  }
1790
+ get idleTimeout() {
1791
+ return;
1792
+ }
1808
1793
  }
1809
1794
 
1810
1795
  class DefaultHttpOptions extends HttpOptionsProvider {
@@ -1831,7 +1816,8 @@ function resolveHttpOptions(settings, given) {
1831
1816
  relayResubscribe: settings.relayResubscribe,
1832
1817
  http2: settings.http2,
1833
1818
  http1: settings.http1,
1834
- gatewayPort: settings.gatewayPort
1819
+ gatewayPort: settings.gatewayPort,
1820
+ idleTimeout: settings.idleTimeout
1835
1821
  };
1836
1822
  for (const key of Object.keys(given)) {
1837
1823
  merged[key] = given[key];
@@ -1866,7 +1852,13 @@ class HttpFactory {
1866
1852
  useFactory: (logger, context, settings) => new SocketLoggingMiddleware(logger, context, pick(options.socketLogging, settings.socketLogging)),
1867
1853
  inject: [Logger4, RequestContext3, HttpOptionsProvider]
1868
1854
  });
1869
- const services = [PubSub, ClientAddress, RequestMetrics, RoutePrefix2];
1855
+ const services = [
1856
+ PubSub,
1857
+ ClientAddress,
1858
+ RequestMetrics,
1859
+ RoutePrefix2,
1860
+ ClaimedRoutes
1861
+ ];
1870
1862
  const providers = [...services, logging, metricsMiddleware, socketLogging];
1871
1863
  const scope = {
1872
1864
  module: HttpModule,
@@ -1921,89 +1913,14 @@ class HttpFactory {
1921
1913
  return entries.map((entry) => app.get(entry, root));
1922
1914
  }
1923
1915
  }
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
1916
  // src/static/module.ts
2000
1917
  import {
2001
1918
  Module,
2002
1919
  provide as provide2
2003
1920
  } from "@dunx/core";
2004
- var files = () => provide2(StaticFiles, {
2005
- useFactory: (options) => new StaticFiles(options),
2006
- inject: [StaticOptions]
1921
+ var files = () => provide2(StaticFiles2, {
1922
+ useFactory: (options) => new StaticFiles2(options),
1923
+ inject: [StaticOptions2]
2007
1924
  });
2008
1925
  var _dec = [
2009
1926
  Module({})
@@ -2014,9 +1931,9 @@ class StaticModule {
2014
1931
  static forRoot(init) {
2015
1932
  return {
2016
1933
  module: StaticModule,
2017
- exports: [StaticOptions, StaticFiles],
1934
+ exports: [StaticOptions2, StaticFiles2],
2018
1935
  providers: [
2019
- provide2(StaticOptions, { useValue: new StaticOptions(init) }),
1936
+ provide2(StaticOptions2, { useValue: new StaticOptions2(init) }),
2020
1937
  files()
2021
1938
  ]
2022
1939
  };
@@ -2025,10 +1942,10 @@ class StaticModule {
2025
1942
  return {
2026
1943
  module: StaticModule,
2027
1944
  ...config.imports && { imports: config.imports },
2028
- exports: [StaticOptions, StaticFiles],
1945
+ exports: [StaticOptions2, StaticFiles2],
2029
1946
  providers: [
2030
- provide2(StaticOptions, {
2031
- useFactory: async (...deps) => new StaticOptions(await config.useFactory(...deps)),
1947
+ provide2(StaticOptions2, {
1948
+ useFactory: async (...deps) => new StaticOptions2(await config.useFactory(...deps)),
2032
1949
  inject: config.inject ?? []
2033
1950
  }),
2034
1951
  files()
@@ -2092,10 +2009,13 @@ var COMPRESSIBLE = new Set([
2092
2009
  "application/xml",
2093
2010
  "image/svg+xml"
2094
2011
  ]);
2012
+ var ENDLESS = new Set(["text/event-stream"]);
2095
2013
  var isCompressibleType = (contentType) => {
2096
2014
  if (contentType === null)
2097
2015
  return false;
2098
2016
  const type = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
2017
+ if (ENDLESS.has(type))
2018
+ return false;
2099
2019
  if (type.startsWith("text/"))
2100
2020
  return true;
2101
2021
  if (type.endsWith("+json") || type.endsWith("+xml"))
@@ -2304,6 +2224,143 @@ CompressionModule = __decorateElement(_init, 0, "CompressionModule", _dec, Compr
2304
2224
  __runInitializers(_init, 1, CompressionModule);
2305
2225
  __decoratorMetadata(_init, CompressionModule);
2306
2226
  let _CompressionModule = CompressionModule;
2227
+ // src/sse/event.ts
2228
+ var lines = (value) => value.split(/\r\n|\r|\n/);
2229
+ var oneLine = (value) => lines(value)[0] ?? "";
2230
+ var frameEvent = (event) => {
2231
+ const fields = [];
2232
+ if (event.event !== undefined)
2233
+ fields.push(`event: ${oneLine(event.event)}`);
2234
+ if (event.id !== undefined)
2235
+ fields.push(`id: ${oneLine(event.id)}`);
2236
+ if (event.retry !== undefined) {
2237
+ fields.push(`retry: ${Math.trunc(event.retry)}`);
2238
+ }
2239
+ if (event.data !== undefined) {
2240
+ const data = typeof event.data === "string" ? event.data : JSON.stringify(event.data);
2241
+ for (const line of lines(data ?? ""))
2242
+ fields.push(`data: ${line}`);
2243
+ }
2244
+ return `${fields.join(`
2245
+ `)}
2246
+
2247
+ `;
2248
+ };
2249
+ var frameComment = (text) => `${lines(text).map((line) => `:${line}`).join(`
2250
+ `)}
2251
+
2252
+ `;
2253
+
2254
+ // src/sse/stream.ts
2255
+ var DEFAULT_HEARTBEAT_MS = 15000;
2256
+ var encoder = new TextEncoder;
2257
+ var SSE_HEADERS = Object.freeze({
2258
+ "content-type": "text/event-stream",
2259
+ "cache-control": "no-cache, no-transform",
2260
+ connection: "keep-alive"
2261
+ });
2262
+
2263
+ class SseStream {
2264
+ #body;
2265
+ #controller;
2266
+ #timer;
2267
+ #lastEventId;
2268
+ #closed = false;
2269
+ constructor(options = {}) {
2270
+ this.#body = new ReadableStream({
2271
+ start: (controller) => {
2272
+ this.#controller = controller;
2273
+ },
2274
+ cancel: () => {
2275
+ this.#stop();
2276
+ }
2277
+ });
2278
+ this.comment();
2279
+ const heartbeatMs = options.heartbeatMs ?? DEFAULT_HEARTBEAT_MS;
2280
+ if (heartbeatMs > 0) {
2281
+ this.#timer = setInterval(() => {
2282
+ this.comment();
2283
+ }, heartbeatMs);
2284
+ this.#timer.unref();
2285
+ }
2286
+ }
2287
+ static from(events, options = {}) {
2288
+ const stream = new SseStream(options);
2289
+ (async () => {
2290
+ try {
2291
+ for await (const event of events) {
2292
+ if (stream.#closed)
2293
+ break;
2294
+ stream.send(event);
2295
+ }
2296
+ stream.close();
2297
+ } catch (error) {
2298
+ stream.#fail(error);
2299
+ }
2300
+ })();
2301
+ return stream;
2302
+ }
2303
+ get lastEventId() {
2304
+ return this.#lastEventId;
2305
+ }
2306
+ get closed() {
2307
+ return this.#closed;
2308
+ }
2309
+ send(event) {
2310
+ if (event.id !== undefined)
2311
+ this.#lastEventId = event.id;
2312
+ this.#write(frameEvent(event));
2313
+ }
2314
+ comment(text = "") {
2315
+ this.#write(frameComment(text));
2316
+ }
2317
+ close() {
2318
+ if (this.#closed)
2319
+ return;
2320
+ this.#stop();
2321
+ this.#controller?.close();
2322
+ }
2323
+ toResponse(headers = {}) {
2324
+ return new Response(this.#body, {
2325
+ headers: { ...headers, ...SSE_HEADERS }
2326
+ });
2327
+ }
2328
+ #write(text) {
2329
+ if (this.#closed)
2330
+ return;
2331
+ this.#controller?.enqueue(encoder.encode(text));
2332
+ }
2333
+ #fail(error) {
2334
+ if (this.#closed)
2335
+ return;
2336
+ this.#stop();
2337
+ this.#controller?.error(error);
2338
+ }
2339
+ #stop() {
2340
+ this.#closed = true;
2341
+ if (this.#timer !== undefined)
2342
+ clearInterval(this.#timer);
2343
+ this.#timer = undefined;
2344
+ }
2345
+ }
2346
+ Object.defineProperty(SseStream, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "options: SseStreamOptions = {}", optional: true }] });
2347
+
2348
+ // src/sse/decorators.ts
2349
+ var LAST_EVENT_ID = "last-event-id";
2350
+ var respond = (result) => (result instanceof SseStream ? result : SseStream.from(result)).toResponse();
2351
+ var Sse = (path = "/", options) => (value, _context) => {
2352
+ const handler = value;
2353
+ function served(input) {
2354
+ const result = handler.call(this, {
2355
+ ...input,
2356
+ lastEventId: input.req.headers.get(LAST_EVENT_ID) ?? undefined
2357
+ });
2358
+ return result instanceof Promise ? result.then((settled) => respond(settled)) : respond(result);
2359
+ }
2360
+ markRoute(served, { method: "GET", path, options });
2361
+ meta2(STREAMS2, true)(served);
2362
+ return served;
2363
+ };
2307
2364
  // src/throttle/decorators.ts
2308
2365
  var THROTTLE = metaKey2("throttle");
2309
2366
  var SKIP_THROTTLE = metaKey2("skip-throttle");
@@ -2415,16 +2472,19 @@ class ThrottleGuard {
2415
2472
  store;
2416
2473
  address;
2417
2474
  logger;
2475
+ claimed;
2418
2476
  #warned = false;
2419
- constructor(options, store, address, logger) {
2477
+ constructor(options, store, address, logger, claimed) {
2420
2478
  this.options = options;
2421
2479
  this.store = store;
2422
2480
  this.address = address;
2423
2481
  this.logger = logger;
2482
+ this.claimed = claimed;
2424
2483
  }
2425
2484
  async handle(req, ctx, next) {
2426
- if (ctx.get(UNMATCHED2) === true)
2485
+ if (ctx.get(UNMATCHED2) === true && !this.claimed.has(ctx.path)) {
2427
2486
  return next();
2487
+ }
2428
2488
  if (ctx.get(SKIP_THROTTLE) === true)
2429
2489
  return next();
2430
2490
  const limit = ctx.get(THROTTLE) ?? this.options;
@@ -2477,7 +2537,7 @@ class ThrottleGuard {
2477
2537
  this.logger.warn("The rate limiter is unreachable, so requests are not being counted.", { reason: error.message });
2478
2538
  }
2479
2539
  }
2480
- Object.defineProperty(ThrottleGuard, Symbol.for("dunx.deps"), { value: () => [ThrottleOptions, ThrottleStore, ClientAddress, Logger5] });
2540
+ Object.defineProperty(ThrottleGuard, Symbol.for("dunx.deps"), { value: () => [ThrottleOptions, ThrottleStore, ClientAddress, Logger5, ClaimedRoutes] });
2481
2541
  // src/throttle/module.ts
2482
2542
  import {
2483
2543
  Logger as Logger6,
@@ -2486,8 +2546,14 @@ import {
2486
2546
  } from "@dunx/core";
2487
2547
  var EXPORTS = [ThrottleOptions, ThrottleStore, ThrottleGuard];
2488
2548
  var guard = () => provide4(ThrottleGuard, {
2489
- useFactory: (options, store, address, logger) => new ThrottleGuard(options, store, address, logger),
2490
- inject: [ThrottleOptions, ThrottleStore, ClientAddress, Logger6]
2549
+ useFactory: (options, store, address, logger, claimed) => new ThrottleGuard(options, store, address, logger, claimed),
2550
+ inject: [
2551
+ ThrottleOptions,
2552
+ ThrottleStore,
2553
+ ClientAddress,
2554
+ Logger6,
2555
+ ClaimedRoutes
2556
+ ]
2491
2557
  });
2492
2558
  var store = () => provide4(ThrottleStore, {
2493
2559
  useFactory: (options) => options.store ?? new MemoryThrottleStore,
@@ -3259,11 +3325,14 @@ export {
3259
3325
  RequestMetrics,
3260
3326
  Roles2 as Roles,
3261
3327
  SKIP_THROTTLE,
3328
+ STREAMS2 as STREAMS,
3262
3329
  SkipThrottle,
3263
3330
  SocketLoggingMiddleware,
3264
- StaticFiles,
3331
+ Sse,
3332
+ SseStream,
3333
+ StaticFiles2 as StaticFiles,
3265
3334
  StaticModule,
3266
- StaticOptions,
3335
+ StaticOptions2 as StaticOptions,
3267
3336
  THROTTLE,
3268
3337
  TRACEPARENT_HEADER2 as TRACEPARENT_HEADER,
3269
3338
  TRACERESPONSE_HEADER2 as TRACERESPONSE_HEADER,
@@ -1,20 +1,18 @@
1
1
  /**
2
- * What the framework calls on itself, and the only place it is exported from.
2
+ * What the framework calls on itself, and the only place it is exported from: the
3
+ * discovery readers and route metadata `@dunx/dashboard`, `@dunx/mcp`,
4
+ * `@dunx/openapi` and `@dunx/testing` import. The barrel stays the surface an app
5
+ * writes against.
3
6
  *
4
- * The barrel was a semver promise 173 symbols wide, which is more than this
5
- * package meant to make. What stays public there is the surface an app writes
6
- * against. What is here is the discovery readers and route metadata that
7
- * `@dunx/dashboard`, `@dunx/mcp`, `@dunx/openapi` and `@dunx/testing` import.
8
- *
9
- * It held 62 symbols and 50 had no importer, all of them reachable from inside
10
- * this package by relative import. Add one back when a sibling needs it.
11
- *
12
- * No stability promise attaches to this subpath.
7
+ * It held 62 symbols and 50 had no importer, all reachable from inside this
8
+ * package by relative import. Add one back when a sibling needs it. No stability
9
+ * promise attaches here.
13
10
  */
14
11
  export { discoverRoutes, joinPath, type DiscoveredRoute, } from './route/discover.js';
15
12
  export { RoutePrefix } from './route/prefix.js';
16
13
  export { defaultStatusFor } from './route/marker.js';
17
14
  export { gatewaysOf, routesOf, type GatewayHandler, type GatewayNode, type RouteInputs, type RouteNode, } from './inspect.js';
18
15
  export { buildContext } from './server/context.js';
16
+ export { IMMUTABLE_CACHE_CONTROL } from './static/files.js';
19
17
  export { embedJson } from './server/html.js';
20
18
  export { isGateway } from './ws/marker.js';