@dunx/http 3.0.0 → 3.0.1
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 +2 -2
- package/dist/chunk-b699cfes.js +130 -0
- package/dist/{chunk-ywdpxbkf.js → chunk-f6pw36av.js} +827 -1101
- package/dist/chunk-sz4pvqxy.js +108 -0
- package/dist/client.d.ts +0 -7
- package/dist/client.js +3 -13
- package/dist/index.d.ts +0 -7
- package/dist/index.js +9 -61
- package/dist/internal.d.ts +4 -4
- package/dist/internal.js +75 -10
- package/package.json +2 -2
|
@@ -1,112 +1,13 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return target;
|
|
12
|
-
};
|
|
13
|
-
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
14
|
-
var __typeError = (msg) => {
|
|
15
|
-
throw TypeError(msg);
|
|
16
|
-
};
|
|
17
|
-
var __defNormalProp = (obj, key, value) => (key in obj) ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
18
|
-
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
19
|
-
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
|
|
20
|
-
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
21
|
-
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);
|
|
22
|
-
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
23
|
-
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
24
|
-
var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
|
|
25
|
-
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
26
|
-
var __expectFn = (fn) => fn !== undefined && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
|
27
|
-
var __decoratorContext = (kind, name, done, metadata, fns) => ({
|
|
28
|
-
kind: __decoratorStrings[kind],
|
|
29
|
-
name,
|
|
30
|
-
metadata,
|
|
31
|
-
addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null))
|
|
32
|
-
});
|
|
33
|
-
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
34
|
-
var __runInitializers = (array, flags, self, value) => {
|
|
35
|
-
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length;i < n; i++)
|
|
36
|
-
flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
|
|
37
|
-
return value;
|
|
38
|
-
};
|
|
39
|
-
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
40
|
-
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
|
|
41
|
-
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
|
|
42
|
-
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
|
|
43
|
-
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : {
|
|
44
|
-
get [name]() {
|
|
45
|
-
return __privateGet(this, extra);
|
|
46
|
-
},
|
|
47
|
-
set [name](x) {
|
|
48
|
-
__privateSet(this, extra, x);
|
|
49
|
-
}
|
|
50
|
-
}, name));
|
|
51
|
-
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
|
|
52
|
-
for (var i = decorators.length - 1;i >= 0; i--) {
|
|
53
|
-
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
54
|
-
if (k) {
|
|
55
|
-
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => (name in x) };
|
|
56
|
-
if (k ^ 3)
|
|
57
|
-
access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
|
|
58
|
-
if (k > 2)
|
|
59
|
-
access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
|
|
60
|
-
}
|
|
61
|
-
it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? undefined : { get: desc.get, set: desc.set } : target, ctx);
|
|
62
|
-
done._ = 1;
|
|
63
|
-
if (k ^ 4 || it === undefined)
|
|
64
|
-
__expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
|
|
65
|
-
else if (typeof it !== "object" || it === null)
|
|
66
|
-
__typeError("Object expected");
|
|
67
|
-
else
|
|
68
|
-
__expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
|
|
69
|
-
}
|
|
70
|
-
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
// src/route/discover.ts
|
|
74
|
-
import { markedMethods } from "@dunx/core";
|
|
75
|
-
|
|
76
|
-
// src/server/status.ts
|
|
77
|
-
var HttpStatusCode = Object.freeze({
|
|
78
|
-
OK: 200,
|
|
79
|
-
CREATED: 201,
|
|
80
|
-
ACCEPTED: 202,
|
|
81
|
-
NO_CONTENT: 204,
|
|
82
|
-
MOVED_PERMANENTLY: 301,
|
|
83
|
-
FOUND: 302,
|
|
84
|
-
NOT_MODIFIED: 304,
|
|
85
|
-
TEMPORARY_REDIRECT: 307,
|
|
86
|
-
PERMANENT_REDIRECT: 308,
|
|
87
|
-
BAD_REQUEST: 400,
|
|
88
|
-
UNAUTHORIZED: 401,
|
|
89
|
-
PAYMENT_REQUIRED: 402,
|
|
90
|
-
FORBIDDEN: 403,
|
|
91
|
-
NOT_FOUND: 404,
|
|
92
|
-
METHOD_NOT_ALLOWED: 405,
|
|
93
|
-
NOT_ACCEPTABLE: 406,
|
|
94
|
-
REQUEST_TIMEOUT: 408,
|
|
95
|
-
CONFLICT: 409,
|
|
96
|
-
GONE: 410,
|
|
97
|
-
PRECONDITION_FAILED: 412,
|
|
98
|
-
PAYLOAD_TOO_LARGE: 413,
|
|
99
|
-
URI_TOO_LONG: 414,
|
|
100
|
-
UNSUPPORTED_MEDIA_TYPE: 415,
|
|
101
|
-
IM_A_TEAPOT: 418,
|
|
102
|
-
UNPROCESSABLE_ENTITY: 422,
|
|
103
|
-
TOO_MANY_REQUESTS: 429,
|
|
104
|
-
INTERNAL_SERVER_ERROR: 500,
|
|
105
|
-
NOT_IMPLEMENTED: 501,
|
|
106
|
-
BAD_GATEWAY: 502,
|
|
107
|
-
SERVICE_UNAVAILABLE: 503,
|
|
108
|
-
GATEWAY_TIMEOUT: 504
|
|
109
|
-
});
|
|
2
|
+
import {
|
|
3
|
+
HttpStatusCode,
|
|
4
|
+
__decorateElement,
|
|
5
|
+
__decoratorMetadata,
|
|
6
|
+
__decoratorStart,
|
|
7
|
+
__privateAdd,
|
|
8
|
+
__privateGet,
|
|
9
|
+
__runInitializers
|
|
10
|
+
} from "./chunk-sz4pvqxy.js";
|
|
110
11
|
|
|
111
12
|
// src/route/marker.ts
|
|
112
13
|
var ROUTE = Symbol.for("dunx.route");
|
|
@@ -125,6 +26,21 @@ var markController = (target, prefix) => {
|
|
|
125
26
|
};
|
|
126
27
|
var prefixOf = (target) => target[CONTROLLER] ?? "";
|
|
127
28
|
|
|
29
|
+
// src/route/decorators.ts
|
|
30
|
+
var Controller = (prefix = "") => (target) => {
|
|
31
|
+
markController(target, prefix);
|
|
32
|
+
return target;
|
|
33
|
+
};
|
|
34
|
+
var verb = (method) => (path = "/", options) => (value, _context) => {
|
|
35
|
+
markRoute(value, { method, path, options });
|
|
36
|
+
return value;
|
|
37
|
+
};
|
|
38
|
+
var Get = verb("GET");
|
|
39
|
+
var Post = verb("POST");
|
|
40
|
+
var Put = verb("PUT");
|
|
41
|
+
var Patch = verb("PATCH");
|
|
42
|
+
var Delete = verb("DELETE");
|
|
43
|
+
|
|
128
44
|
// src/route/metadata.ts
|
|
129
45
|
var META = Symbol.for("dunx.meta");
|
|
130
46
|
var GUARDS = Symbol.for("dunx.guards");
|
|
@@ -170,7 +86,59 @@ var mergeMeta = (...targets) => {
|
|
|
170
86
|
return merged;
|
|
171
87
|
};
|
|
172
88
|
|
|
89
|
+
// src/server/errors.ts
|
|
90
|
+
import { AppError, ConsoleLogger } from "@dunx/core";
|
|
91
|
+
class HttpError extends AppError {
|
|
92
|
+
status;
|
|
93
|
+
name = "HttpError";
|
|
94
|
+
headers;
|
|
95
|
+
constructor(status, message, options) {
|
|
96
|
+
super(message, options);
|
|
97
|
+
this.status = status;
|
|
98
|
+
this.headers = options?.headers;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
Object.defineProperty(HttpError, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "readonly status: number" }, { unresolved: "message: string" }, { unresolved: "options?: HttpErrorOptions" }] });
|
|
102
|
+
|
|
103
|
+
class ValidationError extends HttpError {
|
|
104
|
+
source;
|
|
105
|
+
issues;
|
|
106
|
+
name = "ValidationError";
|
|
107
|
+
constructor(source, issues) {
|
|
108
|
+
super(HttpStatusCode.BAD_REQUEST, `Invalid ${source}`);
|
|
109
|
+
this.source = source;
|
|
110
|
+
this.issues = issues;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
Object.defineProperty(ValidationError, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "readonly source: InputSource" }, { unresolved: "readonly issues: readonly ValidationIssue[]" }] });
|
|
114
|
+
|
|
115
|
+
class ErrorFilter {
|
|
116
|
+
}
|
|
117
|
+
var isErrorFilter = (handler) => typeof handler === "function" && typeof handler.prototype?.catch === "function";
|
|
118
|
+
var toErrorMapper = (handler, resolve) => isErrorFilter(handler) ? (error, req) => resolve(handler).catch(error, req) : handler;
|
|
119
|
+
var errorMapper = (logger) => (error) => {
|
|
120
|
+
if (error instanceof ValidationError) {
|
|
121
|
+
return Response.json({ error: error.message, status: error.status, issues: error.issues }, {
|
|
122
|
+
status: error.status,
|
|
123
|
+
...error.headers && { headers: error.headers }
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (error instanceof HttpError) {
|
|
127
|
+
return Response.json({ error: error.message, status: error.status }, {
|
|
128
|
+
status: error.status,
|
|
129
|
+
...error.headers && { headers: error.headers }
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
logger.error("Unhandled error", error);
|
|
133
|
+
return Response.json({
|
|
134
|
+
error: "Internal Server Error",
|
|
135
|
+
status: HttpStatusCode.INTERNAL_SERVER_ERROR
|
|
136
|
+
}, { status: HttpStatusCode.INTERNAL_SERVER_ERROR });
|
|
137
|
+
};
|
|
138
|
+
var defaultErrorMapper = errorMapper(new ConsoleLogger);
|
|
139
|
+
|
|
173
140
|
// src/route/discover.ts
|
|
141
|
+
import { markedMethods } from "@dunx/core";
|
|
174
142
|
var joinPath = (prefix, path) => {
|
|
175
143
|
const joined = `/${prefix}/${path}`.replace(/\/{2,}/g, "/");
|
|
176
144
|
return joined.length > 1 ? joined.replace(/\/$/, "") : "/";
|
|
@@ -192,19 +160,23 @@ var discoverRoutes = (instance) => {
|
|
|
192
160
|
guards: [...classGuards, ...guardsOf(marked)]
|
|
193
161
|
}));
|
|
194
162
|
};
|
|
195
|
-
// src/inspect.ts
|
|
196
|
-
import {
|
|
197
|
-
collectModules,
|
|
198
|
-
dependenciesOf,
|
|
199
|
-
readControllers
|
|
200
|
-
} from "@dunx/core";
|
|
201
163
|
|
|
202
|
-
// src/ws/
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
164
|
+
// src/ws/envelope.ts
|
|
165
|
+
var encode = (event, data) => JSON.stringify({ event, data });
|
|
166
|
+
var decode = (message) => {
|
|
167
|
+
if (typeof message !== "string")
|
|
168
|
+
return;
|
|
169
|
+
let parsed;
|
|
170
|
+
try {
|
|
171
|
+
parsed = JSON.parse(message);
|
|
172
|
+
} catch {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
176
|
+
return;
|
|
177
|
+
const { event, data } = parsed;
|
|
178
|
+
return typeof event === "string" ? { event, data } : undefined;
|
|
179
|
+
};
|
|
208
180
|
|
|
209
181
|
// src/ws/marker.ts
|
|
210
182
|
var HANDLER = Symbol.for("dunx.ws.handler");
|
|
@@ -228,607 +200,345 @@ var markGateway = (target, path) => {
|
|
|
228
200
|
var gatewayPathOf = (target) => target[GATEWAY] ?? "/";
|
|
229
201
|
var isGateway = (target) => target[GATEWAY] !== undefined;
|
|
230
202
|
|
|
231
|
-
// src/ws/
|
|
232
|
-
var
|
|
233
|
-
|
|
234
|
-
|
|
203
|
+
// src/ws/middleware.ts
|
|
204
|
+
var composeSocket = (middleware, ctx) => middleware.reduceRight((next, current) => (frame, run) => current.handle(frame, ctx, () => next(frame, run)), (_frame, run) => run());
|
|
205
|
+
var observe = (next, done) => {
|
|
206
|
+
let result;
|
|
207
|
+
try {
|
|
208
|
+
result = next();
|
|
209
|
+
} catch (error) {
|
|
210
|
+
done(error, undefined);
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
if (result instanceof Promise) {
|
|
214
|
+
return result.then((value) => {
|
|
215
|
+
done(undefined, value);
|
|
216
|
+
return value;
|
|
217
|
+
}, (error) => {
|
|
218
|
+
done(error, undefined);
|
|
219
|
+
throw error;
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
done(undefined, result);
|
|
223
|
+
return result;
|
|
235
224
|
};
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
225
|
+
|
|
226
|
+
// src/ws/runtime.ts
|
|
227
|
+
import { AppError as AppError2 } from "@dunx/core";
|
|
228
|
+
var slotOf = (handler) => handler.kind === HandlerKind.MESSAGE && handler.event !== undefined ? `message ${JSON.stringify(handler.event)}` : handler.kind;
|
|
229
|
+
var buildRuntime = (gateway) => {
|
|
230
|
+
if (gateway.handlers.length === 0) {
|
|
231
|
+
throw new AppError2(`${gateway.name} is registered as a gateway but declares no handlers. ` + "Add an @OnMessage/@OnOpen/... method, or drop the @Gateway decorator.");
|
|
232
|
+
}
|
|
233
|
+
const owners = new Map;
|
|
234
|
+
const events = new Map;
|
|
235
|
+
for (const handler of gateway.handlers) {
|
|
236
|
+
const slot = slotOf(handler);
|
|
237
|
+
const existing = owners.get(slot);
|
|
238
|
+
if (existing) {
|
|
239
|
+
throw new AppError2(`Handler collision in ${gateway.name}: ${slot} is claimed by ` + `${existing.method}() and by ${handler.method}(). One handler per event.`);
|
|
240
|
+
}
|
|
241
|
+
owners.set(slot, handler);
|
|
242
|
+
if (handler.kind === HandlerKind.MESSAGE && handler.event !== undefined) {
|
|
243
|
+
events.set(handler.event, handler.invoke);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const at = (slot) => owners.get(slot)?.invoke;
|
|
240
247
|
return {
|
|
241
|
-
name:
|
|
242
|
-
path:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
248
|
+
name: gateway.name,
|
|
249
|
+
path: gateway.path,
|
|
250
|
+
upgrade: at(HandlerKind.UPGRADE),
|
|
251
|
+
open: at(HandlerKind.OPEN),
|
|
252
|
+
close: at(HandlerKind.CLOSE),
|
|
253
|
+
drain: at(HandlerKind.DRAIN),
|
|
254
|
+
ping: at(HandlerKind.PING),
|
|
255
|
+
pong: at(HandlerKind.PONG),
|
|
256
|
+
raw: at(HandlerKind.MESSAGE),
|
|
257
|
+
events
|
|
249
258
|
};
|
|
250
259
|
};
|
|
251
|
-
var
|
|
252
|
-
|
|
253
|
-
const discovered
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
if (!candidate)
|
|
258
|
-
continue;
|
|
259
|
-
if (isGateway(candidate.ctor)) {
|
|
260
|
-
discovered.push(discoverGateway(resolve(candidate.token)));
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
const orphan = findHandlerMethod(candidate.ctor);
|
|
264
|
-
if (orphan !== undefined) {
|
|
265
|
-
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.");
|
|
266
|
-
}
|
|
260
|
+
var buildGateways = (discovered) => {
|
|
261
|
+
const byPath = new Map;
|
|
262
|
+
for (const gateway of discovered) {
|
|
263
|
+
const existing = byPath.get(gateway.path);
|
|
264
|
+
if (existing) {
|
|
265
|
+
throw new AppError2(`Gateway path collision: ${gateway.path} is served by ${existing.name} ` + `and by ${gateway.name}. One gateway per path.`);
|
|
267
266
|
}
|
|
267
|
+
byPath.set(gateway.path, buildRuntime(gateway));
|
|
268
268
|
}
|
|
269
|
-
return
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
// src/inspect.ts
|
|
273
|
-
var vendorOf = (schema) => schema?.["~standard"]?.vendor;
|
|
274
|
-
var validatesIn = (options) => {
|
|
275
|
-
const body = vendorOf(options?.body);
|
|
276
|
-
const query = vendorOf(options?.query);
|
|
277
|
-
const params = vendorOf(options?.params);
|
|
278
|
-
return {
|
|
279
|
-
...body === undefined ? {} : { body },
|
|
280
|
-
...query === undefined ? {} : { query },
|
|
281
|
-
...params === undefined ? {} : { params }
|
|
282
|
-
};
|
|
269
|
+
return byPath;
|
|
283
270
|
};
|
|
284
|
-
var
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
return
|
|
289
|
-
};
|
|
290
|
-
var nodeFor = (route, module) => ({
|
|
291
|
-
method: route.method,
|
|
292
|
-
path: route.path,
|
|
293
|
-
controller: route.controller,
|
|
294
|
-
handler: route.handlerName,
|
|
295
|
-
module,
|
|
296
|
-
public: route.meta?.get(PUBLIC.id) === true,
|
|
297
|
-
roles: rolesIn(route),
|
|
298
|
-
guards: (route.guards ?? []).map((guard) => guard.name),
|
|
299
|
-
hidden: route.meta?.get(HIDDEN.id) === true,
|
|
300
|
-
validates: validatesIn(route.options),
|
|
301
|
-
status: route.options?.status ?? null,
|
|
302
|
-
responses: Object.keys(route.options?.response ?? {}).map(Number)
|
|
303
|
-
});
|
|
304
|
-
var routesOf = (root) => collectModules(root).flatMap((module) => readControllers(module).flatMap((controller) => {
|
|
305
|
-
const { prototype } = controller;
|
|
306
|
-
return discoverRoutes(Object.create(prototype)).map((route) => nodeFor(route, module.name));
|
|
307
|
-
}));
|
|
308
|
-
var gatewayFor = (ctor, module) => {
|
|
309
|
-
const { name, path, handlers } = discoverGateway(Object.create(ctor.prototype));
|
|
310
|
-
return {
|
|
311
|
-
name,
|
|
312
|
-
path,
|
|
313
|
-
module,
|
|
314
|
-
dependencies: dependenciesOf(ctor),
|
|
315
|
-
handlers: handlers.map((handler) => ({
|
|
316
|
-
kind: handler.kind,
|
|
317
|
-
event: handler.event ?? null,
|
|
318
|
-
method: handler.method
|
|
319
|
-
}))
|
|
320
|
-
};
|
|
271
|
+
var someHandler = (gateways, pick) => {
|
|
272
|
+
for (const gateway of gateways)
|
|
273
|
+
if (pick(gateway) !== undefined)
|
|
274
|
+
return true;
|
|
275
|
+
return false;
|
|
321
276
|
};
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
277
|
+
|
|
278
|
+
// src/ws/adapter.ts
|
|
279
|
+
var RUNTIME = Symbol.for("dunx.ws.runtime");
|
|
280
|
+
var UNCLAIMED = Symbol.for("dunx.ws.unclaimed");
|
|
281
|
+
var defaultOnError = (error, socket) => {
|
|
282
|
+
console.error(`[dunx/http] ${socket.data.path} handler failed:`, error);
|
|
326
283
|
};
|
|
327
|
-
var
|
|
328
|
-
|
|
329
|
-
var EMPTY = new Map;
|
|
330
|
-
var buildContext = (route) => {
|
|
331
|
-
const record = route.meta ?? EMPTY;
|
|
332
|
-
return Object.freeze({
|
|
333
|
-
controller: route.controller,
|
|
334
|
-
handler: route.handlerName,
|
|
335
|
-
method: route.method,
|
|
336
|
-
path: route.path,
|
|
337
|
-
parsesBody: route.options?.body !== undefined,
|
|
338
|
-
get: (key) => record.get(key.id)
|
|
339
|
-
});
|
|
284
|
+
var reportedByMiddleware = () => {
|
|
285
|
+
return;
|
|
340
286
|
};
|
|
341
|
-
|
|
342
|
-
var
|
|
343
|
-
var
|
|
344
|
-
|
|
345
|
-
if (
|
|
346
|
-
if (origin !== "*")
|
|
347
|
-
return origin === requested ? origin : undefined;
|
|
348
|
-
if (!options.credentials)
|
|
349
|
-
return "*";
|
|
350
|
-
return requested ?? undefined;
|
|
351
|
-
}
|
|
352
|
-
if (requested === null)
|
|
287
|
+
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(", ")}.`;
|
|
288
|
+
var runtimeOf = (socket) => socket.data[RUNTIME];
|
|
289
|
+
var isBinary = (value) => value instanceof ArrayBuffer || ArrayBuffer.isView(value);
|
|
290
|
+
var replyRaw = (socket, value) => {
|
|
291
|
+
if (value === undefined)
|
|
353
292
|
return;
|
|
354
|
-
|
|
355
|
-
return allowed ? requested : undefined;
|
|
293
|
+
socket.send(typeof value === "string" || isBinary(value) ? value : JSON.stringify(value));
|
|
356
294
|
};
|
|
357
|
-
var
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
295
|
+
var settle = (result, socket, onError, then) => {
|
|
296
|
+
if (result instanceof Promise) {
|
|
297
|
+
result.then((value) => {
|
|
298
|
+
if (!then)
|
|
299
|
+
return;
|
|
300
|
+
try {
|
|
301
|
+
then(value);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
onError(error, socket);
|
|
304
|
+
}
|
|
305
|
+
}, (error) => onError(error, socket));
|
|
306
|
+
return;
|
|
369
307
|
}
|
|
370
|
-
|
|
308
|
+
if (then)
|
|
309
|
+
then(result);
|
|
371
310
|
};
|
|
372
|
-
var
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
return async (req) => {
|
|
378
|
-
const response = applyCors(options, req, new Response(null, { status: HttpStatusCode.NO_CONTENT }));
|
|
379
|
-
if (!response.headers.has(ORIGIN))
|
|
380
|
-
return response;
|
|
381
|
-
response.headers.set("access-control-allow-methods", allowMethods);
|
|
382
|
-
const allowHeaders = options.allowedHeaders ?? (req.headers.get("access-control-request-headers") ?? "").split(",").map((header) => header.trim()).filter((header) => header.length > 0);
|
|
383
|
-
if (allowHeaders.length > 0) {
|
|
384
|
-
response.headers.set("access-control-allow-headers", allowHeaders.join(", "));
|
|
385
|
-
}
|
|
386
|
-
if (options.maxAge !== undefined) {
|
|
387
|
-
response.headers.set("access-control-max-age", String(options.maxAge));
|
|
388
|
-
}
|
|
389
|
-
return response;
|
|
390
|
-
};
|
|
391
|
-
};
|
|
392
|
-
// src/server/errors.ts
|
|
393
|
-
import { AppError as AppError2, ConsoleLogger } from "@dunx/core";
|
|
394
|
-
class HttpError extends AppError2 {
|
|
395
|
-
status;
|
|
396
|
-
name = "HttpError";
|
|
397
|
-
headers;
|
|
398
|
-
constructor(status, message, options) {
|
|
399
|
-
super(message, options);
|
|
400
|
-
this.status = status;
|
|
401
|
-
this.headers = options?.headers;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
Object.defineProperty(HttpError, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "readonly status: number" }, { unresolved: "message: string" }, { unresolved: "options?: HttpErrorOptions" }] });
|
|
405
|
-
|
|
406
|
-
class ValidationError extends HttpError {
|
|
407
|
-
source;
|
|
408
|
-
issues;
|
|
409
|
-
name = "ValidationError";
|
|
410
|
-
constructor(source, issues) {
|
|
411
|
-
super(HttpStatusCode.BAD_REQUEST, `Invalid ${source}`);
|
|
412
|
-
this.source = source;
|
|
413
|
-
this.issues = issues;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
Object.defineProperty(ValidationError, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "readonly source: InputSource" }, { unresolved: "readonly issues: readonly ValidationIssue[]" }] });
|
|
417
|
-
|
|
418
|
-
class ErrorFilter {
|
|
419
|
-
}
|
|
420
|
-
var isErrorFilter = (handler) => typeof handler === "function" && typeof handler.prototype?.catch === "function";
|
|
421
|
-
var toErrorMapper = (handler, resolve) => isErrorFilter(handler) ? (error, req) => resolve(handler).catch(error, req) : handler;
|
|
422
|
-
var errorMapper = (logger) => (error) => {
|
|
423
|
-
if (error instanceof ValidationError) {
|
|
424
|
-
return Response.json({ error: error.message, status: error.status, issues: error.issues }, {
|
|
425
|
-
status: error.status,
|
|
426
|
-
...error.headers && { headers: error.headers }
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
if (error instanceof HttpError) {
|
|
430
|
-
return Response.json({ error: error.message, status: error.status }, {
|
|
431
|
-
status: error.status,
|
|
432
|
-
...error.headers && { headers: error.headers }
|
|
311
|
+
var framing = (kind) => {
|
|
312
|
+
if (kind === HandlerKind.CLOSE) {
|
|
313
|
+
return (args) => ({
|
|
314
|
+
socket: args[0],
|
|
315
|
+
data: { code: args[1], reason: args[2] }
|
|
433
316
|
});
|
|
434
317
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
error: "Internal Server Error",
|
|
438
|
-
status: HttpStatusCode.INTERNAL_SERVER_ERROR
|
|
439
|
-
}, { status: HttpStatusCode.INTERNAL_SERVER_ERROR });
|
|
440
|
-
};
|
|
441
|
-
var defaultErrorMapper = errorMapper(new ConsoleLogger);
|
|
442
|
-
// src/server/middleware.ts
|
|
443
|
-
var compose = (middleware, ctx, handler) => middleware.reduceRight((next, current) => (req) => current.handle(req, ctx, () => next(req)), handler);
|
|
444
|
-
// src/server/routes.ts
|
|
445
|
-
import { AppError as AppError3 } from "@dunx/core";
|
|
446
|
-
|
|
447
|
-
// src/server/raw-body.ts
|
|
448
|
-
var WANTED = Symbol.for("dunx.http.rawBody.wanted");
|
|
449
|
-
var TEXT = Symbol.for("dunx.http.rawBody.text");
|
|
450
|
-
|
|
451
|
-
class RawBody {
|
|
452
|
-
static want(req) {
|
|
453
|
-
req[WANTED] = true;
|
|
454
|
-
}
|
|
455
|
-
static wanted(req) {
|
|
456
|
-
return req[WANTED] === true;
|
|
457
|
-
}
|
|
458
|
-
static record(req, text) {
|
|
459
|
-
req[TEXT] = text;
|
|
460
|
-
}
|
|
461
|
-
static read(req) {
|
|
462
|
-
return req[TEXT];
|
|
318
|
+
if (kind === HandlerKind.OPEN || kind === HandlerKind.DRAIN) {
|
|
319
|
+
return (args) => ({ socket: args[0], data: undefined });
|
|
463
320
|
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// src/server/input.ts
|
|
467
|
-
var grouped = (entries) => {
|
|
468
|
-
const collected = {};
|
|
469
|
-
entries.forEach((value, key) => {
|
|
470
|
-
const existing = collected[key];
|
|
471
|
-
if (existing === undefined)
|
|
472
|
-
collected[key] = value;
|
|
473
|
-
else if (Array.isArray(existing))
|
|
474
|
-
existing.push(value);
|
|
475
|
-
else
|
|
476
|
-
collected[key] = [existing, value];
|
|
477
|
-
});
|
|
478
|
-
return collected;
|
|
321
|
+
return (args) => ({ socket: args[1], data: args[0] });
|
|
479
322
|
};
|
|
480
|
-
var
|
|
481
|
-
var asUrlEncoded = async (req) => grouped(new URLSearchParams(await req.text()));
|
|
482
|
-
var asMultipart = async (req) => grouped(await req.formData());
|
|
483
|
-
var asText = (req) => req.text();
|
|
484
|
-
var parserFor = (media) => {
|
|
485
|
-
if (media === "application/json" || media.endsWith("+json"))
|
|
486
|
-
return asJson;
|
|
487
|
-
if (media === "application/x-www-form-urlencoded")
|
|
488
|
-
return asUrlEncoded;
|
|
489
|
-
if (media === "multipart/form-data")
|
|
490
|
-
return asMultipart;
|
|
491
|
-
if (media.startsWith("text/"))
|
|
492
|
-
return asText;
|
|
323
|
+
var NOTHING = () => {
|
|
493
324
|
return;
|
|
494
325
|
};
|
|
495
|
-
var
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
const media = (end === -1 ? header : header.slice(0, end)).trim();
|
|
502
|
-
return media === "" ? JSON_MEDIA : media.toLowerCase();
|
|
503
|
-
};
|
|
504
|
-
var flatten = (issue) => {
|
|
505
|
-
const path = issue.path?.map((segment) => String(typeof segment === "object" ? segment.key : segment)).join(".");
|
|
506
|
-
return path === undefined || path === "" ? { message: issue.message } : { message: issue.message, path };
|
|
507
|
-
};
|
|
508
|
-
var accept = (source, result) => {
|
|
509
|
-
if (result.issues !== undefined) {
|
|
510
|
-
throw new ValidationError(source, result.issues.map(flatten));
|
|
511
|
-
}
|
|
512
|
-
return result.value;
|
|
513
|
-
};
|
|
514
|
-
var fillWith = (draft, source, schema, value) => {
|
|
515
|
-
const result = schema["~standard"].validate(value);
|
|
516
|
-
if (result instanceof Promise) {
|
|
517
|
-
return result.then((settled) => {
|
|
518
|
-
draft[source] = accept(source, settled);
|
|
519
|
-
return draft;
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
draft[source] = accept(source, result);
|
|
523
|
-
return draft;
|
|
524
|
-
};
|
|
525
|
-
var bodyFill = (schema) => (draft) => {
|
|
526
|
-
const media = mediaTypeOf(draft.req);
|
|
527
|
-
const parse = parserFor(media);
|
|
528
|
-
if (parse === undefined) {
|
|
529
|
-
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/*.");
|
|
530
|
-
}
|
|
531
|
-
const read = parse === asJson && RawBody.wanted(draft.req) ? draft.req.text().then((text) => {
|
|
532
|
-
RawBody.record(draft.req, text);
|
|
533
|
-
return JSON.parse(text);
|
|
534
|
-
}) : parse(draft.req);
|
|
535
|
-
return read.then((value) => fillWith(draft, "body", schema, value), (error) => {
|
|
536
|
-
throw new HttpError(HttpStatusCode.BAD_REQUEST, `Malformed ${media} body`, { cause: error });
|
|
537
|
-
});
|
|
538
|
-
};
|
|
539
|
-
var searchOf = (url) => {
|
|
540
|
-
const start = url.indexOf("?");
|
|
541
|
-
if (start === -1)
|
|
542
|
-
return "";
|
|
543
|
-
const end = url.indexOf("#", start + 1);
|
|
544
|
-
return end === -1 ? url.slice(start + 1) : url.slice(start + 1, end);
|
|
545
|
-
};
|
|
546
|
-
var queryFill = (schema) => (draft) => {
|
|
547
|
-
const params = new URLSearchParams(searchOf(draft.req.url));
|
|
548
|
-
return fillWith(draft, "query", schema, grouped(params));
|
|
549
|
-
};
|
|
550
|
-
var paramsFill = (schema) => (draft) => fillWith(draft, "params", schema, draft.req.params);
|
|
551
|
-
var then = (first, second) => (draft) => {
|
|
552
|
-
const started = first(draft);
|
|
553
|
-
return started instanceof Promise ? started.then(second) : second(started);
|
|
554
|
-
};
|
|
555
|
-
var buildInputReader = (options) => {
|
|
556
|
-
const fills = [];
|
|
557
|
-
if (options?.body !== undefined)
|
|
558
|
-
fills.push(bodyFill(options.body));
|
|
559
|
-
if (options?.query !== undefined)
|
|
560
|
-
fills.push(queryFill(options.query));
|
|
561
|
-
if (options?.params !== undefined)
|
|
562
|
-
fills.push(paramsFill(options.params));
|
|
563
|
-
if (fills.length === 0)
|
|
564
|
-
return (req) => ({ req });
|
|
565
|
-
const fill = fills.reduce(then);
|
|
566
|
-
return (req) => fill({ req });
|
|
567
|
-
};
|
|
568
|
-
|
|
569
|
-
// src/server/request-id.ts
|
|
570
|
-
var REQUEST_ID_HEADER = "x-request-id";
|
|
571
|
-
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
572
|
-
var traceId = (inbound) => inbound !== null && inbound.length === 36 && UUID.test(inbound) ? inbound : crypto.randomUUID();
|
|
573
|
-
var ID = Symbol.for("dunx.http.requestId");
|
|
574
|
-
|
|
575
|
-
class RequestIds {
|
|
576
|
-
static assign(req) {
|
|
577
|
-
const id = traceId(req.headers.get(REQUEST_ID_HEADER));
|
|
578
|
-
req[ID] = id;
|
|
579
|
-
return id;
|
|
580
|
-
}
|
|
581
|
-
static stamp(response, req) {
|
|
582
|
-
const id = req[ID];
|
|
583
|
-
if (id !== undefined)
|
|
584
|
-
response.headers.set(REQUEST_ID_HEADER, id);
|
|
585
|
-
return response;
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// src/server/routes.ts
|
|
590
|
-
var construct = (guard) => new guard;
|
|
591
|
-
var toResponse = (value, status) => {
|
|
592
|
-
if (value instanceof Response)
|
|
593
|
-
return value;
|
|
594
|
-
if (value === undefined || value === null) {
|
|
595
|
-
return new Response(null, { status: HttpStatusCode.NO_CONTENT });
|
|
596
|
-
}
|
|
597
|
-
return Response.json(value, { status });
|
|
598
|
-
};
|
|
599
|
-
var statusFor = (route) => route.options?.status ?? defaultStatusFor(route.method);
|
|
600
|
-
var assertNoCollisions = (discovered) => {
|
|
601
|
-
const owners = new Map;
|
|
602
|
-
for (const route of discovered) {
|
|
603
|
-
const key = `${route.method} ${route.path}`;
|
|
604
|
-
const owner = `${route.controller}.${route.handlerName}`;
|
|
605
|
-
const existing = owners.get(key);
|
|
606
|
-
if (existing !== undefined) {
|
|
607
|
-
throw new AppError3(`Route collision: ${key} is declared by ${existing} and by ${owner}. ` + "Bun would keep only one of them.");
|
|
608
|
-
}
|
|
609
|
-
owners.set(key, owner);
|
|
610
|
-
}
|
|
611
|
-
};
|
|
612
|
-
var assertNoGatewayCollisions = (discovered, gatewayPaths) => {
|
|
613
|
-
const gateways = new Set(gatewayPaths);
|
|
614
|
-
for (const route of discovered) {
|
|
615
|
-
if (gateways.has(route.path)) {
|
|
616
|
-
throw new AppError3(`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.");
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
};
|
|
620
|
-
var withUpgradeRoutes = (routes, gateways) => {
|
|
621
|
-
const merged = { ...routes };
|
|
622
|
-
for (const [path, upgrade] of gateways)
|
|
623
|
-
merged[path] = { GET: upgrade };
|
|
624
|
-
return merged;
|
|
625
|
-
};
|
|
626
|
-
var unmatchedContext = (req, isPublic) => Object.freeze({
|
|
627
|
-
controller: "(unmatched)",
|
|
628
|
-
handler: "(none)",
|
|
629
|
-
method: req.method,
|
|
630
|
-
path: new URL(req.url).pathname,
|
|
631
|
-
parsesBody: false,
|
|
632
|
-
get: (key) => {
|
|
633
|
-
if (key.id === UNMATCHED.id)
|
|
634
|
-
return true;
|
|
635
|
-
if (key.id === PUBLIC.id && isPublic)
|
|
636
|
-
return true;
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
});
|
|
640
|
-
var buildFallback = (middleware = [], onError = defaultErrorMapper, cors, notFound = "guarded") => {
|
|
641
|
-
const miss = () => {
|
|
642
|
-
throw new HttpError(HttpStatusCode.NOT_FOUND, "NOT_FOUND");
|
|
643
|
-
};
|
|
644
|
-
const run = async (req) => {
|
|
645
|
-
try {
|
|
646
|
-
return await compose(middleware, unmatchedContext(req, notFound === "public"), miss)(req);
|
|
647
|
-
} catch (error) {
|
|
648
|
-
return RequestIds.stamp(onError(error, req), req);
|
|
649
|
-
}
|
|
326
|
+
var through = (gateway, middleware, kind, event, invoke) => {
|
|
327
|
+
const ctx = {
|
|
328
|
+
gateway: gateway.name,
|
|
329
|
+
path: gateway.path,
|
|
330
|
+
kind,
|
|
331
|
+
event
|
|
650
332
|
};
|
|
651
|
-
|
|
333
|
+
const dispatch = composeSocket(middleware, ctx);
|
|
334
|
+
const frameOf = framing(kind);
|
|
335
|
+
const run = invoke ?? NOTHING;
|
|
336
|
+
return (...args) => dispatch(frameOf(args), () => run(...args));
|
|
652
337
|
};
|
|
653
|
-
var
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
return onError(error, req);
|
|
669
|
-
}
|
|
338
|
+
var withMiddleware = (gateway, middleware) => {
|
|
339
|
+
const wrap = (kind, event, invoke) => through(gateway, middleware, kind, event, invoke);
|
|
340
|
+
const optional = (kind, invoke) => invoke === undefined ? undefined : wrap(kind, undefined, invoke);
|
|
341
|
+
return {
|
|
342
|
+
...gateway,
|
|
343
|
+
open: wrap(HandlerKind.OPEN, undefined, gateway.open),
|
|
344
|
+
close: wrap(HandlerKind.CLOSE, undefined, gateway.close),
|
|
345
|
+
drain: optional(HandlerKind.DRAIN, gateway.drain),
|
|
346
|
+
ping: optional(HandlerKind.PING, gateway.ping),
|
|
347
|
+
pong: optional(HandlerKind.PONG, gateway.pong),
|
|
348
|
+
raw: optional(HandlerKind.MESSAGE, gateway.raw),
|
|
349
|
+
events: new Map([...gateway.events].map(([event, invoke]) => [
|
|
350
|
+
event,
|
|
351
|
+
wrap(HandlerKind.MESSAGE, event, invoke)
|
|
352
|
+
]))
|
|
670
353
|
};
|
|
671
|
-
|
|
354
|
+
};
|
|
355
|
+
var unclaimedDispatch = (gateway, middleware) => (frame, event) => composeSocket(middleware, {
|
|
356
|
+
gateway: gateway.name,
|
|
357
|
+
path: gateway.path,
|
|
358
|
+
kind: HandlerKind.MESSAGE,
|
|
359
|
+
event
|
|
360
|
+
})(frame, () => {
|
|
361
|
+
return;
|
|
362
|
+
});
|
|
363
|
+
var buildWebSocket = (discovered, options = {}, middleware = []) => {
|
|
364
|
+
const byPath = buildGateways(discovered);
|
|
365
|
+
const wrapped = middleware.length === 0 ? byPath : new Map([...byPath].map(([path, gateway]) => [
|
|
366
|
+
path,
|
|
367
|
+
withMiddleware(gateway, middleware)
|
|
368
|
+
]));
|
|
369
|
+
const gateways = [...wrapped.values()];
|
|
370
|
+
const onError = options.onError ?? (middleware.length === 0 ? defaultOnError : reportedByMiddleware);
|
|
371
|
+
const reports = options.onError !== undefined || middleware.some((entry) => entry.reportsErrors === true);
|
|
372
|
+
const { onError: _onError, ...socketOptions } = options;
|
|
373
|
+
const run = (invoke, args, ws, then) => {
|
|
672
374
|
try {
|
|
673
|
-
|
|
674
|
-
return input instanceof Promise ? input.then((resolved) => invoke(resolved, req), (error) => onError(error, req)) : invoke(input, req);
|
|
375
|
+
settle(invoke(...args), ws, onError, then);
|
|
675
376
|
} catch (error) {
|
|
676
|
-
|
|
377
|
+
onError(error, ws);
|
|
677
378
|
}
|
|
678
379
|
};
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
380
|
+
const websocket = {
|
|
381
|
+
...socketOptions,
|
|
382
|
+
message(ws, message) {
|
|
383
|
+
const gateway = runtimeOf(ws);
|
|
384
|
+
let event;
|
|
385
|
+
if (gateway.events.size > 0) {
|
|
386
|
+
const envelope = decode(message);
|
|
387
|
+
const handler = envelope && gateway.events.get(envelope.event);
|
|
388
|
+
if (envelope && handler) {
|
|
389
|
+
run(handler, [envelope.data, ws], ws, (value) => {
|
|
390
|
+
if (value !== undefined)
|
|
391
|
+
ws.send(encode(envelope.event, value));
|
|
392
|
+
});
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
event = envelope?.event;
|
|
396
|
+
}
|
|
397
|
+
if (gateway.raw) {
|
|
398
|
+
run(gateway.raw, [message, ws], ws, (value) => replyRaw(ws, value));
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const unclaimed2 = ws.data[UNCLAIMED];
|
|
402
|
+
if (!unclaimed2)
|
|
403
|
+
return;
|
|
702
404
|
try {
|
|
703
|
-
|
|
405
|
+
settle(unclaimed2({ socket: ws, data: message }, event), ws, onError, undefined);
|
|
704
406
|
} catch (error) {
|
|
705
|
-
|
|
407
|
+
onError(error, ws);
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
...someHandler(gateways, (g) => g.open) && {
|
|
411
|
+
open(ws) {
|
|
412
|
+
const { open } = runtimeOf(ws);
|
|
413
|
+
if (open)
|
|
414
|
+
run(open, [ws], ws, undefined);
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
...someHandler(gateways, (g) => g.close) && {
|
|
418
|
+
close(ws, code, reason) {
|
|
419
|
+
const { close } = runtimeOf(ws);
|
|
420
|
+
if (close)
|
|
421
|
+
run(close, [ws, code, reason], ws, undefined);
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
...someHandler(gateways, (g) => g.drain) && {
|
|
425
|
+
drain(ws) {
|
|
426
|
+
const { drain } = runtimeOf(ws);
|
|
427
|
+
if (drain)
|
|
428
|
+
run(drain, [ws], ws, undefined);
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
...someHandler(gateways, (g) => g.ping) && {
|
|
432
|
+
ping(ws, data) {
|
|
433
|
+
const { ping } = runtimeOf(ws);
|
|
434
|
+
if (ping)
|
|
435
|
+
run(ping, [data, ws], ws, undefined);
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
...someHandler(gateways, (g) => g.pong) && {
|
|
439
|
+
pong(ws, data) {
|
|
440
|
+
const { pong } = runtimeOf(ws);
|
|
441
|
+
if (pong)
|
|
442
|
+
run(pong, [data, ws], ws, undefined);
|
|
706
443
|
}
|
|
707
|
-
};
|
|
708
|
-
const byMethod = routes[route.path] ??= {};
|
|
709
|
-
byMethod[route.method] = cors ? withCors(cors, guarded) : directOr(guarded, route, read, status, onError, chain.length === 0);
|
|
710
|
-
}
|
|
711
|
-
if (cors) {
|
|
712
|
-
for (const byMethod of Object.values(routes)) {
|
|
713
|
-
byMethod.OPTIONS = preflight(cors, Object.keys(byMethod));
|
|
714
444
|
}
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
var quality = (params) => {
|
|
738
|
-
for (const param of params) {
|
|
739
|
-
const [key, value] = param.split("=");
|
|
740
|
-
if (key?.trim().toLowerCase() !== "q")
|
|
741
|
-
continue;
|
|
742
|
-
const q = Number.parseFloat(value ?? "");
|
|
743
|
-
return Number.isFinite(q) && q >= 0 && q <= 1 ? q : 1;
|
|
744
|
-
}
|
|
745
|
-
return 1;
|
|
746
|
-
};
|
|
747
|
-
var negotiate = (header, offered) => {
|
|
748
|
-
if (header === null)
|
|
749
|
-
return;
|
|
750
|
-
const accepted = new Map;
|
|
751
|
-
for (const element of header.split(",")) {
|
|
752
|
-
const [name, ...params] = element.split(";");
|
|
753
|
-
const token = name?.trim().toLowerCase();
|
|
754
|
-
if (token === undefined || token === "")
|
|
755
|
-
continue;
|
|
756
|
-
accepted.set(token, quality(params));
|
|
757
|
-
}
|
|
758
|
-
const wildcard = accepted.get("*");
|
|
759
|
-
let best;
|
|
760
|
-
let bestQuality = 0;
|
|
761
|
-
for (const encoding of offered) {
|
|
762
|
-
const q = accepted.get(encoding) ?? wildcard ?? 0;
|
|
763
|
-
if (q > bestQuality) {
|
|
764
|
-
best = encoding;
|
|
765
|
-
bestQuality = q;
|
|
445
|
+
};
|
|
446
|
+
const unclaimed = new Map(middleware.length === 0 ? [] : gateways.map((gateway) => [
|
|
447
|
+
gateway,
|
|
448
|
+
unclaimedDispatch(gateway, middleware)
|
|
449
|
+
]));
|
|
450
|
+
const accept = (req, server, gateway, context) => {
|
|
451
|
+
const fallback = unclaimed.get(gateway);
|
|
452
|
+
const data = {
|
|
453
|
+
path: gateway.path,
|
|
454
|
+
context,
|
|
455
|
+
id: crypto.randomUUID(),
|
|
456
|
+
[RUNTIME]: gateway,
|
|
457
|
+
...fallback === undefined ? {} : { [UNCLAIMED]: fallback }
|
|
458
|
+
};
|
|
459
|
+
return server.upgrade(req, { data }) ? undefined : new Response("Expected a WebSocket upgrade", { status: 426 });
|
|
460
|
+
};
|
|
461
|
+
const upgradeHandler = (gateway) => (req, server) => {
|
|
462
|
+
if (!gateway.upgrade)
|
|
463
|
+
return accept(req, server, gateway, undefined);
|
|
464
|
+
const result = gateway.upgrade(req);
|
|
465
|
+
if (result instanceof Promise) {
|
|
466
|
+
return result.then((value) => value instanceof Response ? value : accept(req, server, gateway, value));
|
|
766
467
|
}
|
|
767
|
-
|
|
768
|
-
|
|
468
|
+
return result instanceof Response ? result : accept(req, server, gateway, result);
|
|
469
|
+
};
|
|
470
|
+
return {
|
|
471
|
+
websocket,
|
|
472
|
+
routes: new Map(gateways.map((gateway) => [gateway.path, upgradeHandler(gateway)])),
|
|
473
|
+
paths: [...byPath.keys()],
|
|
474
|
+
warnings: middleware.length > 0 && !reports ? [unreported(middleware)] : [],
|
|
475
|
+
gateways: gateways.map((gateway) => ({
|
|
476
|
+
name: gateway.name,
|
|
477
|
+
path: gateway.path,
|
|
478
|
+
events: [...gateway.events.keys()]
|
|
479
|
+
}))
|
|
480
|
+
};
|
|
769
481
|
};
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
"
|
|
779
|
-
"
|
|
780
|
-
"application/manifest+json",
|
|
781
|
-
"application/wasm",
|
|
782
|
-
"application/x-javascript",
|
|
783
|
-
"application/x-ndjson",
|
|
784
|
-
"application/xml",
|
|
785
|
-
"image/svg+xml"
|
|
786
|
-
]);
|
|
787
|
-
var isCompressibleType = (contentType) => {
|
|
788
|
-
if (contentType === null)
|
|
789
|
-
return false;
|
|
790
|
-
const type = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
791
|
-
if (type.startsWith("text/"))
|
|
792
|
-
return true;
|
|
793
|
-
if (type.endsWith("+json") || type.endsWith("+xml"))
|
|
794
|
-
return true;
|
|
795
|
-
return COMPRESSIBLE.has(type);
|
|
482
|
+
|
|
483
|
+
// src/ws/discover.ts
|
|
484
|
+
import {
|
|
485
|
+
AppError as AppError3,
|
|
486
|
+
classOf,
|
|
487
|
+
markedMethods as markedMethods2
|
|
488
|
+
} from "@dunx/core";
|
|
489
|
+
var normalizePath = (path) => {
|
|
490
|
+
const joined = `/${path}`.replace(/\/{2,}/g, "/");
|
|
491
|
+
return joined.length > 1 ? joined.replace(/\/$/, "") : "/";
|
|
796
492
|
};
|
|
797
|
-
var
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
493
|
+
var eachHandler = (start) => markedMethods2(start, handlerMetaOf);
|
|
494
|
+
var discoverGateway = (instance) => {
|
|
495
|
+
const klass = instance.constructor;
|
|
496
|
+
const members = instance;
|
|
497
|
+
return {
|
|
498
|
+
name: klass.name,
|
|
499
|
+
path: normalizePath(gatewayPathOf(klass)),
|
|
500
|
+
handlers: eachHandler(Object.getPrototypeOf(instance)).map(({ name, meta: meta2 }) => ({
|
|
501
|
+
kind: meta2.kind,
|
|
502
|
+
event: meta2.event,
|
|
503
|
+
method: name,
|
|
504
|
+
invoke: members[name].bind(instance)
|
|
505
|
+
}))
|
|
506
|
+
};
|
|
807
507
|
};
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
508
|
+
var findHandlerMethod = (ctor) => eachHandler(ctor.prototype)[0]?.name;
|
|
509
|
+
var discoverGateways = (modules, resolve) => {
|
|
510
|
+
const discovered = [];
|
|
511
|
+
for (const module of modules) {
|
|
512
|
+
for (const entry of module.options.providers ?? []) {
|
|
513
|
+
const candidate = classOf(entry);
|
|
514
|
+
if (!candidate)
|
|
515
|
+
continue;
|
|
516
|
+
if (isGateway(candidate.ctor)) {
|
|
517
|
+
discovered.push(discoverGateway(resolve(candidate.token)));
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
const orphan = findHandlerMethod(candidate.ctor);
|
|
521
|
+
if (orphan !== undefined) {
|
|
522
|
+
throw new AppError3(`${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.");
|
|
523
|
+
}
|
|
821
524
|
}
|
|
822
|
-
this.threshold = init.threshold ?? 1024;
|
|
823
|
-
this.filter = init.filter ?? isCompressibleType;
|
|
824
525
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
var
|
|
830
|
-
|
|
831
|
-
|
|
526
|
+
return discovered;
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
// src/ws/relay.ts
|
|
530
|
+
var DEFAULT_RELAY_CHANNEL = "dunx:ws";
|
|
531
|
+
var defaultRelayError = (error, phase) => {
|
|
532
|
+
console.warn(`[dunx/http] the websocket relay could not ${phase}. Fan-out is local to ` + "this process until it recovers:", error);
|
|
533
|
+
};
|
|
534
|
+
var toBytes = (data) => ArrayBuffer.isView(data) ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : new Uint8Array(data);
|
|
535
|
+
var encodeRelay = (origin, topic, data) => typeof data === "string" ? JSON.stringify({ o: origin, t: topic, d: data }) : JSON.stringify({
|
|
536
|
+
o: origin,
|
|
537
|
+
t: topic,
|
|
538
|
+
d: Buffer.from(toBytes(data)).toString("base64"),
|
|
539
|
+
b: 1
|
|
540
|
+
});
|
|
541
|
+
var decodeRelay = (message) => {
|
|
832
542
|
let parsed;
|
|
833
543
|
try {
|
|
834
544
|
parsed = JSON.parse(message);
|
|
@@ -837,289 +547,469 @@ var decode = (message) => {
|
|
|
837
547
|
}
|
|
838
548
|
if (typeof parsed !== "object" || parsed === null)
|
|
839
549
|
return;
|
|
840
|
-
const {
|
|
841
|
-
|
|
550
|
+
const { o, t, d, b } = parsed;
|
|
551
|
+
if (typeof o !== "string" || typeof t !== "string" || typeof d !== "string") {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
return { origin: o, topic: t, data: b ? Buffer.from(d, "base64") : d };
|
|
842
555
|
};
|
|
843
556
|
|
|
844
|
-
// src/
|
|
845
|
-
var
|
|
846
|
-
var
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
557
|
+
// src/server/request-id.ts
|
|
558
|
+
var REQUEST_ID_HEADER = "x-request-id";
|
|
559
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
560
|
+
var traceId = (inbound) => inbound !== null && inbound.length === 36 && UUID.test(inbound) ? inbound : crypto.randomUUID();
|
|
561
|
+
var ID = Symbol.for("dunx.http.requestId");
|
|
562
|
+
|
|
563
|
+
class RequestIds {
|
|
564
|
+
static assign(req) {
|
|
565
|
+
const id = traceId(req.headers.get(REQUEST_ID_HEADER));
|
|
566
|
+
req[ID] = id;
|
|
567
|
+
return id;
|
|
568
|
+
}
|
|
569
|
+
static stamp(response, req) {
|
|
570
|
+
const id = req[ID];
|
|
571
|
+
if (id !== undefined)
|
|
572
|
+
response.headers.set(REQUEST_ID_HEADER, id);
|
|
573
|
+
return response;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/server/context.ts
|
|
578
|
+
var EMPTY = new Map;
|
|
579
|
+
var buildContext = (route) => {
|
|
580
|
+
const record = route.meta ?? EMPTY;
|
|
581
|
+
return Object.freeze({
|
|
582
|
+
controller: route.controller,
|
|
583
|
+
handler: route.handlerName,
|
|
584
|
+
method: route.method,
|
|
585
|
+
path: route.path,
|
|
586
|
+
parsesBody: route.options?.body !== undefined,
|
|
587
|
+
get: (key) => record.get(key.id)
|
|
588
|
+
});
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
// src/server/cors.ts
|
|
592
|
+
var ORIGIN = "access-control-allow-origin";
|
|
593
|
+
var allowedOrigin = (options, requested) => {
|
|
594
|
+
const origin = options.origin ?? "*";
|
|
595
|
+
if (typeof origin === "string") {
|
|
596
|
+
if (origin !== "*")
|
|
597
|
+
return origin === requested ? origin : undefined;
|
|
598
|
+
if (!options.credentials)
|
|
599
|
+
return "*";
|
|
600
|
+
return requested ?? undefined;
|
|
601
|
+
}
|
|
602
|
+
if (requested === null)
|
|
603
|
+
return;
|
|
604
|
+
const allowed = typeof origin === "function" ? origin(requested) : origin.includes(requested);
|
|
605
|
+
return allowed ? requested : undefined;
|
|
606
|
+
};
|
|
607
|
+
var applyCors = (options, req, response) => {
|
|
608
|
+
const origin = allowedOrigin(options, req.headers.get("origin"));
|
|
609
|
+
if (origin === undefined)
|
|
610
|
+
return response;
|
|
611
|
+
response.headers.set(ORIGIN, origin);
|
|
612
|
+
if (origin !== "*")
|
|
613
|
+
response.headers.append("vary", "Origin");
|
|
614
|
+
if (options.credentials) {
|
|
615
|
+
response.headers.set("access-control-allow-credentials", "true");
|
|
616
|
+
}
|
|
617
|
+
if (options.exposedHeaders?.length) {
|
|
618
|
+
response.headers.set("access-control-expose-headers", options.exposedHeaders.join(", "));
|
|
619
|
+
}
|
|
620
|
+
return response;
|
|
621
|
+
};
|
|
622
|
+
var withCors = (options, handler) => {
|
|
623
|
+
return async (req) => applyCors(options, req, await handler(req));
|
|
624
|
+
};
|
|
625
|
+
var preflight = (options, methods) => {
|
|
626
|
+
const allowMethods = (options.methods ?? methods).join(", ");
|
|
627
|
+
return async (req) => {
|
|
628
|
+
const response = applyCors(options, req, new Response(null, { status: HttpStatusCode.NO_CONTENT }));
|
|
629
|
+
if (!response.headers.has(ORIGIN))
|
|
630
|
+
return response;
|
|
631
|
+
response.headers.set("access-control-allow-methods", allowMethods);
|
|
632
|
+
const allowHeaders = options.allowedHeaders ?? (req.headers.get("access-control-request-headers") ?? "").split(",").map((header) => header.trim()).filter((header) => header.length > 0);
|
|
633
|
+
if (allowHeaders.length > 0) {
|
|
634
|
+
response.headers.set("access-control-allow-headers", allowHeaders.join(", "));
|
|
635
|
+
}
|
|
636
|
+
if (options.maxAge !== undefined) {
|
|
637
|
+
response.headers.set("access-control-max-age", String(options.maxAge));
|
|
638
|
+
}
|
|
639
|
+
return response;
|
|
640
|
+
};
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
// src/server/middleware.ts
|
|
644
|
+
var compose = (middleware, ctx, handler) => middleware.reduceRight((next, current) => (req) => current.handle(req, ctx, () => next(req)), handler);
|
|
645
|
+
|
|
646
|
+
// src/server/routes.ts
|
|
647
|
+
import { AppError as AppError4 } from "@dunx/core";
|
|
648
|
+
|
|
649
|
+
// src/server/raw-body.ts
|
|
650
|
+
var WANTED = Symbol.for("dunx.http.rawBody.wanted");
|
|
651
|
+
var TEXT = Symbol.for("dunx.http.rawBody.text");
|
|
652
|
+
|
|
653
|
+
class RawBody {
|
|
654
|
+
static want(req) {
|
|
655
|
+
req[WANTED] = true;
|
|
656
|
+
}
|
|
657
|
+
static wanted(req) {
|
|
658
|
+
return req[WANTED] === true;
|
|
659
|
+
}
|
|
660
|
+
static record(req, text) {
|
|
661
|
+
req[TEXT] = text;
|
|
662
|
+
}
|
|
663
|
+
static read(req) {
|
|
664
|
+
return req[TEXT];
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// src/server/input.ts
|
|
669
|
+
var grouped = (entries) => {
|
|
670
|
+
const collected = {};
|
|
671
|
+
entries.forEach((value, key) => {
|
|
672
|
+
const existing = collected[key];
|
|
673
|
+
if (existing === undefined)
|
|
674
|
+
collected[key] = value;
|
|
675
|
+
else if (Array.isArray(existing))
|
|
676
|
+
existing.push(value);
|
|
677
|
+
else
|
|
678
|
+
collected[key] = [existing, value];
|
|
679
|
+
});
|
|
680
|
+
return collected;
|
|
681
|
+
};
|
|
682
|
+
var asJson = (req) => req.json();
|
|
683
|
+
var asUrlEncoded = async (req) => grouped(new URLSearchParams(await req.text()));
|
|
684
|
+
var asMultipart = async (req) => grouped(await req.formData());
|
|
685
|
+
var asText = (req) => req.text();
|
|
686
|
+
var parserFor = (media) => {
|
|
687
|
+
if (media === "application/json" || media.endsWith("+json"))
|
|
688
|
+
return asJson;
|
|
689
|
+
if (media === "application/x-www-form-urlencoded")
|
|
690
|
+
return asUrlEncoded;
|
|
691
|
+
if (media === "multipart/form-data")
|
|
692
|
+
return asMultipart;
|
|
693
|
+
if (media.startsWith("text/"))
|
|
694
|
+
return asText;
|
|
695
|
+
return;
|
|
696
|
+
};
|
|
697
|
+
var JSON_MEDIA = "application/json";
|
|
698
|
+
var mediaTypeOf = (req) => {
|
|
699
|
+
const header = req.headers.get("content-type");
|
|
700
|
+
if (header === JSON_MEDIA || header === null)
|
|
701
|
+
return JSON_MEDIA;
|
|
702
|
+
const end = header.indexOf(";");
|
|
703
|
+
const media = (end === -1 ? header : header.slice(0, end)).trim();
|
|
704
|
+
return media === "" ? JSON_MEDIA : media.toLowerCase();
|
|
705
|
+
};
|
|
706
|
+
var flatten = (issue) => {
|
|
707
|
+
const path = issue.path?.map((segment) => String(typeof segment === "object" ? segment.key : segment)).join(".");
|
|
708
|
+
return path === undefined || path === "" ? { message: issue.message } : { message: issue.message, path };
|
|
709
|
+
};
|
|
710
|
+
var accept = (source, result) => {
|
|
711
|
+
if (result.issues !== undefined) {
|
|
712
|
+
throw new ValidationError(source, result.issues.map(flatten));
|
|
853
713
|
}
|
|
714
|
+
return result.value;
|
|
715
|
+
};
|
|
716
|
+
var fillWith = (draft, source, schema, value) => {
|
|
717
|
+
const result = schema["~standard"].validate(value);
|
|
854
718
|
if (result instanceof Promise) {
|
|
855
|
-
return result.then((
|
|
856
|
-
|
|
857
|
-
return
|
|
858
|
-
}, (error) => {
|
|
859
|
-
done(error, undefined);
|
|
860
|
-
throw error;
|
|
719
|
+
return result.then((settled) => {
|
|
720
|
+
draft[source] = accept(source, settled);
|
|
721
|
+
return draft;
|
|
861
722
|
});
|
|
862
723
|
}
|
|
863
|
-
|
|
864
|
-
return
|
|
724
|
+
draft[source] = accept(source, result);
|
|
725
|
+
return draft;
|
|
726
|
+
};
|
|
727
|
+
var bodyFill = (schema) => (draft) => {
|
|
728
|
+
const media = mediaTypeOf(draft.req);
|
|
729
|
+
const parse = parserFor(media);
|
|
730
|
+
if (parse === undefined) {
|
|
731
|
+
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/*.");
|
|
732
|
+
}
|
|
733
|
+
const read = parse === asJson && RawBody.wanted(draft.req) ? draft.req.text().then((text) => {
|
|
734
|
+
RawBody.record(draft.req, text);
|
|
735
|
+
return JSON.parse(text);
|
|
736
|
+
}) : parse(draft.req);
|
|
737
|
+
return read.then((value) => fillWith(draft, "body", schema, value), (error) => {
|
|
738
|
+
throw new HttpError(HttpStatusCode.BAD_REQUEST, `Malformed ${media} body`, { cause: error });
|
|
739
|
+
});
|
|
740
|
+
};
|
|
741
|
+
var searchOf = (url) => {
|
|
742
|
+
const start = url.indexOf("?");
|
|
743
|
+
if (start === -1)
|
|
744
|
+
return "";
|
|
745
|
+
const end = url.indexOf("#", start + 1);
|
|
746
|
+
return end === -1 ? url.slice(start + 1) : url.slice(start + 1, end);
|
|
747
|
+
};
|
|
748
|
+
var queryFill = (schema) => (draft) => {
|
|
749
|
+
const params = new URLSearchParams(searchOf(draft.req.url));
|
|
750
|
+
return fillWith(draft, "query", schema, grouped(params));
|
|
751
|
+
};
|
|
752
|
+
var paramsFill = (schema) => (draft) => fillWith(draft, "params", schema, draft.req.params);
|
|
753
|
+
var then = (first, second) => (draft) => {
|
|
754
|
+
const started = first(draft);
|
|
755
|
+
return started instanceof Promise ? started.then(second) : second(started);
|
|
756
|
+
};
|
|
757
|
+
var buildInputReader = (options) => {
|
|
758
|
+
const fills = [];
|
|
759
|
+
if (options?.body !== undefined)
|
|
760
|
+
fills.push(bodyFill(options.body));
|
|
761
|
+
if (options?.query !== undefined)
|
|
762
|
+
fills.push(queryFill(options.query));
|
|
763
|
+
if (options?.params !== undefined)
|
|
764
|
+
fills.push(paramsFill(options.params));
|
|
765
|
+
if (fills.length === 0)
|
|
766
|
+
return (req) => ({ req });
|
|
767
|
+
const fill = fills.reduce(then);
|
|
768
|
+
return (req) => fill({ req });
|
|
865
769
|
};
|
|
866
770
|
|
|
867
|
-
// src/
|
|
868
|
-
|
|
869
|
-
var
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
771
|
+
// src/server/routes.ts
|
|
772
|
+
var construct = (guard) => new guard;
|
|
773
|
+
var toResponse = (value, status) => {
|
|
774
|
+
if (value instanceof Response)
|
|
775
|
+
return value;
|
|
776
|
+
if (value === undefined || value === null) {
|
|
777
|
+
return new Response(null, { status: HttpStatusCode.NO_CONTENT });
|
|
873
778
|
}
|
|
779
|
+
return Response.json(value, { status });
|
|
780
|
+
};
|
|
781
|
+
var statusFor = (route) => route.options?.status ?? defaultStatusFor(route.method);
|
|
782
|
+
var assertNoCollisions = (discovered) => {
|
|
874
783
|
const owners = new Map;
|
|
875
|
-
const
|
|
876
|
-
|
|
877
|
-
const
|
|
878
|
-
const existing = owners.get(
|
|
879
|
-
if (existing) {
|
|
880
|
-
throw new AppError4(`
|
|
881
|
-
}
|
|
882
|
-
owners.set(slot, handler);
|
|
883
|
-
if (handler.kind === HandlerKind.MESSAGE && handler.event !== undefined) {
|
|
884
|
-
events.set(handler.event, handler.invoke);
|
|
784
|
+
for (const route of discovered) {
|
|
785
|
+
const key = `${route.method} ${route.path}`;
|
|
786
|
+
const owner = `${route.controller}.${route.handlerName}`;
|
|
787
|
+
const existing = owners.get(key);
|
|
788
|
+
if (existing !== undefined) {
|
|
789
|
+
throw new AppError4(`Route collision: ${key} is declared by ${existing} and by ${owner}. ` + "Bun would keep only one of them.");
|
|
885
790
|
}
|
|
791
|
+
owners.set(key, owner);
|
|
886
792
|
}
|
|
887
|
-
const at = (slot) => owners.get(slot)?.invoke;
|
|
888
|
-
return {
|
|
889
|
-
name: gateway.name,
|
|
890
|
-
path: gateway.path,
|
|
891
|
-
upgrade: at(HandlerKind.UPGRADE),
|
|
892
|
-
open: at(HandlerKind.OPEN),
|
|
893
|
-
close: at(HandlerKind.CLOSE),
|
|
894
|
-
drain: at(HandlerKind.DRAIN),
|
|
895
|
-
ping: at(HandlerKind.PING),
|
|
896
|
-
pong: at(HandlerKind.PONG),
|
|
897
|
-
raw: at(HandlerKind.MESSAGE),
|
|
898
|
-
events
|
|
899
|
-
};
|
|
900
793
|
};
|
|
901
|
-
var
|
|
902
|
-
const
|
|
903
|
-
for (const
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
throw new AppError4(`Gateway path collision: ${gateway.path} is served by ${existing.name} ` + `and by ${gateway.name}. One gateway per path.`);
|
|
794
|
+
var assertNoGatewayCollisions = (discovered, gatewayPaths) => {
|
|
795
|
+
const gateways = new Set(gatewayPaths);
|
|
796
|
+
for (const route of discovered) {
|
|
797
|
+
if (gateways.has(route.path)) {
|
|
798
|
+
throw new AppError4(`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.");
|
|
907
799
|
}
|
|
908
|
-
byPath.set(gateway.path, buildRuntime(gateway));
|
|
909
800
|
}
|
|
910
|
-
return byPath;
|
|
911
801
|
};
|
|
912
|
-
var
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
return
|
|
917
|
-
};
|
|
918
|
-
|
|
919
|
-
// src/ws/adapter.ts
|
|
920
|
-
var RUNTIME = Symbol.for("dunx.ws.runtime");
|
|
921
|
-
var UNCLAIMED = Symbol.for("dunx.ws.unclaimed");
|
|
922
|
-
var defaultOnError = (error, socket) => {
|
|
923
|
-
console.error(`[dunx/http] ${socket.data.path} handler failed:`, error);
|
|
924
|
-
};
|
|
925
|
-
var reportedByMiddleware = () => {
|
|
926
|
-
return;
|
|
927
|
-
};
|
|
928
|
-
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(", ")}.`;
|
|
929
|
-
var runtimeOf = (socket) => socket.data[RUNTIME];
|
|
930
|
-
var isBinary = (value) => value instanceof ArrayBuffer || ArrayBuffer.isView(value);
|
|
931
|
-
var replyRaw = (socket, value) => {
|
|
932
|
-
if (value === undefined)
|
|
933
|
-
return;
|
|
934
|
-
socket.send(typeof value === "string" || isBinary(value) ? value : JSON.stringify(value));
|
|
802
|
+
var withUpgradeRoutes = (routes, gateways) => {
|
|
803
|
+
const merged = { ...routes };
|
|
804
|
+
for (const [path, upgrade] of gateways)
|
|
805
|
+
merged[path] = { GET: upgrade };
|
|
806
|
+
return merged;
|
|
935
807
|
};
|
|
936
|
-
var
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
808
|
+
var unmatchedContext = (req, isPublic) => Object.freeze({
|
|
809
|
+
controller: "(unmatched)",
|
|
810
|
+
handler: "(none)",
|
|
811
|
+
method: req.method,
|
|
812
|
+
path: new URL(req.url).pathname,
|
|
813
|
+
parsesBody: false,
|
|
814
|
+
get: (key) => {
|
|
815
|
+
if (key.id === UNMATCHED.id)
|
|
816
|
+
return true;
|
|
817
|
+
if (key.id === PUBLIC.id && isPublic)
|
|
818
|
+
return true;
|
|
947
819
|
return;
|
|
948
820
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
if (kind === HandlerKind.CLOSE) {
|
|
954
|
-
return (args) => ({
|
|
955
|
-
socket: args[0],
|
|
956
|
-
data: { code: args[1], reason: args[2] }
|
|
957
|
-
});
|
|
958
|
-
}
|
|
959
|
-
if (kind === HandlerKind.OPEN || kind === HandlerKind.DRAIN) {
|
|
960
|
-
return (args) => ({ socket: args[0], data: undefined });
|
|
961
|
-
}
|
|
962
|
-
return (args) => ({ socket: args[1], data: args[0] });
|
|
963
|
-
};
|
|
964
|
-
var NOTHING = () => {
|
|
965
|
-
return;
|
|
966
|
-
};
|
|
967
|
-
var through = (gateway, middleware, kind, event, invoke) => {
|
|
968
|
-
const ctx = {
|
|
969
|
-
gateway: gateway.name,
|
|
970
|
-
path: gateway.path,
|
|
971
|
-
kind,
|
|
972
|
-
event
|
|
821
|
+
});
|
|
822
|
+
var buildFallback = (middleware = [], onError = defaultErrorMapper, cors, notFound = "guarded") => {
|
|
823
|
+
const miss = () => {
|
|
824
|
+
throw new HttpError(HttpStatusCode.NOT_FOUND, "NOT_FOUND");
|
|
973
825
|
};
|
|
974
|
-
const
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
const wrap = (kind, event, invoke) => through(gateway, middleware, kind, event, invoke);
|
|
981
|
-
const optional = (kind, invoke) => invoke === undefined ? undefined : wrap(kind, undefined, invoke);
|
|
982
|
-
return {
|
|
983
|
-
...gateway,
|
|
984
|
-
open: wrap(HandlerKind.OPEN, undefined, gateway.open),
|
|
985
|
-
close: wrap(HandlerKind.CLOSE, undefined, gateway.close),
|
|
986
|
-
drain: optional(HandlerKind.DRAIN, gateway.drain),
|
|
987
|
-
ping: optional(HandlerKind.PING, gateway.ping),
|
|
988
|
-
pong: optional(HandlerKind.PONG, gateway.pong),
|
|
989
|
-
raw: optional(HandlerKind.MESSAGE, gateway.raw),
|
|
990
|
-
events: new Map([...gateway.events].map(([event, invoke]) => [
|
|
991
|
-
event,
|
|
992
|
-
wrap(HandlerKind.MESSAGE, event, invoke)
|
|
993
|
-
]))
|
|
826
|
+
const run = async (req) => {
|
|
827
|
+
try {
|
|
828
|
+
return await compose(middleware, unmatchedContext(req, notFound === "public"), miss)(req);
|
|
829
|
+
} catch (error) {
|
|
830
|
+
return RequestIds.stamp(onError(error, req), req);
|
|
831
|
+
}
|
|
994
832
|
};
|
|
833
|
+
return cors ? withCors(cors, run) : run;
|
|
995
834
|
};
|
|
996
|
-
var
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
event
|
|
1001
|
-
})(frame, () => {
|
|
1002
|
-
return;
|
|
1003
|
-
});
|
|
1004
|
-
var buildWebSocket = (discovered, options = {}, middleware = []) => {
|
|
1005
|
-
const byPath = buildGateways(discovered);
|
|
1006
|
-
const wrapped = middleware.length === 0 ? byPath : new Map([...byPath].map(([path, gateway]) => [
|
|
1007
|
-
path,
|
|
1008
|
-
withMiddleware(gateway, middleware)
|
|
1009
|
-
]));
|
|
1010
|
-
const gateways = [...wrapped.values()];
|
|
1011
|
-
const onError = options.onError ?? (middleware.length === 0 ? defaultOnError : reportedByMiddleware);
|
|
1012
|
-
const reports = options.onError !== undefined || middleware.some((entry) => entry.reportsErrors === true);
|
|
1013
|
-
const { onError: _onError, ...socketOptions } = options;
|
|
1014
|
-
const run = (invoke, args, ws, then2) => {
|
|
835
|
+
var directOr = (guarded, route, read, status, onError, noMiddleware) => {
|
|
836
|
+
if (!noMiddleware)
|
|
837
|
+
return guarded;
|
|
838
|
+
const settle2 = (value, req) => {
|
|
1015
839
|
try {
|
|
1016
|
-
|
|
840
|
+
return toResponse(value, status);
|
|
1017
841
|
} catch (error) {
|
|
1018
|
-
onError(error,
|
|
842
|
+
return onError(error, req);
|
|
1019
843
|
}
|
|
1020
844
|
};
|
|
1021
|
-
const
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
const envelope = decode(message);
|
|
1028
|
-
const handler = envelope && gateway.events.get(envelope.event);
|
|
1029
|
-
if (envelope && handler) {
|
|
1030
|
-
run(handler, [envelope.data, ws], ws, (value) => {
|
|
1031
|
-
if (value !== undefined)
|
|
1032
|
-
ws.send(encode(envelope.event, value));
|
|
1033
|
-
});
|
|
1034
|
-
return;
|
|
1035
|
-
}
|
|
1036
|
-
event = envelope?.event;
|
|
1037
|
-
}
|
|
1038
|
-
if (gateway.raw) {
|
|
1039
|
-
run(gateway.raw, [message, ws], ws, (value) => replyRaw(ws, value));
|
|
1040
|
-
return;
|
|
1041
|
-
}
|
|
1042
|
-
const unclaimed2 = ws.data[UNCLAIMED];
|
|
1043
|
-
if (!unclaimed2)
|
|
1044
|
-
return;
|
|
1045
|
-
try {
|
|
1046
|
-
settle(unclaimed2({ socket: ws, data: message }, event), ws, onError, undefined);
|
|
1047
|
-
} catch (error) {
|
|
1048
|
-
onError(error, ws);
|
|
1049
|
-
}
|
|
1050
|
-
},
|
|
1051
|
-
...someHandler(gateways, (g) => g.open) && {
|
|
1052
|
-
open(ws) {
|
|
1053
|
-
const { open } = runtimeOf(ws);
|
|
1054
|
-
if (open)
|
|
1055
|
-
run(open, [ws], ws, undefined);
|
|
1056
|
-
}
|
|
1057
|
-
},
|
|
1058
|
-
...someHandler(gateways, (g) => g.close) && {
|
|
1059
|
-
close(ws, code, reason) {
|
|
1060
|
-
const { close } = runtimeOf(ws);
|
|
1061
|
-
if (close)
|
|
1062
|
-
run(close, [ws, code, reason], ws, undefined);
|
|
1063
|
-
}
|
|
1064
|
-
},
|
|
1065
|
-
...someHandler(gateways, (g) => g.drain) && {
|
|
1066
|
-
drain(ws) {
|
|
1067
|
-
const { drain } = runtimeOf(ws);
|
|
1068
|
-
if (drain)
|
|
1069
|
-
run(drain, [ws], ws, undefined);
|
|
1070
|
-
}
|
|
1071
|
-
},
|
|
1072
|
-
...someHandler(gateways, (g) => g.ping) && {
|
|
1073
|
-
ping(ws, data) {
|
|
1074
|
-
const { ping } = runtimeOf(ws);
|
|
1075
|
-
if (ping)
|
|
1076
|
-
run(ping, [data, ws], ws, undefined);
|
|
1077
|
-
}
|
|
1078
|
-
},
|
|
1079
|
-
...someHandler(gateways, (g) => g.pong) && {
|
|
1080
|
-
pong(ws, data) {
|
|
1081
|
-
const { pong } = runtimeOf(ws);
|
|
1082
|
-
if (pong)
|
|
1083
|
-
run(pong, [data, ws], ws, undefined);
|
|
1084
|
-
}
|
|
845
|
+
const invoke = (input, req) => {
|
|
846
|
+
try {
|
|
847
|
+
const value = route.handler(input);
|
|
848
|
+
return value instanceof Promise ? value.then((resolved) => settle2(resolved, req), (error) => onError(error, req)) : settle2(value, req);
|
|
849
|
+
} catch (error) {
|
|
850
|
+
return onError(error, req);
|
|
1085
851
|
}
|
|
1086
852
|
};
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
const data = {
|
|
1094
|
-
path: gateway.path,
|
|
1095
|
-
context,
|
|
1096
|
-
id: crypto.randomUUID(),
|
|
1097
|
-
[RUNTIME]: gateway,
|
|
1098
|
-
...fallback === undefined ? {} : { [UNCLAIMED]: fallback }
|
|
1099
|
-
};
|
|
1100
|
-
return server.upgrade(req, { data }) ? undefined : new Response("Expected a WebSocket upgrade", { status: 426 });
|
|
1101
|
-
};
|
|
1102
|
-
const upgradeHandler = (gateway) => (req, server) => {
|
|
1103
|
-
if (!gateway.upgrade)
|
|
1104
|
-
return accept2(req, server, gateway, undefined);
|
|
1105
|
-
const result = gateway.upgrade(req);
|
|
1106
|
-
if (result instanceof Promise) {
|
|
1107
|
-
return result.then((value) => value instanceof Response ? value : accept2(req, server, gateway, value));
|
|
853
|
+
return (req) => {
|
|
854
|
+
try {
|
|
855
|
+
const input = read(req);
|
|
856
|
+
return input instanceof Promise ? input.then((resolved) => invoke(resolved, req), (error) => onError(error, req)) : invoke(input, req);
|
|
857
|
+
} catch (error) {
|
|
858
|
+
return onError(error, req);
|
|
1108
859
|
}
|
|
1109
|
-
return result instanceof Response ? result : accept2(req, server, gateway, result);
|
|
1110
860
|
};
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
861
|
+
};
|
|
862
|
+
var buildRoutes = (discovered, middleware = [], onError = defaultErrorMapper, cors, resolve = construct) => {
|
|
863
|
+
assertNoCollisions(discovered);
|
|
864
|
+
const routes = {};
|
|
865
|
+
const instances = new Map;
|
|
866
|
+
const guardOf = (guard, from) => {
|
|
867
|
+
const existing = instances.get(guard);
|
|
868
|
+
if (existing)
|
|
869
|
+
return existing;
|
|
870
|
+
const created = resolve(guard, from);
|
|
871
|
+
instances.set(guard, created);
|
|
872
|
+
return created;
|
|
1121
873
|
};
|
|
874
|
+
for (const route of discovered) {
|
|
875
|
+
const read = buildInputReader(route.options);
|
|
876
|
+
const status = statusFor(route);
|
|
877
|
+
const chain = [
|
|
878
|
+
...middleware,
|
|
879
|
+
...(route.moduleMiddleware ?? []).map((entry) => guardOf(entry, route.module)),
|
|
880
|
+
...(route.guards ?? []).map((guard) => guardOf(guard, route.module))
|
|
881
|
+
];
|
|
882
|
+
const chained = compose(chain, buildContext(route), async (req) => toResponse(await route.handler(await read(req)), status));
|
|
883
|
+
const guarded = async (req) => {
|
|
884
|
+
try {
|
|
885
|
+
return await chained(req);
|
|
886
|
+
} catch (error) {
|
|
887
|
+
return RequestIds.stamp(onError(error, req), req);
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
const byMethod = routes[route.path] ??= {};
|
|
891
|
+
byMethod[route.method] = cors ? withCors(cors, guarded) : directOr(guarded, route, read, status, onError, chain.length === 0);
|
|
892
|
+
}
|
|
893
|
+
if (cors) {
|
|
894
|
+
for (const byMethod of Object.values(routes)) {
|
|
895
|
+
byMethod.OPTIONS = preflight(cors, Object.keys(byMethod));
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return routes;
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
// src/static/options.ts
|
|
902
|
+
class StaticOptions {
|
|
903
|
+
root;
|
|
904
|
+
path;
|
|
905
|
+
maxAge;
|
|
906
|
+
immutable;
|
|
907
|
+
constructor(init) {
|
|
908
|
+
this.root = init.root;
|
|
909
|
+
this.path = normalizePrefix(init.path ?? "/");
|
|
910
|
+
this.maxAge = init.maxAge ?? 60;
|
|
911
|
+
this.immutable = init.immutable ?? (() => false);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
Object.defineProperty(StaticOptions, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "init: StaticOptionsInit" }] });
|
|
915
|
+
var normalizePrefix = (path) => {
|
|
916
|
+
const trimmed = path.split("/").filter(Boolean).join("/");
|
|
917
|
+
return trimmed === "" ? "/" : `/${trimmed}`;
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
// src/compression/negotiate.ts
|
|
921
|
+
var quality = (params) => {
|
|
922
|
+
for (const param of params) {
|
|
923
|
+
const [key, value] = param.split("=");
|
|
924
|
+
if (key?.trim().toLowerCase() !== "q")
|
|
925
|
+
continue;
|
|
926
|
+
const q = Number.parseFloat(value ?? "");
|
|
927
|
+
return Number.isFinite(q) && q >= 0 && q <= 1 ? q : 1;
|
|
928
|
+
}
|
|
929
|
+
return 1;
|
|
930
|
+
};
|
|
931
|
+
var negotiate = (header, offered) => {
|
|
932
|
+
if (header === null)
|
|
933
|
+
return;
|
|
934
|
+
const accepted = new Map;
|
|
935
|
+
for (const element of header.split(",")) {
|
|
936
|
+
const [name, ...params] = element.split(";");
|
|
937
|
+
const token = name?.trim().toLowerCase();
|
|
938
|
+
if (token === undefined || token === "")
|
|
939
|
+
continue;
|
|
940
|
+
accepted.set(token, quality(params));
|
|
941
|
+
}
|
|
942
|
+
const wildcard = accepted.get("*");
|
|
943
|
+
let best;
|
|
944
|
+
let bestQuality = 0;
|
|
945
|
+
for (const encoding of offered) {
|
|
946
|
+
const q = accepted.get(encoding) ?? wildcard ?? 0;
|
|
947
|
+
if (q > bestQuality) {
|
|
948
|
+
best = encoding;
|
|
949
|
+
bestQuality = q;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return best;
|
|
953
|
+
};
|
|
954
|
+
|
|
955
|
+
// src/compression/options.ts
|
|
956
|
+
var CompressionEncoding = Object.freeze({
|
|
957
|
+
ZSTD: "zstd",
|
|
958
|
+
GZIP: "gzip"
|
|
959
|
+
});
|
|
960
|
+
var COMPRESSIBLE = new Set([
|
|
961
|
+
"application/graphql",
|
|
962
|
+
"application/graphql-response+json",
|
|
963
|
+
"application/javascript",
|
|
964
|
+
"application/json",
|
|
965
|
+
"application/manifest+json",
|
|
966
|
+
"application/wasm",
|
|
967
|
+
"application/x-javascript",
|
|
968
|
+
"application/x-ndjson",
|
|
969
|
+
"application/xml",
|
|
970
|
+
"image/svg+xml"
|
|
971
|
+
]);
|
|
972
|
+
var isCompressibleType = (contentType) => {
|
|
973
|
+
if (contentType === null)
|
|
974
|
+
return false;
|
|
975
|
+
const type = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
976
|
+
if (type.startsWith("text/"))
|
|
977
|
+
return true;
|
|
978
|
+
if (type.endsWith("+json") || type.endsWith("+xml"))
|
|
979
|
+
return true;
|
|
980
|
+
return COMPRESSIBLE.has(type);
|
|
981
|
+
};
|
|
982
|
+
var encodable = (encoding) => {
|
|
983
|
+
const sync = encoding === CompressionEncoding.ZSTD ? Bun.zstdCompressSync : Bun.gzipSync;
|
|
984
|
+
if (typeof sync !== "function")
|
|
985
|
+
return false;
|
|
986
|
+
try {
|
|
987
|
+
new CompressionStream(encoding);
|
|
988
|
+
return true;
|
|
989
|
+
} catch {
|
|
990
|
+
return false;
|
|
991
|
+
}
|
|
1122
992
|
};
|
|
993
|
+
|
|
994
|
+
class CompressionOptions {
|
|
995
|
+
encodings;
|
|
996
|
+
threshold;
|
|
997
|
+
filter;
|
|
998
|
+
constructor(init = {}) {
|
|
999
|
+
this.encodings = init.encodings ?? [
|
|
1000
|
+
CompressionEncoding.ZSTD,
|
|
1001
|
+
CompressionEncoding.GZIP
|
|
1002
|
+
];
|
|
1003
|
+
const missing = this.encodings.filter((encoding) => !encodable(encoding));
|
|
1004
|
+
if (missing.length > 0) {
|
|
1005
|
+
throw new Error(`Bun ${Bun.version} cannot encode ${missing.join(", ")}. ` + "Pass `encodings` without it, or upgrade Bun.");
|
|
1006
|
+
}
|
|
1007
|
+
this.threshold = init.threshold ?? 1024;
|
|
1008
|
+
this.filter = init.filter ?? isCompressibleType;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
Object.defineProperty(CompressionOptions, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "init: CompressionOptionsInit = {}", optional: true }] });
|
|
1012
|
+
|
|
1123
1013
|
// src/ws/redis-relay.ts
|
|
1124
1014
|
import { AppError as AppError5 } from "@dunx/core";
|
|
1125
1015
|
var PROTOCOLS = [
|
|
@@ -1211,50 +1101,6 @@ class RedisRelay {
|
|
|
1211
1101
|
}
|
|
1212
1102
|
}
|
|
1213
1103
|
Object.defineProperty(RedisRelay, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "options: RedisRelayOptions = {}", optional: true }] });
|
|
1214
|
-
// src/ws/relay.ts
|
|
1215
|
-
var DEFAULT_RELAY_CHANNEL = "dunx:ws";
|
|
1216
|
-
var defaultRelayError = (error, phase) => {
|
|
1217
|
-
console.warn(`[dunx/http] the websocket relay could not ${phase}. Fan-out is local to ` + "this process until it recovers:", error);
|
|
1218
|
-
};
|
|
1219
|
-
var toBytes = (data) => ArrayBuffer.isView(data) ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : new Uint8Array(data);
|
|
1220
|
-
var encodeRelay = (origin, topic, data) => typeof data === "string" ? JSON.stringify({ o: origin, t: topic, d: data }) : JSON.stringify({
|
|
1221
|
-
o: origin,
|
|
1222
|
-
t: topic,
|
|
1223
|
-
d: Buffer.from(toBytes(data)).toString("base64"),
|
|
1224
|
-
b: 1
|
|
1225
|
-
});
|
|
1226
|
-
var decodeRelay = (message) => {
|
|
1227
|
-
let parsed;
|
|
1228
|
-
try {
|
|
1229
|
-
parsed = JSON.parse(message);
|
|
1230
|
-
} catch {
|
|
1231
|
-
return;
|
|
1232
|
-
}
|
|
1233
|
-
if (typeof parsed !== "object" || parsed === null)
|
|
1234
|
-
return;
|
|
1235
|
-
const { o, t, d, b } = parsed;
|
|
1236
|
-
if (typeof o !== "string" || typeof t !== "string" || typeof d !== "string") {
|
|
1237
|
-
return;
|
|
1238
|
-
}
|
|
1239
|
-
return { origin: o, topic: t, data: b ? Buffer.from(d, "base64") : d };
|
|
1240
|
-
};
|
|
1241
|
-
// src/health/controller.ts
|
|
1242
|
-
import { inject } from "@dunx/core";
|
|
1243
|
-
|
|
1244
|
-
// src/route/decorators.ts
|
|
1245
|
-
var Controller = (prefix = "") => (target) => {
|
|
1246
|
-
markController(target, prefix);
|
|
1247
|
-
return target;
|
|
1248
|
-
};
|
|
1249
|
-
var verb = (method) => (path = "/", options) => (value, _context) => {
|
|
1250
|
-
markRoute(value, { method, path, options });
|
|
1251
|
-
return value;
|
|
1252
|
-
};
|
|
1253
|
-
var Get = verb("GET");
|
|
1254
|
-
var Post = verb("POST");
|
|
1255
|
-
var Put = verb("PUT");
|
|
1256
|
-
var Patch = verb("PATCH");
|
|
1257
|
-
var Delete = verb("DELETE");
|
|
1258
1104
|
|
|
1259
1105
|
// src/health/report-schema.ts
|
|
1260
1106
|
var state = {
|
|
@@ -1400,6 +1246,7 @@ class HealthRegistry {
|
|
|
1400
1246
|
Object.defineProperty(HealthRegistry, Symbol.for("dunx.deps"), { value: () => [HealthOptions, { unresolved: "private readonly readiness_: Readiness", typeOnly: "Readiness" }] });
|
|
1401
1247
|
|
|
1402
1248
|
// src/health/controller.ts
|
|
1249
|
+
import { inject } from "@dunx/core";
|
|
1403
1250
|
var probeResponses = {
|
|
1404
1251
|
response: { 200: HEALTH_REPORT_SCHEMA, 503: HEALTH_REPORT_SCHEMA }
|
|
1405
1252
|
};
|
|
@@ -1448,126 +1295,5 @@ HiddenHealthController = __decorateElement(_init, 0, "HiddenHealthController", _
|
|
|
1448
1295
|
__runInitializers(_init, 1, HiddenHealthController);
|
|
1449
1296
|
__decoratorMetadata(_init, HiddenHealthController);
|
|
1450
1297
|
let _HiddenHealthController = HiddenHealthController;
|
|
1451
|
-
// src/client/errors.ts
|
|
1452
|
-
import { AppError as AppError6 } from "@dunx/core";
|
|
1453
|
-
|
|
1454
|
-
class FetchError extends AppError6 {
|
|
1455
|
-
status;
|
|
1456
|
-
statusText;
|
|
1457
|
-
body;
|
|
1458
|
-
response;
|
|
1459
|
-
name = "FetchError";
|
|
1460
|
-
constructor(status, statusText, body, response) {
|
|
1461
|
-
super(`HTTP ${status} ${statusText} from ${response.method} ${response.url}`);
|
|
1462
|
-
this.status = status;
|
|
1463
|
-
this.statusText = statusText;
|
|
1464
|
-
this.body = body;
|
|
1465
|
-
this.response = response;
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
|
-
Object.defineProperty(FetchError, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "readonly status: number" }, { unresolved: "readonly statusText: string" }, { unresolved: "readonly body: unknown" }, { unresolved: `readonly response: {
|
|
1469
|
-
readonly method: string;
|
|
1470
|
-
readonly url: string;
|
|
1471
|
-
readonly headers: Headers;
|
|
1472
|
-
}` }] });
|
|
1473
|
-
|
|
1474
|
-
class FetchTransportError extends AppError6 {
|
|
1475
|
-
response;
|
|
1476
|
-
aborted;
|
|
1477
|
-
name = "FetchTransportError";
|
|
1478
|
-
constructor(response, aborted, options) {
|
|
1479
|
-
super(`${response.method} ${response.url} failed: ${aborted ? "aborted" : "transport error"}`, options);
|
|
1480
|
-
this.response = response;
|
|
1481
|
-
this.aborted = aborted;
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
Object.defineProperty(FetchTransportError, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "readonly response: { readonly method: string; readonly url: string }" }, { unresolved: "readonly aborted: boolean" }, ErrorOptions] });
|
|
1485
1298
|
|
|
1486
|
-
|
|
1487
|
-
var uniform = () => {
|
|
1488
|
-
const buffer = new Uint32Array(1);
|
|
1489
|
-
crypto.getRandomValues(buffer);
|
|
1490
|
-
return (buffer[0] ?? 0) / 2 ** 32;
|
|
1491
|
-
};
|
|
1492
|
-
var backoffDelay = (attempt, { baseMs, power = 2, jitterMs = 1000, maxMs = 30000 }) => Math.min(baseMs * power ** attempt + uniform() * jitterMs, maxMs);
|
|
1493
|
-
var retryAfterMs = (headers, now = Date.now()) => {
|
|
1494
|
-
const header = headers.get("retry-after");
|
|
1495
|
-
if (header === null)
|
|
1496
|
-
return;
|
|
1497
|
-
const seconds = Number(header);
|
|
1498
|
-
if (Number.isFinite(seconds))
|
|
1499
|
-
return Math.max(0, seconds * 1000);
|
|
1500
|
-
const at = Date.parse(header);
|
|
1501
|
-
return Number.isNaN(at) ? undefined : Math.max(0, at - now);
|
|
1502
|
-
};
|
|
1503
|
-
var isRetryableStatus = (status) => status >= HttpStatusCode.INTERNAL_SERVER_ERROR || status === HttpStatusCode.REQUEST_TIMEOUT || status === HttpStatusCode.TOO_MANY_REQUESTS;
|
|
1504
|
-
var decide = (error, attempt, options) => {
|
|
1505
|
-
const {
|
|
1506
|
-
retryDelayMs = 1000,
|
|
1507
|
-
backoff,
|
|
1508
|
-
shouldRetryOnStatus = isRetryableStatus,
|
|
1509
|
-
respectRetryAfter = true
|
|
1510
|
-
} = options;
|
|
1511
|
-
const computed = backoffDelay(attempt, { baseMs: retryDelayMs, ...backoff });
|
|
1512
|
-
if (error instanceof FetchTransportError) {
|
|
1513
|
-
return { retry: !error.aborted, delayMs: computed };
|
|
1514
|
-
}
|
|
1515
|
-
if (error instanceof FetchError) {
|
|
1516
|
-
if (!shouldRetryOnStatus(error.status))
|
|
1517
|
-
return { retry: false, delayMs: 0 };
|
|
1518
|
-
const asked = respectRetryAfter ? retryAfterMs(error.response.headers) : undefined;
|
|
1519
|
-
const maxMs = backoff?.maxMs ?? 30000;
|
|
1520
|
-
return {
|
|
1521
|
-
retry: true,
|
|
1522
|
-
delayMs: asked === undefined ? computed : Math.min(asked, maxMs)
|
|
1523
|
-
};
|
|
1524
|
-
}
|
|
1525
|
-
return { retry: true, delayMs: computed };
|
|
1526
|
-
};
|
|
1527
|
-
var executeWithRetry = async (operation, options = {}) => {
|
|
1528
|
-
const { maxRetries = 3, onAttempt, onError, onSuccess } = options;
|
|
1529
|
-
let lastError;
|
|
1530
|
-
for (let attempt = 0;attempt <= maxRetries; attempt += 1) {
|
|
1531
|
-
onAttempt?.(attempt + 1, attempt > 0);
|
|
1532
|
-
try {
|
|
1533
|
-
const result = await operation();
|
|
1534
|
-
onSuccess?.(result, attempt + 1);
|
|
1535
|
-
return result;
|
|
1536
|
-
} catch (error) {
|
|
1537
|
-
lastError = error;
|
|
1538
|
-
const { retry, delayMs } = decide(error, attempt, options);
|
|
1539
|
-
const willRetry = retry && attempt < maxRetries;
|
|
1540
|
-
onError?.(error, attempt + 1, willRetry);
|
|
1541
|
-
if (!willRetry)
|
|
1542
|
-
throw error;
|
|
1543
|
-
await Bun.sleep(delayMs);
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
throw lastError;
|
|
1547
|
-
};
|
|
1548
|
-
// src/client/json.ts
|
|
1549
|
-
var safeStringify = (value) => {
|
|
1550
|
-
const seen = new WeakSet;
|
|
1551
|
-
return JSON.stringify(value, (_key, entry) => {
|
|
1552
|
-
if (typeof entry === "object" && entry !== null) {
|
|
1553
|
-
if (seen.has(entry))
|
|
1554
|
-
return "[Circular]";
|
|
1555
|
-
seen.add(entry);
|
|
1556
|
-
}
|
|
1557
|
-
return entry;
|
|
1558
|
-
});
|
|
1559
|
-
};
|
|
1560
|
-
var isPlainObject = (value) => {
|
|
1561
|
-
if (typeof value !== "object" || value === null)
|
|
1562
|
-
return false;
|
|
1563
|
-
const proto = Object.getPrototypeOf(value);
|
|
1564
|
-
return proto === Object.prototype || proto === null;
|
|
1565
|
-
};
|
|
1566
|
-
var isJsonBody = (payload) => {
|
|
1567
|
-
if (payload === null || payload === undefined)
|
|
1568
|
-
return false;
|
|
1569
|
-
if (typeof payload !== "object")
|
|
1570
|
-
return typeof payload !== "string";
|
|
1571
|
-
return !(payload instanceof FormData || payload instanceof URLSearchParams || payload instanceof Blob || payload instanceof ArrayBuffer || payload instanceof ReadableStream || ArrayBuffer.isView(payload));
|
|
1572
|
-
};
|
|
1573
|
-
export { __decoratorStart, __decoratorMetadata, __runInitializers, __decorateElement, HttpStatusCode, defaultStatusFor, Controller, Get, Post, Put, Patch, Delete, metaKey, meta, ROLES, PUBLIC, HIDDEN, UNMATCHED, Roles, Public, ApiHidden, UseGuards, guardsOf, metaOf, mergeMeta, HttpError, ValidationError, ErrorFilter, isErrorFilter, toErrorMapper, errorMapper, defaultErrorMapper, joinPath, discoverRoutes, encode, decode, HandlerKind, markHandler, markGateway, isGateway, composeSocket, observe, buildRuntime, buildGateways, buildWebSocket, normalizePath, discoverGateway, discoverGateways, DEFAULT_RELAY_CHANNEL, defaultRelayError, encodeRelay, decodeRelay, RawBody, REQUEST_ID_HEADER, RequestIds, buildContext, withCors, preflight, compose, assertNoCollisions, assertNoGatewayCollisions, withUpgradeRoutes, buildFallback, buildRoutes, StaticOptions, normalizePrefix, negotiate, CompressionEncoding, isCompressibleType, CompressionOptions, defaultRelayUrl, RedisRelay, HEALTH_REPORT_SCHEMA, HealthOptions, HealthRegistry, HealthController, HiddenHealthController, routesOf, gatewaysOf, FetchError, FetchTransportError, backoffDelay, retryAfterMs, isRetryableStatus, executeWithRetry, safeStringify, isPlainObject, isJsonBody };
|
|
1299
|
+
export { defaultStatusFor, Controller, Get, Post, Put, Patch, Delete, metaKey, meta, ROLES, PUBLIC, HIDDEN, UNMATCHED, Roles, Public, ApiHidden, UseGuards, guardsOf, metaOf, mergeMeta, HttpError, ValidationError, ErrorFilter, isErrorFilter, toErrorMapper, errorMapper, defaultErrorMapper, joinPath, discoverRoutes, encode, decode, HandlerKind, markHandler, markGateway, isGateway, composeSocket, observe, buildRuntime, buildGateways, buildWebSocket, normalizePath, discoverGateway, discoverGateways, DEFAULT_RELAY_CHANNEL, defaultRelayError, encodeRelay, decodeRelay, RawBody, REQUEST_ID_HEADER, RequestIds, buildContext, withCors, preflight, compose, assertNoCollisions, assertNoGatewayCollisions, withUpgradeRoutes, buildFallback, buildRoutes, StaticOptions, normalizePrefix, negotiate, CompressionEncoding, isCompressibleType, CompressionOptions, defaultRelayUrl, RedisRelay, HEALTH_REPORT_SCHEMA, HealthOptions, HealthRegistry, HealthController, HiddenHealthController };
|