@dunx/http 2.4.0 → 3.0.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 +31 -909
- package/dist/chunk-25g22350.js +58 -0
- package/dist/chunk-ywdpxbkf.js +1573 -0
- package/dist/client/module.d.ts +2 -7
- package/dist/client/options.d.ts +12 -0
- package/dist/client/service.d.ts +11 -22
- package/dist/client.d.ts +8 -2
- package/dist/client.js +26 -137
- package/dist/compression/compression.d.ts +25 -0
- package/dist/compression/module.d.ts +20 -0
- package/dist/compression/negotiate.d.ts +12 -0
- package/dist/compression/options.d.ts +61 -0
- package/dist/health/indicators.d.ts +5 -10
- package/dist/index.d.ts +24 -20
- package/dist/index.js +325 -1366
- package/dist/internal.d.ts +36 -0
- package/dist/internal.js +89 -0
- package/dist/route/decorators.d.ts +6 -6
- package/dist/route/marker.d.ts +13 -0
- package/dist/route/metadata.d.ts +6 -9
- package/dist/route/schema.d.ts +59 -29
- package/dist/server/application.d.ts +33 -89
- package/dist/server/client-address.d.ts +6 -13
- package/dist/server/errors.d.ts +24 -55
- package/dist/server/request-id.d.ts +6 -11
- package/dist/server/request-logging.d.ts +33 -91
- package/dist/server/routes.d.ts +8 -20
- package/dist/server/trace-context.d.ts +47 -0
- package/dist/static/files.d.ts +5 -13
- package/dist/static/module.d.ts +10 -19
- package/dist/throttle/guard.d.ts +6 -9
- package/dist/throttle/module.d.ts +6 -8
- package/dist/throttle/store.d.ts +11 -20
- package/dist/ws/middleware.d.ts +12 -21
- package/dist/ws/redis-relay.d.ts +9 -19
- package/package.json +7 -3
- package/dist/chunk-sz4pvqxy.js +0 -111
- package/dist/chunk-sz4pvqxy.js.map +0 -10
- package/dist/client.js.map +0 -15
- package/dist/index.js.map +0 -53
package/dist/index.js
CHANGED
|
@@ -1,249 +1,92 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
+
TRACEPARENT_HEADER,
|
|
4
|
+
TRACESTATE_HEADER,
|
|
5
|
+
TraceContext
|
|
6
|
+
} from "./chunk-25g22350.js";
|
|
7
|
+
import {
|
|
8
|
+
ApiHidden,
|
|
9
|
+
CompressionEncoding,
|
|
10
|
+
CompressionOptions,
|
|
11
|
+
Controller,
|
|
12
|
+
DEFAULT_RELAY_CHANNEL,
|
|
13
|
+
Delete,
|
|
14
|
+
ErrorFilter,
|
|
15
|
+
Get,
|
|
16
|
+
HEALTH_REPORT_SCHEMA,
|
|
17
|
+
HIDDEN,
|
|
18
|
+
HandlerKind,
|
|
19
|
+
HealthController,
|
|
20
|
+
HealthOptions,
|
|
21
|
+
HealthRegistry,
|
|
22
|
+
HiddenHealthController,
|
|
23
|
+
HttpError,
|
|
3
24
|
HttpStatusCode,
|
|
25
|
+
PUBLIC,
|
|
26
|
+
Patch,
|
|
27
|
+
Post,
|
|
28
|
+
Public,
|
|
29
|
+
Put,
|
|
30
|
+
REQUEST_ID_HEADER,
|
|
31
|
+
ROLES,
|
|
32
|
+
RawBody,
|
|
33
|
+
RedisRelay,
|
|
34
|
+
RequestIds,
|
|
35
|
+
Roles,
|
|
36
|
+
StaticOptions,
|
|
37
|
+
UNMATCHED,
|
|
38
|
+
UseGuards,
|
|
39
|
+
ValidationError,
|
|
4
40
|
__decorateElement,
|
|
5
41
|
__decoratorMetadata,
|
|
6
42
|
__decoratorStart,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
var write = (target, key, value) => {
|
|
53
|
-
const record = new Map(target[META]);
|
|
54
|
-
record.set(key.id, value);
|
|
55
|
-
Object.defineProperty(target, META, { value: record, configurable: true });
|
|
56
|
-
};
|
|
57
|
-
var meta = (key, value) => (target) => {
|
|
58
|
-
write(target, key, value);
|
|
59
|
-
return target;
|
|
60
|
-
};
|
|
61
|
-
var ROLES = metaKey("roles");
|
|
62
|
-
var PUBLIC = metaKey("public");
|
|
63
|
-
var HIDDEN = metaKey("hidden");
|
|
64
|
-
var UNMATCHED = metaKey("unmatched");
|
|
65
|
-
var Roles = (...roles) => meta(ROLES, roles);
|
|
66
|
-
var Public = () => meta(PUBLIC, true);
|
|
67
|
-
var ApiHidden = () => meta(HIDDEN, true);
|
|
68
|
-
var UseGuards = (...guards) => (target) => {
|
|
69
|
-
const existing = target[GUARDS] ?? [];
|
|
70
|
-
const merged = Object.hasOwn(target, GUARDS) ? [...guards, ...existing] : [...existing, ...guards];
|
|
71
|
-
Object.defineProperty(target, GUARDS, {
|
|
72
|
-
value: merged,
|
|
73
|
-
configurable: true
|
|
74
|
-
});
|
|
75
|
-
return target;
|
|
76
|
-
};
|
|
77
|
-
var guardsOf = (target) => target[GUARDS] ?? [];
|
|
78
|
-
var metaOf = (target) => target[META];
|
|
79
|
-
var mergeMeta = (...targets) => {
|
|
80
|
-
const merged = new Map;
|
|
81
|
-
for (const target of targets) {
|
|
82
|
-
const record = target[META];
|
|
83
|
-
if (record)
|
|
84
|
-
for (const [id, value] of record)
|
|
85
|
-
merged.set(id, value);
|
|
86
|
-
}
|
|
87
|
-
return merged;
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
// src/route/discover.ts
|
|
91
|
-
var joinPath = (prefix, path) => {
|
|
92
|
-
const joined = `/${prefix}/${path}`.replace(/\/{2,}/g, "/");
|
|
93
|
-
return joined.length > 1 ? joined.replace(/\/$/, "") : "/";
|
|
94
|
-
};
|
|
95
|
-
var discoverRoutes = (instance) => {
|
|
96
|
-
const klass = instance.constructor;
|
|
97
|
-
const prefix = prefixOf(klass);
|
|
98
|
-
const classGuards = guardsOf(klass);
|
|
99
|
-
const members = instance;
|
|
100
|
-
return markedMethods(Object.getPrototypeOf(instance), routeMetaOf).map(({ name, meta: meta2, value: marked }) => ({
|
|
101
|
-
method: meta2.method,
|
|
102
|
-
path: joinPath(prefix, resolvePath(meta2.path)),
|
|
103
|
-
controller: klass.name,
|
|
104
|
-
handlerName: name,
|
|
105
|
-
handler: members[name].bind(instance),
|
|
106
|
-
options: meta2.options,
|
|
107
|
-
meta: mergeMeta(klass, marked),
|
|
108
|
-
classMeta: metaOf(klass),
|
|
109
|
-
guards: [...classGuards, ...guardsOf(marked)]
|
|
110
|
-
}));
|
|
111
|
-
};
|
|
112
|
-
// src/inspect.ts
|
|
113
|
-
import {
|
|
114
|
-
collectModules,
|
|
115
|
-
dependenciesOf,
|
|
116
|
-
readControllers
|
|
117
|
-
} from "@dunx/core";
|
|
118
|
-
|
|
119
|
-
// src/ws/discover.ts
|
|
120
|
-
import {
|
|
121
|
-
AppError,
|
|
122
|
-
classOf,
|
|
123
|
-
markedMethods as markedMethods2
|
|
124
|
-
} from "@dunx/core";
|
|
125
|
-
|
|
126
|
-
// src/ws/marker.ts
|
|
127
|
-
var HANDLER = Symbol.for("dunx.ws.handler");
|
|
128
|
-
var GATEWAY = Symbol.for("dunx.ws.gateway");
|
|
129
|
-
var HandlerKind = Object.freeze({
|
|
130
|
-
UPGRADE: "upgrade",
|
|
131
|
-
OPEN: "open",
|
|
132
|
-
MESSAGE: "message",
|
|
133
|
-
CLOSE: "close",
|
|
134
|
-
DRAIN: "drain",
|
|
135
|
-
PING: "ping",
|
|
136
|
-
PONG: "pong"
|
|
137
|
-
});
|
|
138
|
-
var markHandler = (target, meta2) => {
|
|
139
|
-
Object.defineProperty(target, HANDLER, { value: meta2, configurable: true });
|
|
140
|
-
};
|
|
141
|
-
var handlerMetaOf = (value) => typeof value === "function" ? value[HANDLER] : undefined;
|
|
142
|
-
var markGateway = (target, path) => {
|
|
143
|
-
Object.defineProperty(target, GATEWAY, { value: path, configurable: true });
|
|
144
|
-
};
|
|
145
|
-
var gatewayPathOf = (target) => target[GATEWAY] ?? "/";
|
|
146
|
-
var isGateway = (target) => target[GATEWAY] !== undefined;
|
|
147
|
-
|
|
148
|
-
// src/ws/discover.ts
|
|
149
|
-
var normalizePath = (path) => {
|
|
150
|
-
const joined = `/${path}`.replace(/\/{2,}/g, "/");
|
|
151
|
-
return joined.length > 1 ? joined.replace(/\/$/, "") : "/";
|
|
152
|
-
};
|
|
153
|
-
var eachHandler = (start) => markedMethods2(start, handlerMetaOf);
|
|
154
|
-
var discoverGateway = (instance) => {
|
|
155
|
-
const klass = instance.constructor;
|
|
156
|
-
const members = instance;
|
|
157
|
-
return {
|
|
158
|
-
name: klass.name,
|
|
159
|
-
path: normalizePath(gatewayPathOf(klass)),
|
|
160
|
-
handlers: eachHandler(Object.getPrototypeOf(instance)).map(({ name, meta: meta2 }) => ({
|
|
161
|
-
kind: meta2.kind,
|
|
162
|
-
event: meta2.event,
|
|
163
|
-
method: name,
|
|
164
|
-
invoke: members[name].bind(instance)
|
|
165
|
-
}))
|
|
166
|
-
};
|
|
167
|
-
};
|
|
168
|
-
var findHandlerMethod = (ctor) => eachHandler(ctor.prototype)[0]?.name;
|
|
169
|
-
var discoverGateways = (modules, resolve) => {
|
|
170
|
-
const discovered = [];
|
|
171
|
-
for (const module of modules) {
|
|
172
|
-
for (const entry of module.options.providers ?? []) {
|
|
173
|
-
const candidate = classOf(entry);
|
|
174
|
-
if (!candidate)
|
|
175
|
-
continue;
|
|
176
|
-
if (isGateway(candidate.ctor)) {
|
|
177
|
-
discovered.push(discoverGateway(resolve(candidate.token)));
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
const orphan = findHandlerMethod(candidate.ctor);
|
|
181
|
-
if (orphan !== undefined) {
|
|
182
|
-
throw new AppError(`${candidate.ctor.name}.${orphan}() is a websocket handler, but ` + `${candidate.ctor.name} is not a gateway. Decorate the class with ` + "@Gateway(path), or drop the handler decorator.");
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return discovered;
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
// src/inspect.ts
|
|
190
|
-
var vendorOf = (schema) => schema?.["~standard"]?.vendor;
|
|
191
|
-
var validatesIn = (options) => {
|
|
192
|
-
const body = vendorOf(options?.body);
|
|
193
|
-
const query = vendorOf(options?.query);
|
|
194
|
-
const params = vendorOf(options?.params);
|
|
195
|
-
return {
|
|
196
|
-
...body === undefined ? {} : { body },
|
|
197
|
-
...query === undefined ? {} : { query },
|
|
198
|
-
...params === undefined ? {} : { params }
|
|
199
|
-
};
|
|
200
|
-
};
|
|
201
|
-
var rolesIn = (route) => {
|
|
202
|
-
const roles = route.meta?.get(ROLES.id);
|
|
203
|
-
if (roles === undefined || roles === null)
|
|
204
|
-
return null;
|
|
205
|
-
return (Array.isArray(roles) ? roles : [roles]).map(String);
|
|
206
|
-
};
|
|
207
|
-
var nodeFor = (route, module) => ({
|
|
208
|
-
method: route.method,
|
|
209
|
-
path: route.path,
|
|
210
|
-
controller: route.controller,
|
|
211
|
-
handler: route.handlerName,
|
|
212
|
-
module,
|
|
213
|
-
public: route.meta?.get(PUBLIC.id) === true,
|
|
214
|
-
roles: rolesIn(route),
|
|
215
|
-
guards: (route.guards ?? []).map((guard) => guard.name),
|
|
216
|
-
hidden: route.meta?.get(HIDDEN.id) === true,
|
|
217
|
-
validates: validatesIn(route.options),
|
|
218
|
-
status: route.options?.status ?? null,
|
|
219
|
-
responses: Object.keys(route.options?.response ?? {}).map(Number)
|
|
220
|
-
});
|
|
221
|
-
var routesOf = (root) => collectModules(root).flatMap((module) => readControllers(module).flatMap((controller) => {
|
|
222
|
-
const { prototype } = controller;
|
|
223
|
-
return discoverRoutes(Object.create(prototype)).map((route) => nodeFor(route, module.name));
|
|
224
|
-
}));
|
|
225
|
-
var gatewayFor = (ctor, module) => {
|
|
226
|
-
const { name, path, handlers } = discoverGateway(Object.create(ctor.prototype));
|
|
227
|
-
return {
|
|
228
|
-
name,
|
|
229
|
-
path,
|
|
230
|
-
module,
|
|
231
|
-
dependencies: dependenciesOf(ctor),
|
|
232
|
-
handlers: handlers.map((handler) => ({
|
|
233
|
-
kind: handler.kind,
|
|
234
|
-
event: handler.event ?? null,
|
|
235
|
-
method: handler.method
|
|
236
|
-
}))
|
|
237
|
-
};
|
|
238
|
-
};
|
|
239
|
-
var classOf2 = (entry) => {
|
|
240
|
-
if (typeof entry === "function")
|
|
241
|
-
return entry;
|
|
242
|
-
return entry.provider.kind === "class" ? entry.provider.ctor : undefined;
|
|
243
|
-
};
|
|
244
|
-
var gatewaysOf = (root) => collectModules(root).flatMap((module) => (module.options.providers ?? []).map(classOf2).filter((ctor) => ctor !== undefined).filter(isGateway).map((ctor) => gatewayFor(ctor, module.name)));
|
|
43
|
+
__runInitializers,
|
|
44
|
+
assertNoCollisions,
|
|
45
|
+
assertNoGatewayCollisions,
|
|
46
|
+
buildContext,
|
|
47
|
+
buildFallback,
|
|
48
|
+
buildGateways,
|
|
49
|
+
buildRoutes,
|
|
50
|
+
buildRuntime,
|
|
51
|
+
buildWebSocket,
|
|
52
|
+
compose,
|
|
53
|
+
composeSocket,
|
|
54
|
+
decode,
|
|
55
|
+
decodeRelay,
|
|
56
|
+
defaultErrorMapper,
|
|
57
|
+
defaultRelayError,
|
|
58
|
+
defaultRelayUrl,
|
|
59
|
+
defaultStatusFor,
|
|
60
|
+
discoverGateway,
|
|
61
|
+
discoverGateways,
|
|
62
|
+
discoverRoutes,
|
|
63
|
+
encode,
|
|
64
|
+
encodeRelay,
|
|
65
|
+
errorMapper,
|
|
66
|
+
gatewaysOf,
|
|
67
|
+
guardsOf,
|
|
68
|
+
isCompressibleType,
|
|
69
|
+
isErrorFilter,
|
|
70
|
+
isGateway,
|
|
71
|
+
joinPath,
|
|
72
|
+
markGateway,
|
|
73
|
+
markHandler,
|
|
74
|
+
mergeMeta,
|
|
75
|
+
meta,
|
|
76
|
+
metaKey,
|
|
77
|
+
metaOf,
|
|
78
|
+
negotiate,
|
|
79
|
+
normalizePath,
|
|
80
|
+
normalizePrefix,
|
|
81
|
+
observe,
|
|
82
|
+
preflight,
|
|
83
|
+
routesOf,
|
|
84
|
+
toErrorMapper,
|
|
85
|
+
withCors,
|
|
86
|
+
withUpgradeRoutes
|
|
87
|
+
} from "./chunk-ywdpxbkf.js";
|
|
245
88
|
// src/server/client-address.ts
|
|
246
|
-
import { AppError
|
|
89
|
+
import { AppError } from "@dunx/core";
|
|
247
90
|
var trustedHops = (setting) => {
|
|
248
91
|
if (setting === true)
|
|
249
92
|
return 1;
|
|
@@ -257,7 +100,7 @@ class ClientAddress {
|
|
|
257
100
|
of(req) {
|
|
258
101
|
const source = sources.get(this);
|
|
259
102
|
if (!source) {
|
|
260
|
-
throw new
|
|
103
|
+
throw new AppError("ClientAddress has no server yet. The address comes from the live Bun " + "server, so it is only available once listen() has run.");
|
|
261
104
|
}
|
|
262
105
|
const hops = trustedHops(source.trustProxy);
|
|
263
106
|
if (hops > 0) {
|
|
@@ -272,432 +115,17 @@ class ClientAddress {
|
|
|
272
115
|
var attachAddressSource = (target, source) => {
|
|
273
116
|
sources.set(target, source);
|
|
274
117
|
};
|
|
275
|
-
// src/server/context.ts
|
|
276
|
-
var EMPTY = new Map;
|
|
277
|
-
var buildContext = (route) => {
|
|
278
|
-
const record = route.meta ?? EMPTY;
|
|
279
|
-
return Object.freeze({
|
|
280
|
-
controller: route.controller,
|
|
281
|
-
handler: route.handlerName,
|
|
282
|
-
method: route.method,
|
|
283
|
-
path: route.path,
|
|
284
|
-
parsesBody: route.options?.body !== undefined,
|
|
285
|
-
get: (key) => record.get(key.id)
|
|
286
|
-
});
|
|
287
|
-
};
|
|
288
|
-
// src/server/cors.ts
|
|
289
|
-
var ORIGIN = "access-control-allow-origin";
|
|
290
|
-
var allowedOrigin = (options, requested) => {
|
|
291
|
-
const origin = options.origin ?? "*";
|
|
292
|
-
if (typeof origin === "string") {
|
|
293
|
-
if (origin !== "*")
|
|
294
|
-
return origin === requested ? origin : undefined;
|
|
295
|
-
if (!options.credentials)
|
|
296
|
-
return "*";
|
|
297
|
-
return requested ?? undefined;
|
|
298
|
-
}
|
|
299
|
-
if (requested === null)
|
|
300
|
-
return;
|
|
301
|
-
const allowed = typeof origin === "function" ? origin(requested) : origin.includes(requested);
|
|
302
|
-
return allowed ? requested : undefined;
|
|
303
|
-
};
|
|
304
|
-
var applyCors = (options, req, response) => {
|
|
305
|
-
const origin = allowedOrigin(options, req.headers.get("origin"));
|
|
306
|
-
if (origin === undefined)
|
|
307
|
-
return response;
|
|
308
|
-
response.headers.set(ORIGIN, origin);
|
|
309
|
-
if (origin !== "*")
|
|
310
|
-
response.headers.append("vary", "Origin");
|
|
311
|
-
if (options.credentials) {
|
|
312
|
-
response.headers.set("access-control-allow-credentials", "true");
|
|
313
|
-
}
|
|
314
|
-
if (options.exposedHeaders?.length) {
|
|
315
|
-
response.headers.set("access-control-expose-headers", options.exposedHeaders.join(", "));
|
|
316
|
-
}
|
|
317
|
-
return response;
|
|
318
|
-
};
|
|
319
|
-
var withCors = (options, handler) => {
|
|
320
|
-
return async (req) => applyCors(options, req, await handler(req));
|
|
321
|
-
};
|
|
322
|
-
var preflight = (options, methods) => {
|
|
323
|
-
const allowMethods = (options.methods ?? methods).join(", ");
|
|
324
|
-
return async (req) => {
|
|
325
|
-
const response = applyCors(options, req, new Response(null, { status: HttpStatusCode.NO_CONTENT }));
|
|
326
|
-
if (!response.headers.has(ORIGIN))
|
|
327
|
-
return response;
|
|
328
|
-
response.headers.set("access-control-allow-methods", allowMethods);
|
|
329
|
-
const allowHeaders = options.allowedHeaders ?? (req.headers.get("access-control-request-headers") ?? "").split(",").map((header) => header.trim()).filter((header) => header.length > 0);
|
|
330
|
-
if (allowHeaders.length > 0) {
|
|
331
|
-
response.headers.set("access-control-allow-headers", allowHeaders.join(", "));
|
|
332
|
-
}
|
|
333
|
-
if (options.maxAge !== undefined) {
|
|
334
|
-
response.headers.set("access-control-max-age", String(options.maxAge));
|
|
335
|
-
}
|
|
336
|
-
return response;
|
|
337
|
-
};
|
|
338
|
-
};
|
|
339
|
-
// src/server/errors.ts
|
|
340
|
-
import { AppError as AppError3, ConsoleLogger } from "@dunx/core";
|
|
341
|
-
class HttpError extends AppError3 {
|
|
342
|
-
status;
|
|
343
|
-
name = "HttpError";
|
|
344
|
-
headers;
|
|
345
|
-
constructor(status, message, options) {
|
|
346
|
-
super(message, options);
|
|
347
|
-
this.status = status;
|
|
348
|
-
this.headers = options?.headers;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
Object.defineProperty(HttpError, Symbol.for("dunx.deps"), {
|
|
352
|
-
value: () => [{ unresolved: "readonly status: number" }, { unresolved: "message: string" }, { unresolved: "options?: HttpErrorOptions" }]
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
class ValidationError extends HttpError {
|
|
356
|
-
source;
|
|
357
|
-
issues;
|
|
358
|
-
name = "ValidationError";
|
|
359
|
-
constructor(source, issues) {
|
|
360
|
-
super(HttpStatusCode.BAD_REQUEST, `Invalid ${source}`);
|
|
361
|
-
this.source = source;
|
|
362
|
-
this.issues = issues;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
Object.defineProperty(ValidationError, Symbol.for("dunx.deps"), {
|
|
366
|
-
value: () => [{ unresolved: "readonly source: InputSource" }, { unresolved: "readonly issues: readonly ValidationIssue[]" }]
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
class ErrorFilter {
|
|
370
|
-
}
|
|
371
|
-
var isErrorFilter = (handler) => typeof handler === "function" && typeof handler.prototype?.catch === "function";
|
|
372
|
-
var toErrorMapper = (handler, resolve) => isErrorFilter(handler) ? (error, req) => resolve(handler).catch(error, req) : handler;
|
|
373
|
-
var errorMapper = (logger) => (error) => {
|
|
374
|
-
if (error instanceof ValidationError) {
|
|
375
|
-
return Response.json({ error: error.message, status: error.status, issues: error.issues }, {
|
|
376
|
-
status: error.status,
|
|
377
|
-
...error.headers && { headers: error.headers }
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
if (error instanceof HttpError) {
|
|
381
|
-
return Response.json({ error: error.message, status: error.status }, {
|
|
382
|
-
status: error.status,
|
|
383
|
-
...error.headers && { headers: error.headers }
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
logger.error("Unhandled error", error);
|
|
387
|
-
return Response.json({
|
|
388
|
-
error: "Internal Server Error",
|
|
389
|
-
status: HttpStatusCode.INTERNAL_SERVER_ERROR
|
|
390
|
-
}, { status: HttpStatusCode.INTERNAL_SERVER_ERROR });
|
|
391
|
-
};
|
|
392
|
-
var defaultErrorMapper = errorMapper(new ConsoleLogger);
|
|
393
118
|
// src/server/factory.ts
|
|
394
119
|
import {
|
|
395
|
-
collectModules
|
|
396
|
-
AppError as
|
|
120
|
+
collectModules,
|
|
121
|
+
AppError as AppError4,
|
|
397
122
|
AppFactory,
|
|
398
123
|
Logger as Logger4,
|
|
399
124
|
provide,
|
|
400
|
-
readControllers
|
|
125
|
+
readControllers,
|
|
401
126
|
RequestContext as RequestContext3
|
|
402
127
|
} from "@dunx/core";
|
|
403
128
|
|
|
404
|
-
// src/ws/envelope.ts
|
|
405
|
-
var encode = (event, data) => JSON.stringify({ event, data });
|
|
406
|
-
var decode = (message) => {
|
|
407
|
-
if (typeof message !== "string")
|
|
408
|
-
return;
|
|
409
|
-
let parsed;
|
|
410
|
-
try {
|
|
411
|
-
parsed = JSON.parse(message);
|
|
412
|
-
} catch {
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
if (typeof parsed !== "object" || parsed === null)
|
|
416
|
-
return;
|
|
417
|
-
const { event, data } = parsed;
|
|
418
|
-
return typeof event === "string" ? { event, data } : undefined;
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
// src/ws/middleware.ts
|
|
422
|
-
var composeSocket = (middleware, ctx) => middleware.reduceRight((next, current) => (frame, run) => current.handle(frame, ctx, () => next(frame, run)), (_frame, run) => run());
|
|
423
|
-
var observe = (next, done) => {
|
|
424
|
-
let result;
|
|
425
|
-
try {
|
|
426
|
-
result = next();
|
|
427
|
-
} catch (error) {
|
|
428
|
-
done(error, undefined);
|
|
429
|
-
throw error;
|
|
430
|
-
}
|
|
431
|
-
if (result instanceof Promise) {
|
|
432
|
-
return result.then((value) => {
|
|
433
|
-
done(undefined, value);
|
|
434
|
-
return value;
|
|
435
|
-
}, (error) => {
|
|
436
|
-
done(error, undefined);
|
|
437
|
-
throw error;
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
done(undefined, result);
|
|
441
|
-
return result;
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
// src/ws/runtime.ts
|
|
445
|
-
import { AppError as AppError4 } from "@dunx/core";
|
|
446
|
-
var slotOf = (handler) => handler.kind === HandlerKind.MESSAGE && handler.event !== undefined ? `message ${JSON.stringify(handler.event)}` : handler.kind;
|
|
447
|
-
var buildRuntime = (gateway) => {
|
|
448
|
-
if (gateway.handlers.length === 0) {
|
|
449
|
-
throw new AppError4(`${gateway.name} is registered as a gateway but declares no handlers. ` + "Add an @OnMessage/@OnOpen/... method, or drop the @Gateway decorator.");
|
|
450
|
-
}
|
|
451
|
-
const owners = new Map;
|
|
452
|
-
const events = new Map;
|
|
453
|
-
for (const handler of gateway.handlers) {
|
|
454
|
-
const slot = slotOf(handler);
|
|
455
|
-
const existing = owners.get(slot);
|
|
456
|
-
if (existing) {
|
|
457
|
-
throw new AppError4(`Handler collision in ${gateway.name}: ${slot} is claimed by ` + `${existing.method}() and by ${handler.method}(). One handler per event.`);
|
|
458
|
-
}
|
|
459
|
-
owners.set(slot, handler);
|
|
460
|
-
if (handler.kind === HandlerKind.MESSAGE && handler.event !== undefined) {
|
|
461
|
-
events.set(handler.event, handler.invoke);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
const at = (slot) => owners.get(slot)?.invoke;
|
|
465
|
-
return {
|
|
466
|
-
name: gateway.name,
|
|
467
|
-
path: gateway.path,
|
|
468
|
-
upgrade: at(HandlerKind.UPGRADE),
|
|
469
|
-
open: at(HandlerKind.OPEN),
|
|
470
|
-
close: at(HandlerKind.CLOSE),
|
|
471
|
-
drain: at(HandlerKind.DRAIN),
|
|
472
|
-
ping: at(HandlerKind.PING),
|
|
473
|
-
pong: at(HandlerKind.PONG),
|
|
474
|
-
raw: at(HandlerKind.MESSAGE),
|
|
475
|
-
events
|
|
476
|
-
};
|
|
477
|
-
};
|
|
478
|
-
var buildGateways = (discovered) => {
|
|
479
|
-
const byPath = new Map;
|
|
480
|
-
for (const gateway of discovered) {
|
|
481
|
-
const existing = byPath.get(gateway.path);
|
|
482
|
-
if (existing) {
|
|
483
|
-
throw new AppError4(`Gateway path collision: ${gateway.path} is served by ${existing.name} ` + `and by ${gateway.name}. One gateway per path.`);
|
|
484
|
-
}
|
|
485
|
-
byPath.set(gateway.path, buildRuntime(gateway));
|
|
486
|
-
}
|
|
487
|
-
return byPath;
|
|
488
|
-
};
|
|
489
|
-
var someHandler = (gateways, pick) => {
|
|
490
|
-
for (const gateway of gateways)
|
|
491
|
-
if (pick(gateway) !== undefined)
|
|
492
|
-
return true;
|
|
493
|
-
return false;
|
|
494
|
-
};
|
|
495
|
-
|
|
496
|
-
// src/ws/adapter.ts
|
|
497
|
-
var RUNTIME = Symbol.for("dunx.ws.runtime");
|
|
498
|
-
var UNCLAIMED = Symbol.for("dunx.ws.unclaimed");
|
|
499
|
-
var defaultOnError = (error, socket) => {
|
|
500
|
-
console.error(`[dunx/http] ${socket.data.path} handler failed:`, error);
|
|
501
|
-
};
|
|
502
|
-
var reportedByMiddleware = () => {
|
|
503
|
-
return;
|
|
504
|
-
};
|
|
505
|
-
var unreported = (middleware) => "Socket middleware is installed and none of it sets reportsErrors, so a " + "throwing gateway handler is reported nowhere: the console fallback is off " + "whenever middleware wraps the handler. Set reportsErrors on the one that " + "records a failure, or pass websocket.onError. Installed: " + `${middleware.map((entry) => entry.constructor.name).join(", ")}.`;
|
|
506
|
-
var runtimeOf = (socket) => socket.data[RUNTIME];
|
|
507
|
-
var isBinary = (value) => value instanceof ArrayBuffer || ArrayBuffer.isView(value);
|
|
508
|
-
var replyRaw = (socket, value) => {
|
|
509
|
-
if (value === undefined)
|
|
510
|
-
return;
|
|
511
|
-
socket.send(typeof value === "string" || isBinary(value) ? value : JSON.stringify(value));
|
|
512
|
-
};
|
|
513
|
-
var settle = (result, socket, onError, then) => {
|
|
514
|
-
if (result instanceof Promise) {
|
|
515
|
-
result.then((value) => {
|
|
516
|
-
if (!then)
|
|
517
|
-
return;
|
|
518
|
-
try {
|
|
519
|
-
then(value);
|
|
520
|
-
} catch (error) {
|
|
521
|
-
onError(error, socket);
|
|
522
|
-
}
|
|
523
|
-
}, (error) => onError(error, socket));
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
if (then)
|
|
527
|
-
then(result);
|
|
528
|
-
};
|
|
529
|
-
var framing = (kind) => {
|
|
530
|
-
if (kind === HandlerKind.CLOSE) {
|
|
531
|
-
return (args) => ({
|
|
532
|
-
socket: args[0],
|
|
533
|
-
data: { code: args[1], reason: args[2] }
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
if (kind === HandlerKind.OPEN || kind === HandlerKind.DRAIN) {
|
|
537
|
-
return (args) => ({ socket: args[0], data: undefined });
|
|
538
|
-
}
|
|
539
|
-
return (args) => ({ socket: args[1], data: args[0] });
|
|
540
|
-
};
|
|
541
|
-
var NOTHING = () => {
|
|
542
|
-
return;
|
|
543
|
-
};
|
|
544
|
-
var through = (gateway, middleware, kind, event, invoke) => {
|
|
545
|
-
const ctx = {
|
|
546
|
-
gateway: gateway.name,
|
|
547
|
-
path: gateway.path,
|
|
548
|
-
kind,
|
|
549
|
-
event
|
|
550
|
-
};
|
|
551
|
-
const dispatch = composeSocket(middleware, ctx);
|
|
552
|
-
const frameOf = framing(kind);
|
|
553
|
-
const run = invoke ?? NOTHING;
|
|
554
|
-
return (...args) => dispatch(frameOf(args), () => run(...args));
|
|
555
|
-
};
|
|
556
|
-
var withMiddleware = (gateway, middleware) => {
|
|
557
|
-
const wrap = (kind, event, invoke) => through(gateway, middleware, kind, event, invoke);
|
|
558
|
-
const optional = (kind, invoke) => invoke === undefined ? undefined : wrap(kind, undefined, invoke);
|
|
559
|
-
return {
|
|
560
|
-
...gateway,
|
|
561
|
-
open: wrap(HandlerKind.OPEN, undefined, gateway.open),
|
|
562
|
-
close: wrap(HandlerKind.CLOSE, undefined, gateway.close),
|
|
563
|
-
drain: optional(HandlerKind.DRAIN, gateway.drain),
|
|
564
|
-
ping: optional(HandlerKind.PING, gateway.ping),
|
|
565
|
-
pong: optional(HandlerKind.PONG, gateway.pong),
|
|
566
|
-
raw: optional(HandlerKind.MESSAGE, gateway.raw),
|
|
567
|
-
events: new Map([...gateway.events].map(([event, invoke]) => [
|
|
568
|
-
event,
|
|
569
|
-
wrap(HandlerKind.MESSAGE, event, invoke)
|
|
570
|
-
]))
|
|
571
|
-
};
|
|
572
|
-
};
|
|
573
|
-
var unclaimedDispatch = (gateway, middleware) => (frame, event) => composeSocket(middleware, {
|
|
574
|
-
gateway: gateway.name,
|
|
575
|
-
path: gateway.path,
|
|
576
|
-
kind: HandlerKind.MESSAGE,
|
|
577
|
-
event
|
|
578
|
-
})(frame, () => {
|
|
579
|
-
return;
|
|
580
|
-
});
|
|
581
|
-
var buildWebSocket = (discovered, options = {}, middleware = []) => {
|
|
582
|
-
const byPath = buildGateways(discovered);
|
|
583
|
-
const wrapped = middleware.length === 0 ? byPath : new Map([...byPath].map(([path, gateway]) => [
|
|
584
|
-
path,
|
|
585
|
-
withMiddleware(gateway, middleware)
|
|
586
|
-
]));
|
|
587
|
-
const gateways = [...wrapped.values()];
|
|
588
|
-
const onError = options.onError ?? (middleware.length === 0 ? defaultOnError : reportedByMiddleware);
|
|
589
|
-
const reports = options.onError !== undefined || middleware.some((entry) => entry.reportsErrors === true);
|
|
590
|
-
const { onError: _onError, ...socketOptions } = options;
|
|
591
|
-
const run = (invoke, args, ws, then) => {
|
|
592
|
-
try {
|
|
593
|
-
settle(invoke(...args), ws, onError, then);
|
|
594
|
-
} catch (error) {
|
|
595
|
-
onError(error, ws);
|
|
596
|
-
}
|
|
597
|
-
};
|
|
598
|
-
const websocket = {
|
|
599
|
-
...socketOptions,
|
|
600
|
-
message(ws, message) {
|
|
601
|
-
const gateway = runtimeOf(ws);
|
|
602
|
-
let event;
|
|
603
|
-
if (gateway.events.size > 0) {
|
|
604
|
-
const envelope = decode(message);
|
|
605
|
-
const handler = envelope && gateway.events.get(envelope.event);
|
|
606
|
-
if (envelope && handler) {
|
|
607
|
-
run(handler, [envelope.data, ws], ws, (value) => {
|
|
608
|
-
if (value !== undefined)
|
|
609
|
-
ws.send(encode(envelope.event, value));
|
|
610
|
-
});
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
event = envelope?.event;
|
|
614
|
-
}
|
|
615
|
-
if (gateway.raw) {
|
|
616
|
-
run(gateway.raw, [message, ws], ws, (value) => replyRaw(ws, value));
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
619
|
-
const unclaimed2 = ws.data[UNCLAIMED];
|
|
620
|
-
if (!unclaimed2)
|
|
621
|
-
return;
|
|
622
|
-
try {
|
|
623
|
-
settle(unclaimed2({ socket: ws, data: message }, event), ws, onError, undefined);
|
|
624
|
-
} catch (error) {
|
|
625
|
-
onError(error, ws);
|
|
626
|
-
}
|
|
627
|
-
},
|
|
628
|
-
...someHandler(gateways, (g) => g.open) && {
|
|
629
|
-
open(ws) {
|
|
630
|
-
const { open } = runtimeOf(ws);
|
|
631
|
-
if (open)
|
|
632
|
-
run(open, [ws], ws, undefined);
|
|
633
|
-
}
|
|
634
|
-
},
|
|
635
|
-
...someHandler(gateways, (g) => g.close) && {
|
|
636
|
-
close(ws, code, reason) {
|
|
637
|
-
const { close } = runtimeOf(ws);
|
|
638
|
-
if (close)
|
|
639
|
-
run(close, [ws, code, reason], ws, undefined);
|
|
640
|
-
}
|
|
641
|
-
},
|
|
642
|
-
...someHandler(gateways, (g) => g.drain) && {
|
|
643
|
-
drain(ws) {
|
|
644
|
-
const { drain } = runtimeOf(ws);
|
|
645
|
-
if (drain)
|
|
646
|
-
run(drain, [ws], ws, undefined);
|
|
647
|
-
}
|
|
648
|
-
},
|
|
649
|
-
...someHandler(gateways, (g) => g.ping) && {
|
|
650
|
-
ping(ws, data) {
|
|
651
|
-
const { ping } = runtimeOf(ws);
|
|
652
|
-
if (ping)
|
|
653
|
-
run(ping, [data, ws], ws, undefined);
|
|
654
|
-
}
|
|
655
|
-
},
|
|
656
|
-
...someHandler(gateways, (g) => g.pong) && {
|
|
657
|
-
pong(ws, data) {
|
|
658
|
-
const { pong } = runtimeOf(ws);
|
|
659
|
-
if (pong)
|
|
660
|
-
run(pong, [data, ws], ws, undefined);
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
};
|
|
664
|
-
const unclaimed = new Map(middleware.length === 0 ? [] : gateways.map((gateway) => [
|
|
665
|
-
gateway,
|
|
666
|
-
unclaimedDispatch(gateway, middleware)
|
|
667
|
-
]));
|
|
668
|
-
const accept = (req, server, gateway, context) => {
|
|
669
|
-
const fallback = unclaimed.get(gateway);
|
|
670
|
-
const data = {
|
|
671
|
-
path: gateway.path,
|
|
672
|
-
context,
|
|
673
|
-
id: crypto.randomUUID(),
|
|
674
|
-
[RUNTIME]: gateway,
|
|
675
|
-
...fallback === undefined ? {} : { [UNCLAIMED]: fallback }
|
|
676
|
-
};
|
|
677
|
-
return server.upgrade(req, { data }) ? undefined : new Response("Expected a WebSocket upgrade", { status: 426 });
|
|
678
|
-
};
|
|
679
|
-
const upgradeHandler = (gateway) => (req, server) => {
|
|
680
|
-
if (!gateway.upgrade)
|
|
681
|
-
return accept(req, server, gateway, undefined);
|
|
682
|
-
const result = gateway.upgrade(req);
|
|
683
|
-
if (result instanceof Promise) {
|
|
684
|
-
return result.then((value) => value instanceof Response ? value : accept(req, server, gateway, value));
|
|
685
|
-
}
|
|
686
|
-
return result instanceof Response ? result : accept(req, server, gateway, result);
|
|
687
|
-
};
|
|
688
|
-
return {
|
|
689
|
-
websocket,
|
|
690
|
-
routes: new Map(gateways.map((gateway) => [gateway.path, upgradeHandler(gateway)])),
|
|
691
|
-
paths: [...byPath.keys()],
|
|
692
|
-
warnings: middleware.length > 0 && !reports ? [unreported(middleware)] : [],
|
|
693
|
-
gateways: gateways.map((gateway) => ({
|
|
694
|
-
name: gateway.name,
|
|
695
|
-
path: gateway.path,
|
|
696
|
-
events: [...gateway.events.keys()]
|
|
697
|
-
}))
|
|
698
|
-
};
|
|
699
|
-
};
|
|
700
|
-
|
|
701
129
|
// src/ws/logging.ts
|
|
702
130
|
import { Logger, LogLevel, RequestContext } from "@dunx/core";
|
|
703
131
|
var LIFECYCLE_LABEL = {
|
|
@@ -742,7 +170,7 @@ class SocketLoggingMiddleware {
|
|
|
742
170
|
const label = ctx.event ?? LIFECYCLE_LABEL[ctx.kind] ?? ctx.kind;
|
|
743
171
|
const connectionId = frame.socket.data.id;
|
|
744
172
|
const started = Bun.nanoseconds();
|
|
745
|
-
const
|
|
173
|
+
const write = (error, value) => {
|
|
746
174
|
const entry = {
|
|
747
175
|
gateway: ctx.gateway,
|
|
748
176
|
path: ctx.path,
|
|
@@ -756,8 +184,8 @@ class SocketLoggingMiddleware {
|
|
|
756
184
|
this.#emit(error === undefined ? level : this.#errorLevel, line, entry);
|
|
757
185
|
};
|
|
758
186
|
if (!this.#correlate)
|
|
759
|
-
return observe(next,
|
|
760
|
-
return this.context.runWithContext({ connectionId, event: label, flow: "ws", context: ctx.gateway }, () => observe(next,
|
|
187
|
+
return observe(next, write);
|
|
188
|
+
return this.context.runWithContext({ connectionId, event: label, flow: "ws", context: ctx.gateway }, () => observe(next, write));
|
|
761
189
|
}
|
|
762
190
|
#emit(level, line, entry) {
|
|
763
191
|
switch (level) {
|
|
@@ -793,42 +221,10 @@ class SocketLoggingMiddleware {
|
|
|
793
221
|
return text.length > this.#limit ? `[${text.length} chars]` : data;
|
|
794
222
|
}
|
|
795
223
|
}
|
|
796
|
-
Object.defineProperty(SocketLoggingMiddleware, Symbol.for("dunx.deps"), {
|
|
797
|
-
value: () => [Logger, RequestContext, { unresolved: "options: SocketLoggingOptions = {}" }]
|
|
798
|
-
});
|
|
799
|
-
|
|
800
|
-
// src/ws/pubsub.ts
|
|
801
|
-
import { AppError as AppError5 } from "@dunx/core";
|
|
802
|
-
|
|
803
|
-
// src/ws/relay.ts
|
|
804
|
-
var DEFAULT_RELAY_CHANNEL = "dunx:ws";
|
|
805
|
-
var defaultRelayError = (error, phase) => {
|
|
806
|
-
console.warn(`[dunx/http] the websocket relay could not ${phase}. Fan-out is local to ` + "this process until it recovers:", error);
|
|
807
|
-
};
|
|
808
|
-
var toBytes = (data) => ArrayBuffer.isView(data) ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : new Uint8Array(data);
|
|
809
|
-
var encodeRelay = (origin, topic, data) => typeof data === "string" ? JSON.stringify({ o: origin, t: topic, d: data }) : JSON.stringify({
|
|
810
|
-
o: origin,
|
|
811
|
-
t: topic,
|
|
812
|
-
d: Buffer.from(toBytes(data)).toString("base64"),
|
|
813
|
-
b: 1
|
|
814
|
-
});
|
|
815
|
-
var decodeRelay = (message) => {
|
|
816
|
-
let parsed;
|
|
817
|
-
try {
|
|
818
|
-
parsed = JSON.parse(message);
|
|
819
|
-
} catch {
|
|
820
|
-
return;
|
|
821
|
-
}
|
|
822
|
-
if (typeof parsed !== "object" || parsed === null)
|
|
823
|
-
return;
|
|
824
|
-
const { o, t, d, b } = parsed;
|
|
825
|
-
if (typeof o !== "string" || typeof t !== "string" || typeof d !== "string") {
|
|
826
|
-
return;
|
|
827
|
-
}
|
|
828
|
-
return { origin: o, topic: t, data: b ? Buffer.from(d, "base64") : d };
|
|
829
|
-
};
|
|
224
|
+
Object.defineProperty(SocketLoggingMiddleware, Symbol.for("dunx.deps"), { value: () => [Logger, RequestContext, { unresolved: "options: SocketLoggingOptions = {}", optional: true }] });
|
|
830
225
|
|
|
831
226
|
// src/ws/pubsub.ts
|
|
227
|
+
import { AppError as AppError2 } from "@dunx/core";
|
|
832
228
|
class PubSub {
|
|
833
229
|
#origin = Bun.randomUUIDv7();
|
|
834
230
|
#server;
|
|
@@ -853,7 +249,7 @@ class PubSub {
|
|
|
853
249
|
}
|
|
854
250
|
async relayThrough(relay, options = {}) {
|
|
855
251
|
if (this.#relay) {
|
|
856
|
-
throw new
|
|
252
|
+
throw new AppError2("PubSub already relays. Two subscriptions on one channel would deliver " + "every relayed message twice - pass HttpOptions.relay or call " + "relayThrough(), not both.");
|
|
857
253
|
}
|
|
858
254
|
this.#relay = relay;
|
|
859
255
|
this.#channel = options.channel ?? DEFAULT_RELAY_CHANNEL;
|
|
@@ -949,7 +345,7 @@ class PubSub {
|
|
|
949
345
|
}
|
|
950
346
|
#live() {
|
|
951
347
|
if (!this.#server) {
|
|
952
|
-
throw new
|
|
348
|
+
throw new AppError2("PubSub has no server yet. Publish once the server is listening: " + "HttpApp.listen() is what attaches it.");
|
|
953
349
|
}
|
|
954
350
|
return this.#server;
|
|
955
351
|
}
|
|
@@ -957,7 +353,7 @@ class PubSub {
|
|
|
957
353
|
|
|
958
354
|
// src/server/application.ts
|
|
959
355
|
import {
|
|
960
|
-
AppError as
|
|
356
|
+
AppError as AppError3,
|
|
961
357
|
Logger as Logger3,
|
|
962
358
|
runtimeInfo,
|
|
963
359
|
ShutdownHooks,
|
|
@@ -970,47 +366,6 @@ import {
|
|
|
970
366
|
Logger as Logger2,
|
|
971
367
|
RequestContext as RequestContext2
|
|
972
368
|
} from "@dunx/core";
|
|
973
|
-
|
|
974
|
-
// src/server/raw-body.ts
|
|
975
|
-
var WANTED = Symbol.for("dunx.http.rawBody.wanted");
|
|
976
|
-
var TEXT = Symbol.for("dunx.http.rawBody.text");
|
|
977
|
-
|
|
978
|
-
class RawBody {
|
|
979
|
-
static want(req) {
|
|
980
|
-
req[WANTED] = true;
|
|
981
|
-
}
|
|
982
|
-
static wanted(req) {
|
|
983
|
-
return req[WANTED] === true;
|
|
984
|
-
}
|
|
985
|
-
static record(req, text) {
|
|
986
|
-
req[TEXT] = text;
|
|
987
|
-
}
|
|
988
|
-
static read(req) {
|
|
989
|
-
return req[TEXT];
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
// src/server/request-id.ts
|
|
994
|
-
var REQUEST_ID_HEADER = "x-request-id";
|
|
995
|
-
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
996
|
-
var traceId = (inbound) => inbound !== null && inbound.length === 36 && UUID.test(inbound) ? inbound : crypto.randomUUID();
|
|
997
|
-
var ID = Symbol.for("dunx.http.requestId");
|
|
998
|
-
|
|
999
|
-
class RequestIds {
|
|
1000
|
-
static assign(req) {
|
|
1001
|
-
const id = traceId(req.headers.get(REQUEST_ID_HEADER));
|
|
1002
|
-
req[ID] = id;
|
|
1003
|
-
return id;
|
|
1004
|
-
}
|
|
1005
|
-
static stamp(response, req) {
|
|
1006
|
-
const id = req[ID];
|
|
1007
|
-
if (id !== undefined)
|
|
1008
|
-
response.headers.set(REQUEST_ID_HEADER, id);
|
|
1009
|
-
return response;
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
// src/server/request-logging.ts
|
|
1014
369
|
var parse = (text, limit) => {
|
|
1015
370
|
if (limit === 0)
|
|
1016
371
|
return;
|
|
@@ -1036,6 +391,7 @@ class RequestLoggingMiddleware {
|
|
|
1036
391
|
#ignorePrefix;
|
|
1037
392
|
#correlateIgnored;
|
|
1038
393
|
#correlate;
|
|
394
|
+
#trace;
|
|
1039
395
|
constructor(logger, context, options = {}) {
|
|
1040
396
|
this.logger = logger;
|
|
1041
397
|
this.context = context;
|
|
@@ -1046,6 +402,7 @@ class RequestLoggingMiddleware {
|
|
|
1046
402
|
this.#ignorePrefix = options.ignorePrefix ?? [];
|
|
1047
403
|
this.#correlateIgnored = options.correlateIgnored ?? false;
|
|
1048
404
|
this.#correlate = options.correlate ?? true;
|
|
405
|
+
this.#trace = options.trace ?? false;
|
|
1049
406
|
}
|
|
1050
407
|
#ignored(path) {
|
|
1051
408
|
if (this.#ignore.size > 0 && this.#ignore.has(path))
|
|
@@ -1071,6 +428,14 @@ class RequestLoggingMiddleware {
|
|
|
1071
428
|
flow: "http",
|
|
1072
429
|
context: `${ctx.controller}.${ctx.handler}`
|
|
1073
430
|
};
|
|
431
|
+
if (this.#trace) {
|
|
432
|
+
const trace = TraceContext.adopt(req, requestId);
|
|
433
|
+
scope.traceId = trace.traceId;
|
|
434
|
+
scope.spanId = trace.spanId;
|
|
435
|
+
if (trace.parentSpanId !== undefined) {
|
|
436
|
+
scope.parentSpanId = trace.parentSpanId;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
1074
439
|
return this.#correlate ? this.context.runWithContext(scope, () => this.#begin(req, ctx, url, mark, path, requestId, started, next, undefined)) : this.#begin(req, ctx, url, mark, path, requestId, started, next, scope);
|
|
1075
440
|
}
|
|
1076
441
|
#begin(req, ctx, url, mark, path, requestId, started, next, scope) {
|
|
@@ -1196,248 +561,7 @@ class RequestLoggingMiddleware {
|
|
|
1196
561
|
return response.clone().text().then((text) => parse(text, this.#limit));
|
|
1197
562
|
}
|
|
1198
563
|
}
|
|
1199
|
-
Object.defineProperty(RequestLoggingMiddleware, Symbol.for("dunx.deps"), {
|
|
1200
|
-
value: () => [Logger2, RequestContext2, { unresolved: "options: RequestLoggingOptions = {}" }]
|
|
1201
|
-
});
|
|
1202
|
-
|
|
1203
|
-
// src/server/routes.ts
|
|
1204
|
-
import { AppError as AppError6 } from "@dunx/core";
|
|
1205
|
-
|
|
1206
|
-
// src/server/input.ts
|
|
1207
|
-
var grouped = (entries) => {
|
|
1208
|
-
const collected = {};
|
|
1209
|
-
entries.forEach((value, key) => {
|
|
1210
|
-
const existing = collected[key];
|
|
1211
|
-
if (existing === undefined)
|
|
1212
|
-
collected[key] = value;
|
|
1213
|
-
else if (Array.isArray(existing))
|
|
1214
|
-
existing.push(value);
|
|
1215
|
-
else
|
|
1216
|
-
collected[key] = [existing, value];
|
|
1217
|
-
});
|
|
1218
|
-
return collected;
|
|
1219
|
-
};
|
|
1220
|
-
var asJson = (req) => req.json();
|
|
1221
|
-
var asUrlEncoded = async (req) => grouped(new URLSearchParams(await req.text()));
|
|
1222
|
-
var asMultipart = async (req) => grouped(await req.formData());
|
|
1223
|
-
var asText = (req) => req.text();
|
|
1224
|
-
var parserFor = (media) => {
|
|
1225
|
-
if (media === "application/json" || media.endsWith("+json"))
|
|
1226
|
-
return asJson;
|
|
1227
|
-
if (media === "application/x-www-form-urlencoded")
|
|
1228
|
-
return asUrlEncoded;
|
|
1229
|
-
if (media === "multipart/form-data")
|
|
1230
|
-
return asMultipart;
|
|
1231
|
-
if (media.startsWith("text/"))
|
|
1232
|
-
return asText;
|
|
1233
|
-
return;
|
|
1234
|
-
};
|
|
1235
|
-
var JSON_MEDIA = "application/json";
|
|
1236
|
-
var mediaTypeOf = (req) => {
|
|
1237
|
-
const header = req.headers.get("content-type");
|
|
1238
|
-
if (header === JSON_MEDIA || header === null)
|
|
1239
|
-
return JSON_MEDIA;
|
|
1240
|
-
const end = header.indexOf(";");
|
|
1241
|
-
const media = (end === -1 ? header : header.slice(0, end)).trim();
|
|
1242
|
-
return media === "" ? JSON_MEDIA : media.toLowerCase();
|
|
1243
|
-
};
|
|
1244
|
-
var flatten = (issue) => {
|
|
1245
|
-
const path = issue.path?.map((segment) => String(typeof segment === "object" ? segment.key : segment)).join(".");
|
|
1246
|
-
return path === undefined || path === "" ? { message: issue.message } : { message: issue.message, path };
|
|
1247
|
-
};
|
|
1248
|
-
var accept = (source, result) => {
|
|
1249
|
-
if (result.issues !== undefined) {
|
|
1250
|
-
throw new ValidationError(source, result.issues.map(flatten));
|
|
1251
|
-
}
|
|
1252
|
-
return result.value;
|
|
1253
|
-
};
|
|
1254
|
-
var fillWith = (draft, source, schema, value) => {
|
|
1255
|
-
const result = schema["~standard"].validate(value);
|
|
1256
|
-
if (result instanceof Promise) {
|
|
1257
|
-
return result.then((settled) => {
|
|
1258
|
-
draft[source] = accept(source, settled);
|
|
1259
|
-
return draft;
|
|
1260
|
-
});
|
|
1261
|
-
}
|
|
1262
|
-
draft[source] = accept(source, result);
|
|
1263
|
-
return draft;
|
|
1264
|
-
};
|
|
1265
|
-
var bodyFill = (schema) => (draft) => {
|
|
1266
|
-
const media = mediaTypeOf(draft.req);
|
|
1267
|
-
const parse2 = parserFor(media);
|
|
1268
|
-
if (parse2 === undefined) {
|
|
1269
|
-
throw new HttpError(HttpStatusCode.UNSUPPORTED_MEDIA_TYPE, `Unsupported content type "${media}". Declared bodies accept ` + "application/json, application/x-www-form-urlencoded, multipart/form-data or text/*.");
|
|
1270
|
-
}
|
|
1271
|
-
const read = parse2 === asJson && RawBody.wanted(draft.req) ? draft.req.text().then((text) => {
|
|
1272
|
-
RawBody.record(draft.req, text);
|
|
1273
|
-
return JSON.parse(text);
|
|
1274
|
-
}) : parse2(draft.req);
|
|
1275
|
-
return read.then((value) => fillWith(draft, "body", schema, value), (error) => {
|
|
1276
|
-
throw new HttpError(HttpStatusCode.BAD_REQUEST, `Malformed ${media} body`, { cause: error });
|
|
1277
|
-
});
|
|
1278
|
-
};
|
|
1279
|
-
var searchOf = (url) => {
|
|
1280
|
-
const start = url.indexOf("?");
|
|
1281
|
-
if (start === -1)
|
|
1282
|
-
return "";
|
|
1283
|
-
const end = url.indexOf("#", start + 1);
|
|
1284
|
-
return end === -1 ? url.slice(start + 1) : url.slice(start + 1, end);
|
|
1285
|
-
};
|
|
1286
|
-
var queryFill = (schema) => (draft) => {
|
|
1287
|
-
const params = new URLSearchParams(searchOf(draft.req.url));
|
|
1288
|
-
return fillWith(draft, "query", schema, grouped(params));
|
|
1289
|
-
};
|
|
1290
|
-
var paramsFill = (schema) => (draft) => fillWith(draft, "params", schema, draft.req.params);
|
|
1291
|
-
var then = (first, second) => (draft) => {
|
|
1292
|
-
const started = first(draft);
|
|
1293
|
-
return started instanceof Promise ? started.then(second) : second(started);
|
|
1294
|
-
};
|
|
1295
|
-
var buildInputReader = (options) => {
|
|
1296
|
-
const fills = [];
|
|
1297
|
-
if (options?.body !== undefined)
|
|
1298
|
-
fills.push(bodyFill(options.body));
|
|
1299
|
-
if (options?.query !== undefined)
|
|
1300
|
-
fills.push(queryFill(options.query));
|
|
1301
|
-
if (options?.params !== undefined)
|
|
1302
|
-
fills.push(paramsFill(options.params));
|
|
1303
|
-
if (fills.length === 0)
|
|
1304
|
-
return (req) => ({ req });
|
|
1305
|
-
const fill = fills.reduce(then);
|
|
1306
|
-
return (req) => fill({ req });
|
|
1307
|
-
};
|
|
1308
|
-
|
|
1309
|
-
// src/server/middleware.ts
|
|
1310
|
-
var compose = (middleware, ctx, handler) => middleware.reduceRight((next, current) => (req) => current.handle(req, ctx, () => next(req)), handler);
|
|
1311
|
-
|
|
1312
|
-
// src/server/routes.ts
|
|
1313
|
-
var construct = (guard) => new guard;
|
|
1314
|
-
var toResponse = (value, status) => {
|
|
1315
|
-
if (value instanceof Response)
|
|
1316
|
-
return value;
|
|
1317
|
-
if (value === undefined || value === null) {
|
|
1318
|
-
return new Response(null, { status: HttpStatusCode.NO_CONTENT });
|
|
1319
|
-
}
|
|
1320
|
-
return Response.json(value, { status });
|
|
1321
|
-
};
|
|
1322
|
-
var statusFor = (route) => route.options?.status ?? (route.method === "POST" ? HttpStatusCode.CREATED : HttpStatusCode.OK);
|
|
1323
|
-
var assertNoCollisions = (discovered) => {
|
|
1324
|
-
const owners = new Map;
|
|
1325
|
-
for (const route of discovered) {
|
|
1326
|
-
const key = `${route.method} ${route.path}`;
|
|
1327
|
-
const owner = `${route.controller}.${route.handlerName}`;
|
|
1328
|
-
const existing = owners.get(key);
|
|
1329
|
-
if (existing !== undefined) {
|
|
1330
|
-
throw new AppError6(`Route collision: ${key} is declared by ${existing} and by ${owner}. ` + "Bun would keep only one of them.");
|
|
1331
|
-
}
|
|
1332
|
-
owners.set(key, owner);
|
|
1333
|
-
}
|
|
1334
|
-
};
|
|
1335
|
-
var assertNoGatewayCollisions = (discovered, gatewayPaths) => {
|
|
1336
|
-
const gateways = new Set(gatewayPaths);
|
|
1337
|
-
for (const route of discovered) {
|
|
1338
|
-
if (gateways.has(route.path)) {
|
|
1339
|
-
throw new AppError6(`Gateway path collision: ${route.path} is served by a gateway and by ` + `${route.controller}.${route.handlerName}(). The upgrade is a route too, ` + "so one of them would be dropped.");
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
};
|
|
1343
|
-
var withUpgradeRoutes = (routes, gateways) => {
|
|
1344
|
-
const merged = { ...routes };
|
|
1345
|
-
for (const [path, upgrade] of gateways)
|
|
1346
|
-
merged[path] = { GET: upgrade };
|
|
1347
|
-
return merged;
|
|
1348
|
-
};
|
|
1349
|
-
var unmatchedContext = (req, isPublic) => Object.freeze({
|
|
1350
|
-
controller: "(unmatched)",
|
|
1351
|
-
handler: "(none)",
|
|
1352
|
-
method: req.method,
|
|
1353
|
-
path: new URL(req.url).pathname,
|
|
1354
|
-
parsesBody: false,
|
|
1355
|
-
get: (key) => {
|
|
1356
|
-
if (key.id === UNMATCHED.id)
|
|
1357
|
-
return true;
|
|
1358
|
-
if (key.id === PUBLIC.id && isPublic)
|
|
1359
|
-
return true;
|
|
1360
|
-
return;
|
|
1361
|
-
}
|
|
1362
|
-
});
|
|
1363
|
-
var buildFallback = (middleware = [], onError = defaultErrorMapper, cors, notFound = "guarded") => {
|
|
1364
|
-
const miss = () => {
|
|
1365
|
-
throw new HttpError(HttpStatusCode.NOT_FOUND, "NOT_FOUND");
|
|
1366
|
-
};
|
|
1367
|
-
const run = async (req) => {
|
|
1368
|
-
try {
|
|
1369
|
-
return await compose(middleware, unmatchedContext(req, notFound === "public"), miss)(req);
|
|
1370
|
-
} catch (error) {
|
|
1371
|
-
return RequestIds.stamp(onError(error, req), req);
|
|
1372
|
-
}
|
|
1373
|
-
};
|
|
1374
|
-
return cors ? withCors(cors, run) : run;
|
|
1375
|
-
};
|
|
1376
|
-
var directOr = (guarded, route, read, status, onError, noMiddleware) => {
|
|
1377
|
-
if (!noMiddleware)
|
|
1378
|
-
return guarded;
|
|
1379
|
-
const settle2 = (value, req) => {
|
|
1380
|
-
try {
|
|
1381
|
-
return toResponse(value, status);
|
|
1382
|
-
} catch (error) {
|
|
1383
|
-
return onError(error, req);
|
|
1384
|
-
}
|
|
1385
|
-
};
|
|
1386
|
-
const invoke = (input, req) => {
|
|
1387
|
-
try {
|
|
1388
|
-
const value = route.handler(input);
|
|
1389
|
-
return value instanceof Promise ? value.then((resolved) => settle2(resolved, req), (error) => onError(error, req)) : settle2(value, req);
|
|
1390
|
-
} catch (error) {
|
|
1391
|
-
return onError(error, req);
|
|
1392
|
-
}
|
|
1393
|
-
};
|
|
1394
|
-
return (req) => {
|
|
1395
|
-
try {
|
|
1396
|
-
const input = read(req);
|
|
1397
|
-
return input instanceof Promise ? input.then((resolved) => invoke(resolved, req), (error) => onError(error, req)) : invoke(input, req);
|
|
1398
|
-
} catch (error) {
|
|
1399
|
-
return onError(error, req);
|
|
1400
|
-
}
|
|
1401
|
-
};
|
|
1402
|
-
};
|
|
1403
|
-
var buildRoutes = (discovered, middleware = [], onError = defaultErrorMapper, cors, resolve = construct) => {
|
|
1404
|
-
assertNoCollisions(discovered);
|
|
1405
|
-
const routes = {};
|
|
1406
|
-
const instances = new Map;
|
|
1407
|
-
const guardOf = (guard, from) => {
|
|
1408
|
-
const existing = instances.get(guard);
|
|
1409
|
-
if (existing)
|
|
1410
|
-
return existing;
|
|
1411
|
-
const created = resolve(guard, from);
|
|
1412
|
-
instances.set(guard, created);
|
|
1413
|
-
return created;
|
|
1414
|
-
};
|
|
1415
|
-
for (const route of discovered) {
|
|
1416
|
-
const read = buildInputReader(route.options);
|
|
1417
|
-
const status = statusFor(route);
|
|
1418
|
-
const chain = [
|
|
1419
|
-
...middleware,
|
|
1420
|
-
...(route.moduleMiddleware ?? []).map((entry) => guardOf(entry, route.module)),
|
|
1421
|
-
...(route.guards ?? []).map((guard) => guardOf(guard, route.module))
|
|
1422
|
-
];
|
|
1423
|
-
const chained = compose(chain, buildContext(route), async (req) => toResponse(await route.handler(await read(req)), status));
|
|
1424
|
-
const guarded = async (req) => {
|
|
1425
|
-
try {
|
|
1426
|
-
return await chained(req);
|
|
1427
|
-
} catch (error) {
|
|
1428
|
-
return RequestIds.stamp(onError(error, req), req);
|
|
1429
|
-
}
|
|
1430
|
-
};
|
|
1431
|
-
const byMethod = routes[route.path] ??= {};
|
|
1432
|
-
byMethod[route.method] = cors ? withCors(cors, guarded) : directOr(guarded, route, read, status, onError, chain.length === 0);
|
|
1433
|
-
}
|
|
1434
|
-
if (cors) {
|
|
1435
|
-
for (const byMethod of Object.values(routes)) {
|
|
1436
|
-
byMethod.OPTIONS = preflight(cors, Object.keys(byMethod));
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
return routes;
|
|
1440
|
-
};
|
|
564
|
+
Object.defineProperty(RequestLoggingMiddleware, Symbol.for("dunx.deps"), { value: () => [Logger2, RequestContext2, { unresolved: "options: RequestLoggingOptions = {}", optional: true }] });
|
|
1441
565
|
|
|
1442
566
|
// src/server/settings.ts
|
|
1443
567
|
var defaultSettings = () => ({ "trust proxy": false });
|
|
@@ -1620,12 +744,10 @@ class HttpApplication {
|
|
|
1620
744
|
#assertNotStarted(hook) {
|
|
1621
745
|
if (!this.#started)
|
|
1622
746
|
return;
|
|
1623
|
-
throw new
|
|
747
|
+
throw new AppError3(`${hook} must be called before listen(). The route table and the middleware ` + "chain are folded into one closure per route when the server binds, so " + "this call could not take effect.");
|
|
1624
748
|
}
|
|
1625
749
|
}
|
|
1626
|
-
Object.defineProperty(HttpApplication, Symbol.for("dunx.deps"), {
|
|
1627
|
-
value: () => [{ unresolved: "app: App", typeOnly: "App" }, { unresolved: "discovered: readonly DiscoveredRoute[]" }, { unresolved: "options: HttpOptions" }, { unresolved: "root: ModuleRef", typeOnly: "ModuleRef" }, { unresolved: "websocket?: WebSocketRuntime", typeOnly: "WebSocketRuntime" }]
|
|
1628
|
-
});
|
|
750
|
+
Object.defineProperty(HttpApplication, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "app: App", typeOnly: "App" }, { unresolved: "discovered: readonly DiscoveredRoute[]" }, { unresolved: "options: HttpOptions" }, { unresolved: "root: ModuleRef", typeOnly: "ModuleRef" }, { unresolved: "websocket?: WebSocketRuntime", typeOnly: "WebSocketRuntime" }] });
|
|
1629
751
|
|
|
1630
752
|
// src/server/factory.ts
|
|
1631
753
|
class HttpModule {
|
|
@@ -1655,14 +777,14 @@ class HttpFactory {
|
|
|
1655
777
|
exports: providers.map((entry) => typeof entry === "function" ? entry : entry.token)
|
|
1656
778
|
};
|
|
1657
779
|
const app = await AppFactory.create(scope, options.overrides ? { overrides: options.overrides } : {});
|
|
1658
|
-
const modules =
|
|
780
|
+
const modules = collectModules(scope);
|
|
1659
781
|
const discovered = [];
|
|
1660
782
|
for (const module of modules) {
|
|
1661
783
|
const moduleMiddleware = module.options.middleware ?? [];
|
|
1662
|
-
for (const controller of
|
|
784
|
+
for (const controller of readControllers(module)) {
|
|
1663
785
|
const routes = discoverRoutes(app.get(controller, module.ref));
|
|
1664
786
|
if (routes.length === 0) {
|
|
1665
|
-
throw new
|
|
787
|
+
throw new AppError4(`${controller.name} is registered as a controller but declares no routes. ` + "Add a @Get/@Post/... method, or move it to providers.");
|
|
1666
788
|
}
|
|
1667
789
|
discovered.push(...routes.map((route) => ({
|
|
1668
790
|
...route,
|
|
@@ -1689,29 +811,6 @@ class HttpFactory {
|
|
|
1689
811
|
}
|
|
1690
812
|
// src/static/files.ts
|
|
1691
813
|
import { join, normalize, resolve } from "path";
|
|
1692
|
-
|
|
1693
|
-
// src/static/options.ts
|
|
1694
|
-
class StaticOptions {
|
|
1695
|
-
root;
|
|
1696
|
-
path;
|
|
1697
|
-
maxAge;
|
|
1698
|
-
immutable;
|
|
1699
|
-
constructor(init) {
|
|
1700
|
-
this.root = init.root;
|
|
1701
|
-
this.path = normalizePrefix(init.path ?? "/");
|
|
1702
|
-
this.maxAge = init.maxAge ?? 60;
|
|
1703
|
-
this.immutable = init.immutable ?? (() => false);
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
Object.defineProperty(StaticOptions, Symbol.for("dunx.deps"), {
|
|
1707
|
-
value: () => [{ unresolved: "init: StaticOptionsInit" }]
|
|
1708
|
-
});
|
|
1709
|
-
var normalizePrefix = (path) => {
|
|
1710
|
-
const trimmed = path.split("/").filter(Boolean).join("/");
|
|
1711
|
-
return trimmed === "" ? "/" : `/${trimmed}`;
|
|
1712
|
-
};
|
|
1713
|
-
|
|
1714
|
-
// src/static/files.ts
|
|
1715
814
|
class StaticFiles {
|
|
1716
815
|
#options;
|
|
1717
816
|
#root;
|
|
@@ -1763,9 +862,7 @@ class StaticFiles {
|
|
|
1763
862
|
});
|
|
1764
863
|
}
|
|
1765
864
|
}
|
|
1766
|
-
Object.defineProperty(StaticFiles, Symbol.for("dunx.deps"), {
|
|
1767
|
-
value: () => [StaticOptions]
|
|
1768
|
-
});
|
|
865
|
+
Object.defineProperty(StaticFiles, Symbol.for("dunx.deps"), { value: () => [StaticOptions] });
|
|
1769
866
|
// src/static/module.ts
|
|
1770
867
|
import {
|
|
1771
868
|
Module,
|
|
@@ -1810,6 +907,177 @@ StaticModule = __decorateElement(_init, 0, "StaticModule", _dec, StaticModule);
|
|
|
1810
907
|
__runInitializers(_init, 1, StaticModule);
|
|
1811
908
|
__decoratorMetadata(_init, StaticModule);
|
|
1812
909
|
let _StaticModule = StaticModule;
|
|
910
|
+
// src/compression/compression.ts
|
|
911
|
+
var BODYLESS = new Set([204, 205, 304]);
|
|
912
|
+
var BUFFER_LIMIT = 1024 * 1024;
|
|
913
|
+
var buffer = async (body, limit) => {
|
|
914
|
+
const reader = body.getReader();
|
|
915
|
+
const chunks = [];
|
|
916
|
+
let size = 0;
|
|
917
|
+
for (;; ) {
|
|
918
|
+
const { done, value } = await reader.read();
|
|
919
|
+
if (done)
|
|
920
|
+
break;
|
|
921
|
+
chunks.push(value);
|
|
922
|
+
size += value.byteLength;
|
|
923
|
+
if (size > limit) {
|
|
924
|
+
return {
|
|
925
|
+
rest: new ReadableStream({
|
|
926
|
+
start: (controller) => {
|
|
927
|
+
for (const chunk of chunks)
|
|
928
|
+
controller.enqueue(chunk);
|
|
929
|
+
},
|
|
930
|
+
pull: async (controller) => {
|
|
931
|
+
const next = await reader.read();
|
|
932
|
+
if (next.done)
|
|
933
|
+
controller.close();
|
|
934
|
+
else
|
|
935
|
+
controller.enqueue(next.value);
|
|
936
|
+
},
|
|
937
|
+
cancel: (reason) => reader.cancel(reason)
|
|
938
|
+
})
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
const bytes = new Uint8Array(size);
|
|
943
|
+
let offset = 0;
|
|
944
|
+
for (const chunk of chunks) {
|
|
945
|
+
bytes.set(chunk, offset);
|
|
946
|
+
offset += chunk.byteLength;
|
|
947
|
+
}
|
|
948
|
+
return { bytes };
|
|
949
|
+
};
|
|
950
|
+
var declaredLength = (headers) => {
|
|
951
|
+
const raw = headers.get("content-length");
|
|
952
|
+
if (raw === null)
|
|
953
|
+
return;
|
|
954
|
+
const length = Number.parseInt(raw, 10);
|
|
955
|
+
return Number.isFinite(length) ? length : undefined;
|
|
956
|
+
};
|
|
957
|
+
var weakenETag = (headers) => {
|
|
958
|
+
const etag = headers.get("etag");
|
|
959
|
+
if (etag !== null && !etag.startsWith("W/"))
|
|
960
|
+
headers.set("etag", `W/${etag}`);
|
|
961
|
+
};
|
|
962
|
+
var varyOnEncoding = (headers) => {
|
|
963
|
+
const existing = headers.get("vary");
|
|
964
|
+
if (existing === null) {
|
|
965
|
+
headers.set("vary", "accept-encoding");
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
if (existing.trim() === "*")
|
|
969
|
+
return;
|
|
970
|
+
const listed = existing.split(",").some((field) => field.trim().toLowerCase() === "accept-encoding");
|
|
971
|
+
if (!listed)
|
|
972
|
+
headers.set("vary", `${existing}, accept-encoding`);
|
|
973
|
+
};
|
|
974
|
+
var encodeSync = (encoding, data) => {
|
|
975
|
+
switch (encoding) {
|
|
976
|
+
case CompressionEncoding.ZSTD:
|
|
977
|
+
return Bun.zstdCompressSync(data);
|
|
978
|
+
case CompressionEncoding.GZIP:
|
|
979
|
+
return Bun.gzipSync(data);
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
|
|
983
|
+
class Compression {
|
|
984
|
+
#options;
|
|
985
|
+
constructor(options) {
|
|
986
|
+
this.#options = options;
|
|
987
|
+
}
|
|
988
|
+
#considers(res) {
|
|
989
|
+
if (BODYLESS.has(res.status) || res.status === 206)
|
|
990
|
+
return false;
|
|
991
|
+
if (res.headers.has("content-encoding"))
|
|
992
|
+
return false;
|
|
993
|
+
if (res.headers.get("cache-control")?.includes("no-transform") === true) {
|
|
994
|
+
return false;
|
|
995
|
+
}
|
|
996
|
+
return this.#options.filter(res.headers.get("content-type"));
|
|
997
|
+
}
|
|
998
|
+
async handle(req, _ctx, next) {
|
|
999
|
+
const res = await next();
|
|
1000
|
+
const body = res.body;
|
|
1001
|
+
if (body === null || !this.#considers(res))
|
|
1002
|
+
return res;
|
|
1003
|
+
varyOnEncoding(res.headers);
|
|
1004
|
+
const encoding = negotiate(req.headers.get("accept-encoding"), this.#options.encodings);
|
|
1005
|
+
if (encoding === undefined)
|
|
1006
|
+
return res;
|
|
1007
|
+
const declared = declaredLength(res.headers);
|
|
1008
|
+
if (declared !== undefined && declared < this.#options.threshold)
|
|
1009
|
+
return res;
|
|
1010
|
+
const headers = new Headers(res.headers);
|
|
1011
|
+
headers.set("content-encoding", encoding);
|
|
1012
|
+
weakenETag(headers);
|
|
1013
|
+
const source = declared !== undefined && declared > BUFFER_LIMIT ? { rest: body } : await buffer(body, BUFFER_LIMIT);
|
|
1014
|
+
if ("rest" in source) {
|
|
1015
|
+
headers.delete("content-length");
|
|
1016
|
+
return new Response(source.rest.pipeThrough(new CompressionStream(encoding)), { status: res.status, statusText: res.statusText, headers });
|
|
1017
|
+
}
|
|
1018
|
+
if (source.bytes.byteLength < this.#options.threshold) {
|
|
1019
|
+
const passthrough = new Headers(res.headers);
|
|
1020
|
+
passthrough.set("content-length", String(source.bytes.byteLength));
|
|
1021
|
+
return new Response(source.bytes, {
|
|
1022
|
+
status: res.status,
|
|
1023
|
+
statusText: res.statusText,
|
|
1024
|
+
headers: passthrough
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
const encoded = encodeSync(encoding, source.bytes);
|
|
1028
|
+
headers.set("content-length", String(encoded.byteLength));
|
|
1029
|
+
return new Response(encoded, {
|
|
1030
|
+
status: res.status,
|
|
1031
|
+
statusText: res.statusText,
|
|
1032
|
+
headers
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
Object.defineProperty(Compression, Symbol.for("dunx.deps"), { value: () => [CompressionOptions] });
|
|
1037
|
+
// src/compression/module.ts
|
|
1038
|
+
import {
|
|
1039
|
+
Module as Module2,
|
|
1040
|
+
provide as provide3
|
|
1041
|
+
} from "@dunx/core";
|
|
1042
|
+
var middleware = () => provide3(Compression, {
|
|
1043
|
+
useFactory: (options) => new Compression(options),
|
|
1044
|
+
inject: [CompressionOptions]
|
|
1045
|
+
});
|
|
1046
|
+
var _dec = [
|
|
1047
|
+
Module2({})
|
|
1048
|
+
];
|
|
1049
|
+
var _init = __decoratorStart(undefined);
|
|
1050
|
+
|
|
1051
|
+
class CompressionModule {
|
|
1052
|
+
static forRoot(init = {}) {
|
|
1053
|
+
return {
|
|
1054
|
+
module: CompressionModule,
|
|
1055
|
+
exports: [CompressionOptions, Compression],
|
|
1056
|
+
providers: [
|
|
1057
|
+
provide3(CompressionOptions, { useValue: new CompressionOptions(init) }),
|
|
1058
|
+
middleware()
|
|
1059
|
+
]
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
static forRootAsync(config) {
|
|
1063
|
+
return {
|
|
1064
|
+
module: CompressionModule,
|
|
1065
|
+
...config.imports && { imports: config.imports },
|
|
1066
|
+
exports: [CompressionOptions, Compression],
|
|
1067
|
+
providers: [
|
|
1068
|
+
provide3(CompressionOptions, {
|
|
1069
|
+
useFactory: async (...deps) => new CompressionOptions(await config.useFactory(...deps)),
|
|
1070
|
+
inject: config.inject ?? []
|
|
1071
|
+
}),
|
|
1072
|
+
middleware()
|
|
1073
|
+
]
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
CompressionModule = __decorateElement(_init, 0, "CompressionModule", _dec, CompressionModule);
|
|
1078
|
+
__runInitializers(_init, 1, CompressionModule);
|
|
1079
|
+
__decoratorMetadata(_init, CompressionModule);
|
|
1080
|
+
let _CompressionModule = CompressionModule;
|
|
1813
1081
|
// src/throttle/decorators.ts
|
|
1814
1082
|
var THROTTLE = metaKey("throttle");
|
|
1815
1083
|
var SKIP_THROTTLE = metaKey("skip-throttle");
|
|
@@ -1819,7 +1087,7 @@ var SkipThrottle = () => meta(SKIP_THROTTLE, true);
|
|
|
1819
1087
|
import { Logger as Logger5 } from "@dunx/core";
|
|
1820
1088
|
|
|
1821
1089
|
// src/throttle/options.ts
|
|
1822
|
-
import { AppError as
|
|
1090
|
+
import { AppError as AppError5 } from "@dunx/core";
|
|
1823
1091
|
|
|
1824
1092
|
class ThrottleOptions {
|
|
1825
1093
|
limit;
|
|
@@ -1830,13 +1098,13 @@ class ThrottleOptions {
|
|
|
1830
1098
|
store;
|
|
1831
1099
|
constructor(init) {
|
|
1832
1100
|
if (init.prefix.trim() === "") {
|
|
1833
|
-
throw new
|
|
1101
|
+
throw new AppError5("ThrottleModule needs a prefix naming this application, and it has no " + "default: two apps sharing one Redis with one throttle namespace each " + "spend the other's budget. Pass something like { prefix: 'orders-api' }.");
|
|
1834
1102
|
}
|
|
1835
1103
|
if (!Number.isInteger(init.limit) || init.limit < 1) {
|
|
1836
|
-
throw new
|
|
1104
|
+
throw new AppError5(`ThrottleModule needs a limit of at least 1; got ${init.limit}.`);
|
|
1837
1105
|
}
|
|
1838
1106
|
if (!Number.isInteger(init.windowSeconds) || init.windowSeconds < 1) {
|
|
1839
|
-
throw new
|
|
1107
|
+
throw new AppError5("ThrottleModule needs a windowSeconds of at least 1; got " + `${init.windowSeconds}.`);
|
|
1840
1108
|
}
|
|
1841
1109
|
this.limit = init.limit;
|
|
1842
1110
|
this.windowSeconds = init.windowSeconds;
|
|
@@ -1846,17 +1114,15 @@ class ThrottleOptions {
|
|
|
1846
1114
|
this.store = init.store;
|
|
1847
1115
|
}
|
|
1848
1116
|
}
|
|
1849
|
-
Object.defineProperty(ThrottleOptions, Symbol.for("dunx.deps"), {
|
|
1850
|
-
value: () => [{ unresolved: "init: ThrottleOptionsInit" }]
|
|
1851
|
-
});
|
|
1117
|
+
Object.defineProperty(ThrottleOptions, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "init: ThrottleOptionsInit" }] });
|
|
1852
1118
|
|
|
1853
1119
|
// src/throttle/store.ts
|
|
1854
|
-
import { AppError as
|
|
1120
|
+
import { AppError as AppError6 } from "@dunx/core";
|
|
1855
1121
|
|
|
1856
1122
|
class ThrottleStore {
|
|
1857
1123
|
constructor() {
|
|
1858
1124
|
if (new.target === ThrottleStore) {
|
|
1859
|
-
throw new
|
|
1125
|
+
throw new AppError6("ThrottleStore is a contract, not an implementation. Bind one with " + "ThrottleModule.forRoot({ store: new RedisThrottleStore(redis) }), or " + "leave it out for the in-process MemoryThrottleStore.");
|
|
1860
1126
|
}
|
|
1861
1127
|
}
|
|
1862
1128
|
}
|
|
@@ -1878,9 +1144,7 @@ class RedisThrottleStore extends ThrottleStore {
|
|
|
1878
1144
|
return left > 0 ? left : undefined;
|
|
1879
1145
|
}
|
|
1880
1146
|
}
|
|
1881
|
-
Object.defineProperty(RedisThrottleStore, Symbol.for("dunx.deps"), {
|
|
1882
|
-
value: () => [{ unresolved: "private readonly redis: ThrottleRedis" }]
|
|
1883
|
-
});
|
|
1147
|
+
Object.defineProperty(RedisThrottleStore, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "private readonly redis: ThrottleRedis" }] });
|
|
1884
1148
|
|
|
1885
1149
|
class MemoryThrottleStore extends ThrottleStore {
|
|
1886
1150
|
#windows = new Map;
|
|
@@ -1917,9 +1181,7 @@ class MemoryThrottleStore extends ThrottleStore {
|
|
|
1917
1181
|
this.#windows.clear();
|
|
1918
1182
|
}
|
|
1919
1183
|
}
|
|
1920
|
-
Object.defineProperty(MemoryThrottleStore, Symbol.for("dunx.deps"), {
|
|
1921
|
-
value: () => [{ unresolved: "maxKeys = 10_000" }]
|
|
1922
|
-
});
|
|
1184
|
+
Object.defineProperty(MemoryThrottleStore, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "maxKeys = 10_000", optional: true }] });
|
|
1923
1185
|
|
|
1924
1186
|
// src/throttle/guard.ts
|
|
1925
1187
|
class ThrottleGuard {
|
|
@@ -1989,26 +1251,24 @@ class ThrottleGuard {
|
|
|
1989
1251
|
this.logger.warn("The rate limiter is unreachable, so requests are not being counted.", { reason: error.message });
|
|
1990
1252
|
}
|
|
1991
1253
|
}
|
|
1992
|
-
Object.defineProperty(ThrottleGuard, Symbol.for("dunx.deps"), {
|
|
1993
|
-
value: () => [ThrottleOptions, ThrottleStore, ClientAddress, Logger5]
|
|
1994
|
-
});
|
|
1254
|
+
Object.defineProperty(ThrottleGuard, Symbol.for("dunx.deps"), { value: () => [ThrottleOptions, ThrottleStore, ClientAddress, Logger5] });
|
|
1995
1255
|
// src/throttle/module.ts
|
|
1996
1256
|
import {
|
|
1997
1257
|
Logger as Logger6,
|
|
1998
|
-
Module as
|
|
1999
|
-
provide as
|
|
1258
|
+
Module as Module3,
|
|
1259
|
+
provide as provide4
|
|
2000
1260
|
} from "@dunx/core";
|
|
2001
1261
|
var EXPORTS = [ThrottleOptions, ThrottleStore, ThrottleGuard];
|
|
2002
|
-
var guard = () =>
|
|
1262
|
+
var guard = () => provide4(ThrottleGuard, {
|
|
2003
1263
|
useFactory: (options, store, address, logger) => new ThrottleGuard(options, store, address, logger),
|
|
2004
1264
|
inject: [ThrottleOptions, ThrottleStore, ClientAddress, Logger6]
|
|
2005
1265
|
});
|
|
2006
|
-
var store = () =>
|
|
1266
|
+
var store = () => provide4(ThrottleStore, {
|
|
2007
1267
|
useFactory: (options) => options.store ?? new MemoryThrottleStore,
|
|
2008
1268
|
inject: [ThrottleOptions]
|
|
2009
1269
|
});
|
|
2010
1270
|
var _dec = [
|
|
2011
|
-
|
|
1271
|
+
Module3({})
|
|
2012
1272
|
];
|
|
2013
1273
|
var _init = __decoratorStart(undefined);
|
|
2014
1274
|
|
|
@@ -2019,7 +1279,7 @@ class ThrottleModule {
|
|
|
2019
1279
|
global: true,
|
|
2020
1280
|
exports: EXPORTS,
|
|
2021
1281
|
providers: [
|
|
2022
|
-
|
|
1282
|
+
provide4(ThrottleOptions, { useValue: new ThrottleOptions(init) }),
|
|
2023
1283
|
store(),
|
|
2024
1284
|
guard()
|
|
2025
1285
|
]
|
|
@@ -2032,7 +1292,7 @@ class ThrottleModule {
|
|
|
2032
1292
|
...config.imports && { imports: config.imports },
|
|
2033
1293
|
exports: EXPORTS,
|
|
2034
1294
|
providers: [
|
|
2035
|
-
|
|
1295
|
+
provide4(ThrottleOptions, {
|
|
2036
1296
|
useFactory: async (...deps) => new ThrottleOptions(await config.useFactory(...deps)),
|
|
2037
1297
|
inject: config.inject ?? []
|
|
2038
1298
|
}),
|
|
@@ -2065,99 +1325,6 @@ var OnMessage = (event) => (value) => {
|
|
|
2065
1325
|
markHandler(value, { kind: HandlerKind.MESSAGE, event });
|
|
2066
1326
|
return value;
|
|
2067
1327
|
};
|
|
2068
|
-
// src/ws/redis-relay.ts
|
|
2069
|
-
import { AppError as AppError11 } from "@dunx/core";
|
|
2070
|
-
var PROTOCOLS = [
|
|
2071
|
-
"redis:",
|
|
2072
|
-
"rediss:",
|
|
2073
|
-
"valkey:",
|
|
2074
|
-
"valkeys:",
|
|
2075
|
-
"redis+tls:",
|
|
2076
|
-
"redis+unix:",
|
|
2077
|
-
"redis+tls+unix:"
|
|
2078
|
-
];
|
|
2079
|
-
var defaultRelayUrl = () => process.env["VALKEY_URL"] ?? process.env["REDIS_URL"] ?? "redis://localhost:6379";
|
|
2080
|
-
var assertUrl = (url) => {
|
|
2081
|
-
let parsed;
|
|
2082
|
-
try {
|
|
2083
|
-
parsed = new URL(url);
|
|
2084
|
-
} catch {
|
|
2085
|
-
throw new AppError11(`${JSON.stringify(url)} is not a valid URL for the websocket relay. ` + "Expected something like redis://localhost:6379.");
|
|
2086
|
-
}
|
|
2087
|
-
if (!PROTOCOLS.includes(parsed.protocol)) {
|
|
2088
|
-
throw new AppError11(`Unsupported protocol ${JSON.stringify(parsed.protocol)} in ` + `${JSON.stringify(url)}. Expected one of ${PROTOCOLS.join(", ")}.`);
|
|
2089
|
-
}
|
|
2090
|
-
return url;
|
|
2091
|
-
};
|
|
2092
|
-
|
|
2093
|
-
class RedisRelay {
|
|
2094
|
-
#url;
|
|
2095
|
-
#options;
|
|
2096
|
-
#pub;
|
|
2097
|
-
#sub;
|
|
2098
|
-
#channel;
|
|
2099
|
-
constructor(options = {}) {
|
|
2100
|
-
this.#url = assertUrl(options.url ?? defaultRelayUrl());
|
|
2101
|
-
this.#options = {
|
|
2102
|
-
maxRetries: options.maxRetries ?? 0,
|
|
2103
|
-
...options.connectionTimeout !== undefined && {
|
|
2104
|
-
connectionTimeout: options.connectionTimeout
|
|
2105
|
-
},
|
|
2106
|
-
...options.tls !== undefined && { tls: options.tls }
|
|
2107
|
-
};
|
|
2108
|
-
}
|
|
2109
|
-
get url() {
|
|
2110
|
-
const parsed = new URL(this.#url);
|
|
2111
|
-
if (parsed.password)
|
|
2112
|
-
parsed.password = "***";
|
|
2113
|
-
return parsed.toString();
|
|
2114
|
-
}
|
|
2115
|
-
async publish(channel, message) {
|
|
2116
|
-
const client = this.#pub ??= new Bun.RedisClient(this.#url, this.#options);
|
|
2117
|
-
try {
|
|
2118
|
-
return await client.publish(channel, message);
|
|
2119
|
-
} catch (error) {
|
|
2120
|
-
if (this.#pub === client) {
|
|
2121
|
-
this.#pub = undefined;
|
|
2122
|
-
client.close();
|
|
2123
|
-
}
|
|
2124
|
-
throw error;
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
async subscribe(channel, listener) {
|
|
2128
|
-
const client = this.#sub ??= new Bun.RedisClient(this.#url, this.#options);
|
|
2129
|
-
try {
|
|
2130
|
-
await client.connect();
|
|
2131
|
-
await client.subscribe(channel, listener);
|
|
2132
|
-
this.#channel = channel;
|
|
2133
|
-
} catch (error) {
|
|
2134
|
-
if (this.#sub === client) {
|
|
2135
|
-
this.#sub = undefined;
|
|
2136
|
-
client.close();
|
|
2137
|
-
}
|
|
2138
|
-
throw error;
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
async close() {
|
|
2142
|
-
const sub = this.#sub;
|
|
2143
|
-
const channel = this.#channel;
|
|
2144
|
-
this.#pub?.close();
|
|
2145
|
-
this.#pub = undefined;
|
|
2146
|
-
this.#sub = undefined;
|
|
2147
|
-
this.#channel = undefined;
|
|
2148
|
-
if (!sub)
|
|
2149
|
-
return;
|
|
2150
|
-
if (channel !== undefined) {
|
|
2151
|
-
try {
|
|
2152
|
-
await sub.unsubscribe(channel);
|
|
2153
|
-
} catch {}
|
|
2154
|
-
}
|
|
2155
|
-
sub.close();
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
Object.defineProperty(RedisRelay, Symbol.for("dunx.deps"), {
|
|
2159
|
-
value: () => [{ unresolved: "options: RedisRelayOptions = {}" }]
|
|
2160
|
-
});
|
|
2161
1328
|
// src/health/contracts.ts
|
|
2162
1329
|
class HealthIndicator {
|
|
2163
1330
|
critical = true;
|
|
@@ -2168,205 +1335,6 @@ class PingProbe {
|
|
|
2168
1335
|
|
|
2169
1336
|
class QueryProbe {
|
|
2170
1337
|
}
|
|
2171
|
-
// src/health/controller.ts
|
|
2172
|
-
import { inject } from "@dunx/core";
|
|
2173
|
-
|
|
2174
|
-
// src/health/report-schema.ts
|
|
2175
|
-
var state = {
|
|
2176
|
-
type: "string",
|
|
2177
|
-
enum: ["up", "down", "unknown"],
|
|
2178
|
-
description: "`unknown` is not `down`: a probe that timed out has told you nothing."
|
|
2179
|
-
};
|
|
2180
|
-
var HEALTH_REPORT_SCHEMA = Object.freeze({
|
|
2181
|
-
$id: "HealthReport",
|
|
2182
|
-
type: "object",
|
|
2183
|
-
description: "What the probe found. `up` answers 200 and anything else answers 503.",
|
|
2184
|
-
properties: {
|
|
2185
|
-
status: state,
|
|
2186
|
-
draining: {
|
|
2187
|
-
type: "boolean",
|
|
2188
|
-
description: "The process is shutting down, or something holds it out."
|
|
2189
|
-
},
|
|
2190
|
-
uptimeMs: {
|
|
2191
|
-
type: "integer",
|
|
2192
|
-
description: "Measured on a monotonic clock, so it never goes backwards."
|
|
2193
|
-
},
|
|
2194
|
-
checks: {
|
|
2195
|
-
type: "array",
|
|
2196
|
-
items: {
|
|
2197
|
-
type: "object",
|
|
2198
|
-
properties: {
|
|
2199
|
-
name: { type: "string" },
|
|
2200
|
-
state,
|
|
2201
|
-
critical: {
|
|
2202
|
-
type: "boolean",
|
|
2203
|
-
description: "A failure here sheds traffic. Memory and disk do not."
|
|
2204
|
-
},
|
|
2205
|
-
ms: { type: "integer", description: "How long the check took." },
|
|
2206
|
-
detail: {
|
|
2207
|
-
type: "string",
|
|
2208
|
-
description: "A latency, a version, or a failure message."
|
|
2209
|
-
}
|
|
2210
|
-
},
|
|
2211
|
-
required: ["name", "state", "critical", "ms"]
|
|
2212
|
-
}
|
|
2213
|
-
}
|
|
2214
|
-
},
|
|
2215
|
-
required: ["status", "draining", "uptimeMs", "checks"]
|
|
2216
|
-
});
|
|
2217
|
-
|
|
2218
|
-
// src/health/registry.ts
|
|
2219
|
-
var bounded = async (indicator, timeoutMs) => {
|
|
2220
|
-
let timer;
|
|
2221
|
-
const timeout = new Promise((resolve2) => {
|
|
2222
|
-
timer = setTimeout(() => resolve2({ state: "unknown", detail: `no answer in ${timeoutMs} ms` }), timeoutMs);
|
|
2223
|
-
timer.unref?.();
|
|
2224
|
-
});
|
|
2225
|
-
try {
|
|
2226
|
-
return await Promise.race([
|
|
2227
|
-
Promise.resolve().then(() => indicator.check()).catch((error) => ({
|
|
2228
|
-
state: "down",
|
|
2229
|
-
detail: error instanceof Error ? error.message : String(error)
|
|
2230
|
-
})),
|
|
2231
|
-
timeout
|
|
2232
|
-
]);
|
|
2233
|
-
} finally {
|
|
2234
|
-
if (timer)
|
|
2235
|
-
clearTimeout(timer);
|
|
2236
|
-
}
|
|
2237
|
-
};
|
|
2238
|
-
var worst = (checks) => {
|
|
2239
|
-
const critical = checks.filter((check) => check.critical);
|
|
2240
|
-
if (critical.some((check) => check.state === "down"))
|
|
2241
|
-
return "down";
|
|
2242
|
-
if (critical.some((check) => check.state === "unknown"))
|
|
2243
|
-
return "unknown";
|
|
2244
|
-
return "up";
|
|
2245
|
-
};
|
|
2246
|
-
|
|
2247
|
-
class HealthOptions {
|
|
2248
|
-
liveness;
|
|
2249
|
-
readiness;
|
|
2250
|
-
timeoutMs;
|
|
2251
|
-
routes;
|
|
2252
|
-
documented;
|
|
2253
|
-
drainDelayMs;
|
|
2254
|
-
constructor(init = {}) {
|
|
2255
|
-
this.liveness = init.liveness ?? [];
|
|
2256
|
-
this.readiness = init.readiness ?? [];
|
|
2257
|
-
this.timeoutMs = init.timeoutMs ?? 2000;
|
|
2258
|
-
this.routes = init.routes ?? true;
|
|
2259
|
-
this.documented = init.documented ?? true;
|
|
2260
|
-
this.drainDelayMs = Math.max(0, init.drainDelayMs ?? 0);
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
Object.defineProperty(HealthOptions, Symbol.for("dunx.deps"), {
|
|
2264
|
-
value: () => [{ unresolved: "init: HealthOptionsInit = {}" }]
|
|
2265
|
-
});
|
|
2266
|
-
|
|
2267
|
-
class HealthRegistry {
|
|
2268
|
-
options;
|
|
2269
|
-
readiness_;
|
|
2270
|
-
#startedAt = performance.now();
|
|
2271
|
-
constructor(options, readiness_) {
|
|
2272
|
-
this.options = options;
|
|
2273
|
-
this.readiness_ = readiness_;
|
|
2274
|
-
}
|
|
2275
|
-
async report(indicators) {
|
|
2276
|
-
const checks = await Promise.all(indicators.map(async (indicator) => {
|
|
2277
|
-
const started = performance.now();
|
|
2278
|
-
const result = await bounded(indicator, this.options.timeoutMs);
|
|
2279
|
-
return {
|
|
2280
|
-
name: indicator.name,
|
|
2281
|
-
state: result.state,
|
|
2282
|
-
critical: indicator.critical,
|
|
2283
|
-
ms: Math.round(performance.now() - started),
|
|
2284
|
-
...result.detail === undefined ? {} : { detail: result.detail }
|
|
2285
|
-
};
|
|
2286
|
-
}));
|
|
2287
|
-
return {
|
|
2288
|
-
status: worst(checks),
|
|
2289
|
-
draining: this.readiness_.draining,
|
|
2290
|
-
uptimeMs: Math.round(performance.now() - this.#startedAt),
|
|
2291
|
-
checks
|
|
2292
|
-
};
|
|
2293
|
-
}
|
|
2294
|
-
liveness() {
|
|
2295
|
-
return this.report(this.options.liveness);
|
|
2296
|
-
}
|
|
2297
|
-
async readiness() {
|
|
2298
|
-
const report = await this.report(this.options.readiness);
|
|
2299
|
-
if (!this.readiness_.draining)
|
|
2300
|
-
return report;
|
|
2301
|
-
return {
|
|
2302
|
-
...report,
|
|
2303
|
-
status: "down",
|
|
2304
|
-
checks: [
|
|
2305
|
-
{
|
|
2306
|
-
name: "readiness",
|
|
2307
|
-
state: "down",
|
|
2308
|
-
critical: true,
|
|
2309
|
-
ms: 0,
|
|
2310
|
-
detail: this.readiness_.reason ?? "not accepting traffic"
|
|
2311
|
-
},
|
|
2312
|
-
...report.checks
|
|
2313
|
-
]
|
|
2314
|
-
};
|
|
2315
|
-
}
|
|
2316
|
-
}
|
|
2317
|
-
Object.defineProperty(HealthRegistry, Symbol.for("dunx.deps"), {
|
|
2318
|
-
value: () => [HealthOptions, { unresolved: "private readonly readiness_: Readiness", typeOnly: "Readiness" }]
|
|
2319
|
-
});
|
|
2320
|
-
|
|
2321
|
-
// src/health/controller.ts
|
|
2322
|
-
var probeResponses = {
|
|
2323
|
-
response: { 200: HEALTH_REPORT_SCHEMA, 503: HEALTH_REPORT_SCHEMA }
|
|
2324
|
-
};
|
|
2325
|
-
var answer = (report) => Response.json(report, { status: report.status === "up" ? 200 : 503 });
|
|
2326
|
-
var _dec = [
|
|
2327
|
-
Controller("health")
|
|
2328
|
-
];
|
|
2329
|
-
var _dec2 = [
|
|
2330
|
-
Public(),
|
|
2331
|
-
Get("/live", probeResponses)
|
|
2332
|
-
];
|
|
2333
|
-
var _dec3 = [
|
|
2334
|
-
Public(),
|
|
2335
|
-
Get("/ready", probeResponses)
|
|
2336
|
-
];
|
|
2337
|
-
var _health = new WeakMap;
|
|
2338
|
-
var _init = __decoratorStart(undefined);
|
|
2339
|
-
|
|
2340
|
-
class HealthController {
|
|
2341
|
-
constructor() {
|
|
2342
|
-
__privateAdd(this, _health, inject(HealthRegistry));
|
|
2343
|
-
__runInitializers(_init, 5, this);
|
|
2344
|
-
}
|
|
2345
|
-
async live() {
|
|
2346
|
-
return answer(await __privateGet(this, _health).liveness());
|
|
2347
|
-
}
|
|
2348
|
-
async ready() {
|
|
2349
|
-
return answer(await __privateGet(this, _health).readiness());
|
|
2350
|
-
}
|
|
2351
|
-
}
|
|
2352
|
-
__decorateElement(_init, 1, "live", _dec2, HealthController);
|
|
2353
|
-
__decorateElement(_init, 1, "ready", _dec3, HealthController);
|
|
2354
|
-
HealthController = __decorateElement(_init, 0, "HealthController", _dec, HealthController);
|
|
2355
|
-
__runInitializers(_init, 1, HealthController);
|
|
2356
|
-
__decoratorMetadata(_init, HealthController);
|
|
2357
|
-
let _HealthController = HealthController;
|
|
2358
|
-
var _dec = [
|
|
2359
|
-
ApiHidden()
|
|
2360
|
-
];
|
|
2361
|
-
var _base = HealthController;
|
|
2362
|
-
var _init = __decoratorStart(_base);
|
|
2363
|
-
|
|
2364
|
-
class HiddenHealthController extends _base {
|
|
2365
|
-
}
|
|
2366
|
-
HiddenHealthController = __decorateElement(_init, 0, "HiddenHealthController", _dec, HiddenHealthController);
|
|
2367
|
-
__runInitializers(_init, 1, HiddenHealthController);
|
|
2368
|
-
__decoratorMetadata(_init, HiddenHealthController);
|
|
2369
|
-
let _HiddenHealthController = HiddenHealthController;
|
|
2370
1338
|
// src/health/indicators.ts
|
|
2371
1339
|
import { statfs } from "fs/promises";
|
|
2372
1340
|
var ms = (started) => Math.round(performance.now() - started);
|
|
@@ -2384,9 +1352,7 @@ class RedisIndicator extends HealthIndicator {
|
|
|
2384
1352
|
return { state: "up", detail: `${ms(started)} ms` };
|
|
2385
1353
|
}
|
|
2386
1354
|
}
|
|
2387
|
-
Object.defineProperty(RedisIndicator, Symbol.for("dunx.deps"), {
|
|
2388
|
-
value: () => [{ unresolved: "private readonly redis: PingProbe", typeOnly: "PingProbe" }]
|
|
2389
|
-
});
|
|
1355
|
+
Object.defineProperty(RedisIndicator, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "private readonly redis: PingProbe", typeOnly: "PingProbe" }] });
|
|
2390
1356
|
|
|
2391
1357
|
class DatabaseIndicator extends HealthIndicator {
|
|
2392
1358
|
db;
|
|
@@ -2401,9 +1367,7 @@ class DatabaseIndicator extends HealthIndicator {
|
|
|
2401
1367
|
return { state: "up", detail: `${ms(started)} ms` };
|
|
2402
1368
|
}
|
|
2403
1369
|
}
|
|
2404
|
-
Object.defineProperty(DatabaseIndicator, Symbol.for("dunx.deps"), {
|
|
2405
|
-
value: () => [{ unresolved: "private readonly db: QueryProbe", typeOnly: "QueryProbe" }]
|
|
2406
|
-
});
|
|
1370
|
+
Object.defineProperty(DatabaseIndicator, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "private readonly db: QueryProbe", typeOnly: "QueryProbe" }] });
|
|
2407
1371
|
|
|
2408
1372
|
class MemoryOptions {
|
|
2409
1373
|
maxRssBytes;
|
|
@@ -2411,9 +1375,7 @@ class MemoryOptions {
|
|
|
2411
1375
|
this.maxRssBytes = init.maxRssBytes;
|
|
2412
1376
|
}
|
|
2413
1377
|
}
|
|
2414
|
-
Object.defineProperty(MemoryOptions, Symbol.for("dunx.deps"), {
|
|
2415
|
-
value: () => [{ unresolved: "init: MemoryOptionsInit" }]
|
|
2416
|
-
});
|
|
1378
|
+
Object.defineProperty(MemoryOptions, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "init: MemoryOptionsInit" }] });
|
|
2417
1379
|
var MIB = 1024 * 1024;
|
|
2418
1380
|
var mib = (bytes) => `${Math.round(bytes / MIB)} MiB`;
|
|
2419
1381
|
|
|
@@ -2431,9 +1393,7 @@ class MemoryIndicator extends HealthIndicator {
|
|
|
2431
1393
|
return rss > this.options.maxRssBytes ? { state: "down", detail } : { state: "up", detail };
|
|
2432
1394
|
}
|
|
2433
1395
|
}
|
|
2434
|
-
Object.defineProperty(MemoryIndicator, Symbol.for("dunx.deps"), {
|
|
2435
|
-
value: () => [MemoryOptions]
|
|
2436
|
-
});
|
|
1396
|
+
Object.defineProperty(MemoryIndicator, Symbol.for("dunx.deps"), { value: () => [MemoryOptions] });
|
|
2437
1397
|
|
|
2438
1398
|
class DiskOptions {
|
|
2439
1399
|
path;
|
|
@@ -2443,9 +1403,7 @@ class DiskOptions {
|
|
|
2443
1403
|
this.maxUsedFraction = init.maxUsedFraction;
|
|
2444
1404
|
}
|
|
2445
1405
|
}
|
|
2446
|
-
Object.defineProperty(DiskOptions, Symbol.for("dunx.deps"), {
|
|
2447
|
-
value: () => [{ unresolved: "init: DiskOptionsInit" }]
|
|
2448
|
-
});
|
|
1406
|
+
Object.defineProperty(DiskOptions, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "init: DiskOptionsInit" }] });
|
|
2449
1407
|
|
|
2450
1408
|
class DiskIndicator extends HealthIndicator {
|
|
2451
1409
|
options;
|
|
@@ -2466,13 +1424,11 @@ class DiskIndicator extends HealthIndicator {
|
|
|
2466
1424
|
return used > this.options.maxUsedFraction ? { state: "down", detail } : { state: "up", detail };
|
|
2467
1425
|
}
|
|
2468
1426
|
}
|
|
2469
|
-
Object.defineProperty(DiskIndicator, Symbol.for("dunx.deps"), {
|
|
2470
|
-
value: () => [DiskOptions]
|
|
2471
|
-
});
|
|
1427
|
+
Object.defineProperty(DiskIndicator, Symbol.for("dunx.deps"), { value: () => [DiskOptions] });
|
|
2472
1428
|
// src/health/module.ts
|
|
2473
1429
|
import {
|
|
2474
|
-
Module as
|
|
2475
|
-
provide as
|
|
1430
|
+
Module as Module4,
|
|
1431
|
+
provide as provide5
|
|
2476
1432
|
} from "@dunx/core";
|
|
2477
1433
|
|
|
2478
1434
|
// src/health/readiness.ts
|
|
@@ -2482,9 +1438,7 @@ class ReadinessOptions {
|
|
|
2482
1438
|
this.drainDelayMs = Math.max(0, init.drainDelayMs ?? 0);
|
|
2483
1439
|
}
|
|
2484
1440
|
}
|
|
2485
|
-
Object.defineProperty(ReadinessOptions, Symbol.for("dunx.deps"), {
|
|
2486
|
-
value: () => [{ unresolved: "init: ReadinessOptionsInit = {}" }]
|
|
2487
|
-
});
|
|
1441
|
+
Object.defineProperty(ReadinessOptions, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "init: ReadinessOptionsInit = {}", optional: true }] });
|
|
2488
1442
|
|
|
2489
1443
|
class Readiness {
|
|
2490
1444
|
options;
|
|
@@ -2512,22 +1466,20 @@ class Readiness {
|
|
|
2512
1466
|
}
|
|
2513
1467
|
}
|
|
2514
1468
|
}
|
|
2515
|
-
Object.defineProperty(Readiness, Symbol.for("dunx.deps"), {
|
|
2516
|
-
value: () => [ReadinessOptions]
|
|
2517
|
-
});
|
|
1469
|
+
Object.defineProperty(Readiness, Symbol.for("dunx.deps"), { value: () => [ReadinessOptions] });
|
|
2518
1470
|
|
|
2519
1471
|
// src/health/module.ts
|
|
2520
1472
|
var wiring = (options) => [
|
|
2521
1473
|
...options,
|
|
2522
|
-
|
|
1474
|
+
provide5(ReadinessOptions, {
|
|
2523
1475
|
useFactory: (opts) => new ReadinessOptions({ drainDelayMs: opts.drainDelayMs }),
|
|
2524
1476
|
inject: [HealthOptions]
|
|
2525
1477
|
}),
|
|
2526
|
-
|
|
1478
|
+
provide5(Readiness, {
|
|
2527
1479
|
useFactory: (opts) => new Readiness(opts),
|
|
2528
1480
|
inject: [ReadinessOptions]
|
|
2529
1481
|
}),
|
|
2530
|
-
|
|
1482
|
+
provide5(HealthRegistry, {
|
|
2531
1483
|
useFactory: (opts, readiness) => new HealthRegistry(opts, readiness),
|
|
2532
1484
|
inject: [HealthOptions, Readiness]
|
|
2533
1485
|
})
|
|
@@ -2535,7 +1487,7 @@ var wiring = (options) => [
|
|
|
2535
1487
|
var surface = [HealthOptions, HealthRegistry, Readiness];
|
|
2536
1488
|
var controllerFor = (documented) => documented ? HealthController : HiddenHealthController;
|
|
2537
1489
|
var _dec = [
|
|
2538
|
-
|
|
1490
|
+
Module4({})
|
|
2539
1491
|
];
|
|
2540
1492
|
var _init = __decoratorStart(undefined);
|
|
2541
1493
|
|
|
@@ -2546,7 +1498,7 @@ class HealthModule {
|
|
|
2546
1498
|
module: HealthModule,
|
|
2547
1499
|
...options.routes ? { controllers: [controllerFor(options.documented)] } : {},
|
|
2548
1500
|
exports: surface,
|
|
2549
|
-
providers: wiring([
|
|
1501
|
+
providers: wiring([provide5(HealthOptions, { useValue: options })])
|
|
2550
1502
|
};
|
|
2551
1503
|
}
|
|
2552
1504
|
static forRootAsync(config) {
|
|
@@ -2556,7 +1508,7 @@ class HealthModule {
|
|
|
2556
1508
|
...config.routes ?? true ? { controllers: [controllerFor(config.documented ?? true)] } : {},
|
|
2557
1509
|
exports: surface,
|
|
2558
1510
|
providers: wiring([
|
|
2559
|
-
|
|
1511
|
+
provide5(HealthOptions, {
|
|
2560
1512
|
useFactory: async (...deps) => new HealthOptions(await config.useFactory(...deps)),
|
|
2561
1513
|
inject: config.inject ?? []
|
|
2562
1514
|
})
|
|
@@ -2571,6 +1523,10 @@ let _HealthModule = HealthModule;
|
|
|
2571
1523
|
export {
|
|
2572
1524
|
ApiHidden,
|
|
2573
1525
|
ClientAddress,
|
|
1526
|
+
Compression,
|
|
1527
|
+
CompressionEncoding,
|
|
1528
|
+
CompressionModule,
|
|
1529
|
+
CompressionOptions,
|
|
2574
1530
|
Controller,
|
|
2575
1531
|
DEFAULT_RELAY_CHANNEL,
|
|
2576
1532
|
DatabaseIndicator,
|
|
@@ -2626,11 +1582,14 @@ export {
|
|
|
2626
1582
|
StaticModule,
|
|
2627
1583
|
StaticOptions,
|
|
2628
1584
|
THROTTLE,
|
|
1585
|
+
TRACEPARENT_HEADER,
|
|
1586
|
+
TRACESTATE_HEADER,
|
|
2629
1587
|
Throttle,
|
|
2630
1588
|
ThrottleGuard,
|
|
2631
1589
|
ThrottleModule,
|
|
2632
1590
|
ThrottleOptions,
|
|
2633
1591
|
ThrottleStore,
|
|
1592
|
+
TraceContext,
|
|
2634
1593
|
UNMATCHED,
|
|
2635
1594
|
UseGuards,
|
|
2636
1595
|
ValidationError,
|
|
@@ -2647,6 +1606,7 @@ export {
|
|
|
2647
1606
|
decodeRelay,
|
|
2648
1607
|
defaultErrorMapper,
|
|
2649
1608
|
defaultRelayUrl,
|
|
1609
|
+
defaultStatusFor,
|
|
2650
1610
|
discoverGateway,
|
|
2651
1611
|
discoverGateways,
|
|
2652
1612
|
discoverRoutes,
|
|
@@ -2655,6 +1615,7 @@ export {
|
|
|
2655
1615
|
errorMapper,
|
|
2656
1616
|
gatewaysOf,
|
|
2657
1617
|
guardsOf,
|
|
1618
|
+
isCompressibleType,
|
|
2658
1619
|
isErrorFilter,
|
|
2659
1620
|
isGateway,
|
|
2660
1621
|
joinPath,
|
|
@@ -2662,6 +1623,7 @@ export {
|
|
|
2662
1623
|
meta,
|
|
2663
1624
|
metaKey,
|
|
2664
1625
|
metaOf,
|
|
1626
|
+
negotiate,
|
|
2665
1627
|
normalizePath,
|
|
2666
1628
|
normalizePrefix,
|
|
2667
1629
|
observe,
|
|
@@ -2671,6 +1633,3 @@ export {
|
|
|
2671
1633
|
withCors,
|
|
2672
1634
|
withUpgradeRoutes
|
|
2673
1635
|
};
|
|
2674
|
-
|
|
2675
|
-
//# debugId=1A8278039B82BC0664756E2164756E21
|
|
2676
|
-
//# sourceMappingURL=index.js.map
|