@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.
- package/README.md +9 -0
- package/dist/chunk-08k9vq31.js +94 -0
- package/dist/{chunk-p9hdmkm6.js → chunk-1jt27yka.js} +8 -83
- package/dist/chunk-3eecdh6d.js +160 -0
- package/dist/chunk-cx4btdwe.js +56 -0
- package/dist/client/service.d.ts +13 -10
- package/dist/client/sse.d.ts +22 -8
- package/dist/client.d.ts +2 -0
- package/dist/client.js +72 -20
- package/dist/connect/middleware.d.ts +26 -0
- package/dist/connect/module.d.ts +29 -0
- package/dist/connect/options.d.ts +63 -0
- package/dist/connect/registry.d.ts +47 -0
- package/dist/connect.d.ts +12 -0
- package/dist/connect.js +213 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +312 -189
- package/dist/internal.d.ts +8 -10
- package/dist/internal.js +7 -3
- package/dist/route/claims.d.ts +11 -0
- package/dist/route/metadata.d.ts +11 -0
- package/dist/route/prefix.d.ts +6 -0
- package/dist/server/application.d.ts +1 -1
- package/dist/server/binding.d.ts +4 -2
- package/dist/server/claimed-routes.d.ts +15 -0
- package/dist/server/cors.d.ts +2 -2
- package/dist/server/metrics.d.ts +2 -0
- package/dist/server/middleware.d.ts +19 -2
- package/dist/server/options-provider.d.ts +3 -0
- package/dist/server/options.d.ts +8 -0
- package/dist/server/routes.d.ts +7 -2
- package/dist/server/trace-context.d.ts +9 -11
- package/dist/sse/decorators.d.ts +28 -0
- package/dist/sse/event.d.ts +20 -0
- package/dist/sse/stream.d.ts +38 -0
- package/dist/static/files.d.ts +7 -0
- package/dist/static/options.d.ts +2 -2
- package/dist/throttle/guard.d.ts +3 -1
- package/package.json +19 -2
- 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
|
-
|
|
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-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1321
|
+
const claims = new PathClaims("Route", "Bun would keep only one of them.");
|
|
1355
1322
|
for (const route of discovered) {
|
|
1356
|
-
|
|
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
|
|
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
|
|
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] =
|
|
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 = [
|
|
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(
|
|
2005
|
-
useFactory: (options) => new
|
|
2006
|
-
inject: [
|
|
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: [
|
|
1961
|
+
exports: [StaticOptions2, StaticFiles2],
|
|
2018
1962
|
providers: [
|
|
2019
|
-
provide2(
|
|
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: [
|
|
1972
|
+
exports: [StaticOptions2, StaticFiles2],
|
|
2029
1973
|
providers: [
|
|
2030
|
-
provide2(
|
|
2031
|
-
useFactory: async (...deps) => new
|
|
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
|
-
|
|
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: [
|
|
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
|
-
|
|
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,
|