@forklaunch/express 0.7.12 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.mts +111 -120
- package/lib/index.d.ts +111 -120
- package/lib/index.js +2105 -134
- package/lib/index.mjs +2101 -129
- package/package.json +12 -10
package/lib/index.mjs
CHANGED
@@ -6,15 +6,1886 @@ import {
|
|
6
6
|
generateMcpServer,
|
7
7
|
generateOpenApiSpecs,
|
8
8
|
isForklaunchRequest,
|
9
|
+
isPortBound,
|
9
10
|
meta,
|
10
11
|
OPENAPI_DEFAULT_VERSION
|
11
12
|
} from "@forklaunch/core/http";
|
12
13
|
import { ZodSchemaValidator } from "@forklaunch/validator/zod";
|
13
14
|
import { apiReference } from "@scalar/express-api-reference";
|
14
|
-
import
|
15
|
+
import crypto2 from "crypto";
|
15
16
|
import express2 from "express";
|
16
17
|
import swaggerUi from "swagger-ui-express";
|
17
18
|
|
19
|
+
// src/cluster/bun.cluster.ts
|
20
|
+
import cluster from "cluster";
|
21
|
+
import os from "os";
|
22
|
+
|
23
|
+
// src/cluster/bun.serve.shim.ts
|
24
|
+
import * as crypto from "crypto";
|
25
|
+
import { EventEmitter } from "events";
|
26
|
+
import * as path from "path";
|
27
|
+
import { PassThrough, Readable, Transform } from "stream";
|
28
|
+
import RangeParser from "range-parser";
|
29
|
+
|
30
|
+
// src/cluster/bun.socket.shim.ts
|
31
|
+
import { Duplex } from "stream";
|
32
|
+
var BunSocketShim = class extends Duplex {
|
33
|
+
remoteAddress;
|
34
|
+
remotePort;
|
35
|
+
remoteFamily;
|
36
|
+
localAddress;
|
37
|
+
localPort;
|
38
|
+
localFamily;
|
39
|
+
bytesRead = 0;
|
40
|
+
bytesWritten = 0;
|
41
|
+
connecting = false;
|
42
|
+
pending = false;
|
43
|
+
timeout;
|
44
|
+
readyState = "closed";
|
45
|
+
bufferSize = 0;
|
46
|
+
autoSelectFamilyAttemptedAddresses = [];
|
47
|
+
_destroyed = false;
|
48
|
+
_socketTimeoutId;
|
49
|
+
_abortController;
|
50
|
+
_request;
|
51
|
+
_responseWriter;
|
52
|
+
constructor(options2 = {}) {
|
53
|
+
super({
|
54
|
+
allowHalfOpen: false,
|
55
|
+
objectMode: false
|
56
|
+
});
|
57
|
+
this.remoteAddress = options2.remoteAddress || "127.0.0.1";
|
58
|
+
this.remotePort = options2.remotePort || 0;
|
59
|
+
this.remoteFamily = "IPv4";
|
60
|
+
this.localAddress = options2.localAddress || "localhost";
|
61
|
+
this.localPort = options2.localPort || 0;
|
62
|
+
this.localFamily = "IPv4";
|
63
|
+
this._abortController = options2.abortController || new AbortController();
|
64
|
+
this._request = options2.request;
|
65
|
+
this._responseWriter = options2.responseWriter;
|
66
|
+
this.readyState = "open";
|
67
|
+
this._setupAbortHandling();
|
68
|
+
this._setupRequestReading();
|
69
|
+
}
|
70
|
+
_setupAbortHandling() {
|
71
|
+
if (this._abortController?.signal) {
|
72
|
+
this._abortController.signal.addEventListener("abort", () => {
|
73
|
+
if (!this._destroyed) {
|
74
|
+
this.destroy(new Error("Socket aborted"));
|
75
|
+
}
|
76
|
+
});
|
77
|
+
}
|
78
|
+
}
|
79
|
+
_setupRequestReading() {
|
80
|
+
if (this._request?.body) {
|
81
|
+
const reader = this._request.body.getReader();
|
82
|
+
const pump = async () => {
|
83
|
+
try {
|
84
|
+
while (!this._destroyed) {
|
85
|
+
const { done, value } = await reader.read();
|
86
|
+
if (done) break;
|
87
|
+
this.bytesRead += value.length;
|
88
|
+
this.push(Buffer.from(value));
|
89
|
+
}
|
90
|
+
this.push(null);
|
91
|
+
} catch (error) {
|
92
|
+
if (!this._destroyed) {
|
93
|
+
this.destroy(
|
94
|
+
error instanceof Error ? error : new Error(String(error))
|
95
|
+
);
|
96
|
+
}
|
97
|
+
}
|
98
|
+
};
|
99
|
+
pump();
|
100
|
+
}
|
101
|
+
}
|
102
|
+
_read(_size) {
|
103
|
+
}
|
104
|
+
_write(chunk, encoding, callback) {
|
105
|
+
try {
|
106
|
+
if (this._destroyed || !this.writable) {
|
107
|
+
callback(new Error("Socket is not writable"));
|
108
|
+
return;
|
109
|
+
}
|
110
|
+
let buffer;
|
111
|
+
if (Buffer.isBuffer(chunk)) {
|
112
|
+
buffer = chunk;
|
113
|
+
} else if (chunk instanceof Uint8Array) {
|
114
|
+
buffer = Buffer.from(chunk);
|
115
|
+
} else {
|
116
|
+
buffer = Buffer.from(chunk, encoding);
|
117
|
+
}
|
118
|
+
this.bytesWritten += buffer.length;
|
119
|
+
if (this._responseWriter) {
|
120
|
+
this._responseWriter.write(new Uint8Array(buffer)).then(() => callback()).catch(callback);
|
121
|
+
} else {
|
122
|
+
queueMicrotask(() => callback());
|
123
|
+
}
|
124
|
+
} catch (error) {
|
125
|
+
callback(error instanceof Error ? error : new Error(String(error)));
|
126
|
+
}
|
127
|
+
}
|
128
|
+
_final(callback) {
|
129
|
+
if (this._responseWriter) {
|
130
|
+
this._responseWriter.close().then(() => callback()).catch(callback);
|
131
|
+
} else {
|
132
|
+
callback();
|
133
|
+
}
|
134
|
+
}
|
135
|
+
_destroy(error, callback) {
|
136
|
+
this._destroyed = true;
|
137
|
+
this.readyState = "closed";
|
138
|
+
this.connecting = false;
|
139
|
+
this.pending = false;
|
140
|
+
if (this._socketTimeoutId) {
|
141
|
+
clearTimeout(this._socketTimeoutId);
|
142
|
+
this._socketTimeoutId = void 0;
|
143
|
+
}
|
144
|
+
if (this._responseWriter && !this._responseWriter.closed) {
|
145
|
+
this._responseWriter.abort().catch(() => {
|
146
|
+
});
|
147
|
+
}
|
148
|
+
if (this._abortController && !this._abortController.signal.aborted) {
|
149
|
+
this._abortController.abort();
|
150
|
+
}
|
151
|
+
callback(error);
|
152
|
+
}
|
153
|
+
destroyed = this._destroyed;
|
154
|
+
destroy(error) {
|
155
|
+
if (this._destroyed) return this;
|
156
|
+
super.destroy(error);
|
157
|
+
return this;
|
158
|
+
}
|
159
|
+
destroySoon() {
|
160
|
+
this.end();
|
161
|
+
}
|
162
|
+
connect(optionsPortOrPath, hostOrConnectionListener, connectionListener) {
|
163
|
+
let port;
|
164
|
+
let host;
|
165
|
+
let listener;
|
166
|
+
if (typeof optionsPortOrPath === "number") {
|
167
|
+
port = optionsPortOrPath;
|
168
|
+
if (typeof hostOrConnectionListener === "string") {
|
169
|
+
host = hostOrConnectionListener;
|
170
|
+
listener = connectionListener;
|
171
|
+
} else if (typeof hostOrConnectionListener === "function") {
|
172
|
+
host = "localhost";
|
173
|
+
listener = hostOrConnectionListener;
|
174
|
+
} else {
|
175
|
+
host = "localhost";
|
176
|
+
}
|
177
|
+
this.remoteAddress = host;
|
178
|
+
this.remotePort = port;
|
179
|
+
this.localAddress = "localhost";
|
180
|
+
this.localPort = 0;
|
181
|
+
this.remoteFamily = "IPv4";
|
182
|
+
this.localFamily = "IPv4";
|
183
|
+
} else if (typeof optionsPortOrPath === "string") {
|
184
|
+
this.remoteAddress = optionsPortOrPath;
|
185
|
+
this.remotePort = void 0;
|
186
|
+
this.localAddress = optionsPortOrPath;
|
187
|
+
this.localPort = void 0;
|
188
|
+
this.remoteFamily = "Unix";
|
189
|
+
this.localFamily = "Unix";
|
190
|
+
listener = typeof hostOrConnectionListener === "function" ? hostOrConnectionListener : connectionListener;
|
191
|
+
} else {
|
192
|
+
const options2 = optionsPortOrPath;
|
193
|
+
if ("path" in options2) {
|
194
|
+
this.remoteAddress = options2.path;
|
195
|
+
this.remotePort = void 0;
|
196
|
+
this.localAddress = options2.path;
|
197
|
+
this.localPort = void 0;
|
198
|
+
this.remoteFamily = "Unix";
|
199
|
+
this.localFamily = "Unix";
|
200
|
+
} else {
|
201
|
+
port = options2.port;
|
202
|
+
host = options2.host || "localhost";
|
203
|
+
this.remoteAddress = host;
|
204
|
+
this.remotePort = port;
|
205
|
+
this.localAddress = options2.localAddress || "localhost";
|
206
|
+
this.localPort = options2.localPort || 0;
|
207
|
+
this.remoteFamily = typeof options2.family === "number" ? "IPv4" : options2.family || "IPv4";
|
208
|
+
this.localFamily = typeof options2.family === "number" ? "IPv4" : options2.family || "IPv4";
|
209
|
+
}
|
210
|
+
listener = typeof hostOrConnectionListener === "function" ? hostOrConnectionListener : connectionListener;
|
211
|
+
}
|
212
|
+
this.connecting = true;
|
213
|
+
this.pending = true;
|
214
|
+
this.readyState = "opening";
|
215
|
+
if (listener) {
|
216
|
+
this.once("connect", listener);
|
217
|
+
}
|
218
|
+
queueMicrotask(() => {
|
219
|
+
if (this._destroyed) return;
|
220
|
+
this.connecting = false;
|
221
|
+
this.pending = false;
|
222
|
+
this.readyState = "open";
|
223
|
+
this.remotePort = port;
|
224
|
+
this.remoteAddress = host;
|
225
|
+
this.emit("connect");
|
226
|
+
});
|
227
|
+
return this;
|
228
|
+
}
|
229
|
+
setTimeout(timeout, callback) {
|
230
|
+
if (this._socketTimeoutId) {
|
231
|
+
clearTimeout(this._socketTimeoutId);
|
232
|
+
}
|
233
|
+
this.timeout = timeout;
|
234
|
+
if (timeout > 0) {
|
235
|
+
this._socketTimeoutId = setTimeout(() => {
|
236
|
+
if (this._destroyed) return;
|
237
|
+
this.emit("timeout");
|
238
|
+
if (callback) {
|
239
|
+
try {
|
240
|
+
callback();
|
241
|
+
} catch (error) {
|
242
|
+
this.emit("error", error);
|
243
|
+
}
|
244
|
+
}
|
245
|
+
}, timeout);
|
246
|
+
}
|
247
|
+
return this;
|
248
|
+
}
|
249
|
+
setNoDelay(_noDelay = true) {
|
250
|
+
return this;
|
251
|
+
}
|
252
|
+
setKeepAlive(enable = false, initialDelay = 0) {
|
253
|
+
return this;
|
254
|
+
}
|
255
|
+
address() {
|
256
|
+
if (!this.localAddress || !this.localPort) {
|
257
|
+
return {};
|
258
|
+
}
|
259
|
+
return {
|
260
|
+
address: this.localAddress,
|
261
|
+
family: this.localFamily || "IPv4",
|
262
|
+
port: this.localPort
|
263
|
+
};
|
264
|
+
}
|
265
|
+
unref() {
|
266
|
+
if (this._socketTimeoutId && typeof this._socketTimeoutId === "object" && "unref" in this._socketTimeoutId) {
|
267
|
+
this._socketTimeoutId.unref();
|
268
|
+
}
|
269
|
+
return this;
|
270
|
+
}
|
271
|
+
ref() {
|
272
|
+
if (this._socketTimeoutId && typeof this._socketTimeoutId === "object" && "ref" in this._socketTimeoutId) {
|
273
|
+
this._socketTimeoutId.ref();
|
274
|
+
}
|
275
|
+
return this;
|
276
|
+
}
|
277
|
+
setEncoding(encoding) {
|
278
|
+
super.setEncoding(encoding || "utf-8");
|
279
|
+
return this;
|
280
|
+
}
|
281
|
+
resetAndDestroy() {
|
282
|
+
this.destroy();
|
283
|
+
return this;
|
284
|
+
}
|
285
|
+
end(chunkOrCb, encodingOrCb, cb) {
|
286
|
+
let chunk;
|
287
|
+
let encoding;
|
288
|
+
let callback;
|
289
|
+
if (typeof chunkOrCb === "function") {
|
290
|
+
callback = chunkOrCb;
|
291
|
+
} else {
|
292
|
+
chunk = chunkOrCb;
|
293
|
+
if (typeof encodingOrCb === "function") {
|
294
|
+
callback = encodingOrCb;
|
295
|
+
} else {
|
296
|
+
encoding = encodingOrCb;
|
297
|
+
callback = cb;
|
298
|
+
}
|
299
|
+
}
|
300
|
+
if (callback) {
|
301
|
+
this.once("finish", callback);
|
302
|
+
}
|
303
|
+
super.end(chunk, encoding || "utf-8");
|
304
|
+
return this;
|
305
|
+
}
|
306
|
+
write(chunk, encodingOrCb, cb) {
|
307
|
+
let encoding;
|
308
|
+
let callback;
|
309
|
+
if (typeof encodingOrCb === "function") {
|
310
|
+
callback = encodingOrCb;
|
311
|
+
} else {
|
312
|
+
encoding = encodingOrCb;
|
313
|
+
callback = cb;
|
314
|
+
}
|
315
|
+
return super.write(chunk, encoding || "utf-8", callback);
|
316
|
+
}
|
317
|
+
};
|
318
|
+
function createBunSocket(options2 = {}) {
|
319
|
+
return new BunSocketShim(options2);
|
320
|
+
}
|
321
|
+
function createSocketFromBunRequest(request, serverInfo) {
|
322
|
+
const url = new URL(request.url);
|
323
|
+
const forwardedFor = request.headers.get("x-forwarded-for");
|
324
|
+
const realIP = request.headers.get("x-real-ip");
|
325
|
+
const cfConnectingIP = request.headers.get("cf-connecting-ip");
|
326
|
+
const remoteAddress = forwardedFor?.split(",")[0]?.trim() || realIP || cfConnectingIP || "127.0.0.1";
|
327
|
+
return createBunSocket({
|
328
|
+
remoteAddress,
|
329
|
+
remotePort: 0,
|
330
|
+
localAddress: serverInfo.hostname,
|
331
|
+
localPort: serverInfo.port,
|
332
|
+
request
|
333
|
+
});
|
334
|
+
}
|
335
|
+
|
336
|
+
// src/cluster/bun.serve.shim.ts
|
337
|
+
function toNodeReadable(stream, signal) {
|
338
|
+
const out = new PassThrough();
|
339
|
+
(async () => {
|
340
|
+
try {
|
341
|
+
if (!stream) {
|
342
|
+
out.end();
|
343
|
+
return;
|
344
|
+
}
|
345
|
+
if (signal?.aborted) {
|
346
|
+
out.destroy(new Error("Request aborted"));
|
347
|
+
return;
|
348
|
+
}
|
349
|
+
const reader = stream.getReader();
|
350
|
+
if (signal) {
|
351
|
+
signal.addEventListener("abort", () => {
|
352
|
+
reader.cancel();
|
353
|
+
out.destroy(new Error("Request aborted"));
|
354
|
+
});
|
355
|
+
}
|
356
|
+
while (true) {
|
357
|
+
const { done, value } = await reader.read();
|
358
|
+
if (done) break;
|
359
|
+
if (value) out.write(Buffer.from(value));
|
360
|
+
}
|
361
|
+
out.end();
|
362
|
+
} catch (e) {
|
363
|
+
out.destroy(e);
|
364
|
+
}
|
365
|
+
})();
|
366
|
+
return out;
|
367
|
+
}
|
368
|
+
function extMime(filePath) {
|
369
|
+
const i = filePath.lastIndexOf(".");
|
370
|
+
const ext = i >= 0 ? filePath.slice(i + 1).toLowerCase() : "";
|
371
|
+
const mimeTypes = {
|
372
|
+
html: "text/html; charset=utf-8",
|
373
|
+
htm: "text/html; charset=utf-8",
|
374
|
+
css: "text/css; charset=utf-8",
|
375
|
+
js: "text/javascript; charset=utf-8",
|
376
|
+
mjs: "text/javascript; charset=utf-8",
|
377
|
+
json: "application/json; charset=utf-8",
|
378
|
+
txt: "text/plain; charset=utf-8",
|
379
|
+
md: "text/markdown; charset=utf-8",
|
380
|
+
xml: "application/xml; charset=utf-8",
|
381
|
+
png: "image/png",
|
382
|
+
jpg: "image/jpeg",
|
383
|
+
jpeg: "image/jpeg",
|
384
|
+
gif: "image/gif",
|
385
|
+
svg: "image/svg+xml; charset=utf-8",
|
386
|
+
ico: "image/x-icon",
|
387
|
+
webp: "image/webp",
|
388
|
+
pdf: "application/pdf",
|
389
|
+
zip: "application/zip",
|
390
|
+
mp3: "audio/mpeg",
|
391
|
+
mp4: "video/mp4",
|
392
|
+
woff: "font/woff",
|
393
|
+
woff2: "font/woff2",
|
394
|
+
ttf: "font/ttf",
|
395
|
+
eot: "application/vnd.ms-fontobject"
|
396
|
+
};
|
397
|
+
return mimeTypes[ext];
|
398
|
+
}
|
399
|
+
function serializeCookie(name, value, opts = {}) {
|
400
|
+
const enc = (v) => encodeURIComponent(v);
|
401
|
+
let str = `${name}=${enc(value)}`;
|
402
|
+
if (opts.maxAge != null) str += `; Max-Age=${Math.floor(opts.maxAge)}`;
|
403
|
+
if (opts.domain) str += `; Domain=${opts.domain}`;
|
404
|
+
str += `; Path=${opts.path || "/"}`;
|
405
|
+
if (opts.expires) str += `; Expires=${opts.expires.toUTCString()}`;
|
406
|
+
if (opts.httpOnly) str += `; HttpOnly`;
|
407
|
+
if (opts.secure) str += `; Secure`;
|
408
|
+
if (opts.sameSite) {
|
409
|
+
if (typeof opts.sameSite === "boolean") {
|
410
|
+
str += `; SameSite=Strict`;
|
411
|
+
} else {
|
412
|
+
str += `; SameSite=${opts.sameSite}`;
|
413
|
+
}
|
414
|
+
}
|
415
|
+
return str;
|
416
|
+
}
|
417
|
+
function signCookie(value, secret) {
|
418
|
+
return value + "." + crypto.createHmac("sha256", secret).update(value).digest("base64url");
|
419
|
+
}
|
420
|
+
function unsignCookie(signedValue, secret) {
|
421
|
+
const lastDot = signedValue.lastIndexOf(".");
|
422
|
+
if (lastDot === -1) return false;
|
423
|
+
const value = signedValue.slice(0, lastDot);
|
424
|
+
const signature = signedValue.slice(lastDot + 1);
|
425
|
+
const expected = crypto.createHmac("sha256", secret).update(value).digest("base64url");
|
426
|
+
return signature === expected ? value : false;
|
427
|
+
}
|
428
|
+
function parseCookies(request) {
|
429
|
+
const cookieHeader = request.headers.get("cookie");
|
430
|
+
const cookies = {};
|
431
|
+
if (cookieHeader) {
|
432
|
+
cookieHeader.split(";").forEach((cookie) => {
|
433
|
+
const [name, value] = cookie.trim().split("=");
|
434
|
+
if (name && value) {
|
435
|
+
cookies[decodeURIComponent(name)] = decodeURIComponent(value);
|
436
|
+
}
|
437
|
+
});
|
438
|
+
}
|
439
|
+
return cookies;
|
440
|
+
}
|
441
|
+
function parseSignedCookies(request, secret) {
|
442
|
+
const cookies = parseCookies(request);
|
443
|
+
const signedCookies = {};
|
444
|
+
Object.entries(cookies).forEach(([name, value]) => {
|
445
|
+
if (name.startsWith("s:")) {
|
446
|
+
const cookieName = name.slice(2);
|
447
|
+
const unsigned = unsignCookie(value, secret);
|
448
|
+
if (unsigned !== false) {
|
449
|
+
signedCookies[cookieName] = unsigned;
|
450
|
+
}
|
451
|
+
}
|
452
|
+
});
|
453
|
+
return signedCookies;
|
454
|
+
}
|
455
|
+
function serveExpress(app, openTelemetryCollector, opts = {}) {
|
456
|
+
const {
|
457
|
+
port = Number(process.env.PORT || 3e3),
|
458
|
+
host = "0.0.0.0",
|
459
|
+
reusePort = false,
|
460
|
+
development = process.env.NODE_ENV !== "production",
|
461
|
+
idleTimeout,
|
462
|
+
tls: tls2,
|
463
|
+
cookieSecret = process.env.COOKIE_SECRET || "default-secret"
|
464
|
+
} = opts;
|
465
|
+
let serverClosed = false;
|
466
|
+
const server = Bun.serve({
|
467
|
+
port,
|
468
|
+
hostname: host,
|
469
|
+
reusePort,
|
470
|
+
development,
|
471
|
+
...idleTimeout ? { idleTimeout } : {},
|
472
|
+
...tls2 ? { tls: tls2 } : {},
|
473
|
+
async fetch(request) {
|
474
|
+
const url = new URL(request.url);
|
475
|
+
const headers = {};
|
476
|
+
Object.entries(request.headers).forEach(([k, v]) => {
|
477
|
+
headers[k.toLowerCase()] = v;
|
478
|
+
});
|
479
|
+
const forwardedFor = headers["x-forwarded-for"];
|
480
|
+
const ip = (forwardedFor ? Array.isArray(forwardedFor) ? forwardedFor[0] : forwardedFor.split(",")[0].trim() : void 0) || headers["x-real-ip"] || headers["x-client-ip"] || "127.0.0.1";
|
481
|
+
const proto = headers["x-forwarded-proto"] || (url.protocol === "https:" ? "https" : "http");
|
482
|
+
const nodeReq = toNodeReadable(request.body);
|
483
|
+
let reqTimeoutId;
|
484
|
+
let resTimeoutId;
|
485
|
+
let socketTimeoutId;
|
486
|
+
const socket = createSocketFromBunRequest(request, {
|
487
|
+
hostname: host,
|
488
|
+
port
|
489
|
+
});
|
490
|
+
let ended = false;
|
491
|
+
const headerMap = new Headers();
|
492
|
+
let statusCode = 200;
|
493
|
+
let headersSent = false;
|
494
|
+
const resEE = new EventEmitter();
|
495
|
+
const pathname = url.pathname;
|
496
|
+
const searchParams = url.searchParams;
|
497
|
+
const query = Object.fromEntries(searchParams.entries());
|
498
|
+
const cookies = parseCookies(request);
|
499
|
+
const signedCookies = parseSignedCookies(request, cookieSecret);
|
500
|
+
const abort = new AbortController();
|
501
|
+
const ts = new TransformStream();
|
502
|
+
const writer = ts.writable.getWriter();
|
503
|
+
let responded = false;
|
504
|
+
let resolveResponse;
|
505
|
+
const responsePromise = new Promise(
|
506
|
+
(r) => resolveResponse = r
|
507
|
+
);
|
508
|
+
function ensureResponse() {
|
509
|
+
if (responded) return;
|
510
|
+
responded = true;
|
511
|
+
const finalHeaders = new Headers(headerMap);
|
512
|
+
if (!finalHeaders.has("X-Powered-By")) {
|
513
|
+
finalHeaders.set("X-Powered-By", "Express");
|
514
|
+
}
|
515
|
+
resolveResponse(
|
516
|
+
new Response(ts.readable, {
|
517
|
+
status: statusCode,
|
518
|
+
statusText: res.statusMessage || void 0,
|
519
|
+
headers: finalHeaders
|
520
|
+
})
|
521
|
+
);
|
522
|
+
}
|
523
|
+
function cleanup() {
|
524
|
+
Object.assign(req, {
|
525
|
+
complete: true,
|
526
|
+
destroyed: true,
|
527
|
+
closed: true
|
528
|
+
});
|
529
|
+
Object.assign(res, {
|
530
|
+
finished: true,
|
531
|
+
writableEnded: true
|
532
|
+
});
|
533
|
+
if (reqTimeoutId) {
|
534
|
+
clearTimeout(reqTimeoutId);
|
535
|
+
reqTimeoutId = void 0;
|
536
|
+
}
|
537
|
+
if (resTimeoutId) {
|
538
|
+
clearTimeout(resTimeoutId);
|
539
|
+
resTimeoutId = void 0;
|
540
|
+
}
|
541
|
+
if (socketTimeoutId) {
|
542
|
+
clearTimeout(socketTimeoutId);
|
543
|
+
socketTimeoutId = void 0;
|
544
|
+
}
|
545
|
+
if (writer && !ended) {
|
546
|
+
writer.close();
|
547
|
+
}
|
548
|
+
req.emit("end");
|
549
|
+
req.emit("close");
|
550
|
+
res.emit("close");
|
551
|
+
}
|
552
|
+
const res = Object.assign(resEE, {
|
553
|
+
statusCode,
|
554
|
+
statusMessage: "",
|
555
|
+
headersSent,
|
556
|
+
finished: false,
|
557
|
+
locals: {},
|
558
|
+
charset: "utf-8",
|
559
|
+
strictContentLength: false,
|
560
|
+
writeProcessing: () => {
|
561
|
+
},
|
562
|
+
chunkedEncoding: false,
|
563
|
+
shouldKeepAlive: true,
|
564
|
+
useChunkedEncodingByDefault: true,
|
565
|
+
sendDate: true,
|
566
|
+
connection: socket,
|
567
|
+
socket,
|
568
|
+
writableCorked: 0,
|
569
|
+
writableEnded: false,
|
570
|
+
writableFinished: false,
|
571
|
+
writableHighWaterMark: 16384,
|
572
|
+
writableLength: 0,
|
573
|
+
writableObjectMode: false,
|
574
|
+
destroyed: false,
|
575
|
+
closed: false,
|
576
|
+
errored: null,
|
577
|
+
writableNeedDrain: false,
|
578
|
+
_header: null,
|
579
|
+
_headerSent: false,
|
580
|
+
_headers: {},
|
581
|
+
_headerNames: {},
|
582
|
+
_hasBody: true,
|
583
|
+
_trailer: "",
|
584
|
+
_keepAliveTimeout: 5e3,
|
585
|
+
_last: false,
|
586
|
+
_onPendingData: () => {
|
587
|
+
},
|
588
|
+
_sent100: false,
|
589
|
+
_expect_continue: false,
|
590
|
+
_maxRequestsPerSocket: 0,
|
591
|
+
req: void 0,
|
592
|
+
writable: true,
|
593
|
+
writableAborted: false,
|
594
|
+
setDefaultEncoding: (encoding) => {
|
595
|
+
socket.setEncoding(encoding);
|
596
|
+
return res;
|
597
|
+
},
|
598
|
+
[Symbol.asyncDispose]: async () => {
|
599
|
+
cleanup();
|
600
|
+
},
|
601
|
+
compose: ((stream) => {
|
602
|
+
if (stream && typeof stream === "object" && "pipe" in stream) {
|
603
|
+
return stream;
|
604
|
+
}
|
605
|
+
if (typeof stream === "function") {
|
606
|
+
const transform = new Transform({
|
607
|
+
objectMode: true,
|
608
|
+
transform(chunk, encoding, callback) {
|
609
|
+
try {
|
610
|
+
stream(chunk);
|
611
|
+
callback(null, chunk);
|
612
|
+
} catch (error) {
|
613
|
+
callback(error);
|
614
|
+
}
|
615
|
+
}
|
616
|
+
});
|
617
|
+
return transform;
|
618
|
+
}
|
619
|
+
if (stream && typeof stream === "object" && Symbol.iterator in stream) {
|
620
|
+
const iterator = stream[Symbol.iterator]();
|
621
|
+
const first = iterator.next();
|
622
|
+
if (!first.done && first.value && typeof first.value === "object" && "pipe" in first.value) {
|
623
|
+
return first.value;
|
624
|
+
}
|
625
|
+
}
|
626
|
+
if (stream && typeof stream === "object" && Symbol.asyncIterator in stream) {
|
627
|
+
const readable = new Readable({
|
628
|
+
objectMode: true,
|
629
|
+
read() {
|
630
|
+
this.push(null);
|
631
|
+
}
|
632
|
+
});
|
633
|
+
return readable;
|
634
|
+
}
|
635
|
+
const emptyStream = new Readable({
|
636
|
+
read() {
|
637
|
+
this.push(null);
|
638
|
+
}
|
639
|
+
});
|
640
|
+
return emptyStream;
|
641
|
+
}),
|
642
|
+
setTimeout: (msecs, callback) => {
|
643
|
+
setTimeout(callback || (() => {
|
644
|
+
}), msecs);
|
645
|
+
return res;
|
646
|
+
},
|
647
|
+
setHeaders: (headers2) => {
|
648
|
+
for (const [name, value] of Object.entries(headers2)) {
|
649
|
+
res.setHeader(name, value);
|
650
|
+
}
|
651
|
+
return res;
|
652
|
+
},
|
653
|
+
appendHeader: (name, value) => {
|
654
|
+
return res.append(name, value);
|
655
|
+
},
|
656
|
+
addTrailers: (headers2) => {
|
657
|
+
return res;
|
658
|
+
},
|
659
|
+
flushHeaders: () => {
|
660
|
+
headersSent = true;
|
661
|
+
res.headersSent = true;
|
662
|
+
ensureResponse();
|
663
|
+
},
|
664
|
+
setHeader(name, value) {
|
665
|
+
if (Array.isArray(value)) {
|
666
|
+
headerMap.delete(name);
|
667
|
+
value.forEach((v) => headerMap.append(name, String(v)));
|
668
|
+
} else {
|
669
|
+
headerMap.set(name, String(value));
|
670
|
+
}
|
671
|
+
return res;
|
672
|
+
},
|
673
|
+
getHeader(name) {
|
674
|
+
return headerMap.get(name) ?? void 0;
|
675
|
+
},
|
676
|
+
getHeaders() {
|
677
|
+
const obj = {};
|
678
|
+
headerMap.forEach((v, k) => obj[k] = v);
|
679
|
+
return obj;
|
680
|
+
},
|
681
|
+
getHeaderNames() {
|
682
|
+
const names = [];
|
683
|
+
headerMap.forEach((_, name) => names.push(name));
|
684
|
+
return names;
|
685
|
+
},
|
686
|
+
hasHeader(name) {
|
687
|
+
return headerMap.has(name);
|
688
|
+
},
|
689
|
+
removeHeader(name) {
|
690
|
+
headerMap.delete(name);
|
691
|
+
return res;
|
692
|
+
},
|
693
|
+
writeHead(code, reason, headers2) {
|
694
|
+
if (typeof reason === "object") {
|
695
|
+
headers2 = reason;
|
696
|
+
reason = void 0;
|
697
|
+
}
|
698
|
+
statusCode = code;
|
699
|
+
res.statusCode = statusCode;
|
700
|
+
if (reason) res.statusMessage = reason;
|
701
|
+
if (headers2) {
|
702
|
+
for (const [k, v] of Object.entries(headers2))
|
703
|
+
if (v) res.setHeader(k, v);
|
704
|
+
}
|
705
|
+
headersSent = true;
|
706
|
+
res.headersSent = true;
|
707
|
+
ensureResponse();
|
708
|
+
return res;
|
709
|
+
},
|
710
|
+
write(chunk, encoding, callback) {
|
711
|
+
if (typeof encoding === "function") {
|
712
|
+
callback = encoding;
|
713
|
+
encoding = void 0;
|
714
|
+
}
|
715
|
+
if (ended) {
|
716
|
+
if (callback) callback(new Error("Cannot write after end"));
|
717
|
+
return false;
|
718
|
+
}
|
719
|
+
headersSent = true;
|
720
|
+
res.headersSent = true;
|
721
|
+
ensureResponse();
|
722
|
+
if (chunk == null) {
|
723
|
+
if (callback) callback();
|
724
|
+
return true;
|
725
|
+
}
|
726
|
+
try {
|
727
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk), encoding || "utf8");
|
728
|
+
writer.write(buf);
|
729
|
+
if (callback) callback();
|
730
|
+
return true;
|
731
|
+
} catch (error) {
|
732
|
+
if (callback) callback(error);
|
733
|
+
return false;
|
734
|
+
}
|
735
|
+
},
|
736
|
+
end(chunk, encoding, callback) {
|
737
|
+
if (typeof chunk === "function") {
|
738
|
+
callback = chunk;
|
739
|
+
chunk = void 0;
|
740
|
+
}
|
741
|
+
if (typeof encoding === "function") {
|
742
|
+
callback = encoding;
|
743
|
+
encoding = void 0;
|
744
|
+
}
|
745
|
+
if (ended) {
|
746
|
+
if (callback) callback();
|
747
|
+
return res;
|
748
|
+
}
|
749
|
+
if (chunk) res.write(chunk, encoding || "utf8");
|
750
|
+
ended = true;
|
751
|
+
Object.assign(res, {
|
752
|
+
writableEnded: true
|
753
|
+
});
|
754
|
+
ensureResponse();
|
755
|
+
writer.close().then(() => {
|
756
|
+
resEE.emit("finish");
|
757
|
+
if (callback) callback();
|
758
|
+
});
|
759
|
+
return res;
|
760
|
+
},
|
761
|
+
cork: () => {
|
762
|
+
},
|
763
|
+
uncork: () => {
|
764
|
+
},
|
765
|
+
destroy: (error) => {
|
766
|
+
res.destroyed = true;
|
767
|
+
cleanup();
|
768
|
+
return res;
|
769
|
+
},
|
770
|
+
_destroy: (error, callback) => {
|
771
|
+
callback(error);
|
772
|
+
},
|
773
|
+
_final: (callback) => {
|
774
|
+
callback();
|
775
|
+
},
|
776
|
+
_write: (chunk, encoding, callback) => {
|
777
|
+
callback();
|
778
|
+
},
|
779
|
+
_writev: (chunks, callback) => {
|
780
|
+
callback();
|
781
|
+
},
|
782
|
+
pipe: (destination, options2) => {
|
783
|
+
return destination;
|
784
|
+
},
|
785
|
+
unpipe: (destination) => {
|
786
|
+
return res;
|
787
|
+
},
|
788
|
+
addListener: resEE.addListener.bind(resEE),
|
789
|
+
removeListener: resEE.removeListener.bind(resEE),
|
790
|
+
prependListener: resEE.prependListener.bind(resEE),
|
791
|
+
prependOnceListener: resEE.prependOnceListener.bind(resEE),
|
792
|
+
removeAllListeners: resEE.removeAllListeners.bind(resEE),
|
793
|
+
setMaxListeners: resEE.setMaxListeners.bind(resEE),
|
794
|
+
getMaxListeners: resEE.getMaxListeners.bind(resEE),
|
795
|
+
listeners: resEE.listeners.bind(resEE),
|
796
|
+
rawListeners: resEE.rawListeners.bind(resEE),
|
797
|
+
listenerCount: resEE.listenerCount.bind(resEE),
|
798
|
+
eventNames: resEE.eventNames.bind(resEE),
|
799
|
+
status(code) {
|
800
|
+
statusCode = code;
|
801
|
+
res.statusCode = code;
|
802
|
+
return res;
|
803
|
+
},
|
804
|
+
sendStatus(code) {
|
805
|
+
const statusTexts = {
|
806
|
+
100: "Continue",
|
807
|
+
101: "Switching Protocols",
|
808
|
+
200: "OK",
|
809
|
+
201: "Created",
|
810
|
+
202: "Accepted",
|
811
|
+
203: "Non-Authoritative Information",
|
812
|
+
204: "No Content",
|
813
|
+
205: "Reset Content",
|
814
|
+
206: "Partial Content",
|
815
|
+
300: "Multiple Choices",
|
816
|
+
301: "Moved Permanently",
|
817
|
+
302: "Found",
|
818
|
+
303: "See Other",
|
819
|
+
304: "Not Modified",
|
820
|
+
307: "Temporary Redirect",
|
821
|
+
308: "Permanent Redirect",
|
822
|
+
400: "Bad Request",
|
823
|
+
401: "Unauthorized",
|
824
|
+
402: "Payment Required",
|
825
|
+
403: "Forbidden",
|
826
|
+
404: "Not Found",
|
827
|
+
405: "Method Not Allowed",
|
828
|
+
406: "Not Acceptable",
|
829
|
+
407: "Proxy Authentication Required",
|
830
|
+
408: "Request Timeout",
|
831
|
+
409: "Conflict",
|
832
|
+
410: "Gone",
|
833
|
+
411: "Length Required",
|
834
|
+
412: "Precondition Failed",
|
835
|
+
413: "Payload Too Large",
|
836
|
+
414: "URI Too Long",
|
837
|
+
415: "Unsupported Media Type",
|
838
|
+
416: "Range Not Satisfiable",
|
839
|
+
417: "Expectation Failed",
|
840
|
+
418: "I'm a teapot",
|
841
|
+
422: "Unprocessable Entity",
|
842
|
+
425: "Too Early",
|
843
|
+
426: "Upgrade Required",
|
844
|
+
428: "Precondition Required",
|
845
|
+
429: "Too Many Requests",
|
846
|
+
431: "Request Header Fields Too Large",
|
847
|
+
451: "Unavailable For Legal Reasons",
|
848
|
+
500: "Internal Server Error",
|
849
|
+
501: "Not Implemented",
|
850
|
+
502: "Bad Gateway",
|
851
|
+
503: "Service Unavailable",
|
852
|
+
504: "Gateway Timeout",
|
853
|
+
505: "HTTP Version Not Supported",
|
854
|
+
507: "Insufficient Storage",
|
855
|
+
508: "Loop Detected",
|
856
|
+
510: "Not Extended",
|
857
|
+
511: "Network Authentication Required"
|
858
|
+
};
|
859
|
+
const statusText = statusTexts[code] || "Unknown Status";
|
860
|
+
res.status(code).send(statusText);
|
861
|
+
return res;
|
862
|
+
},
|
863
|
+
set(field, value) {
|
864
|
+
if (typeof field === "string" && value !== void 0) {
|
865
|
+
res.setHeader(field, value);
|
866
|
+
} else if (typeof field === "object") {
|
867
|
+
for (const [k, v] of Object.entries(field)) res.setHeader(k, v);
|
868
|
+
}
|
869
|
+
return res;
|
870
|
+
},
|
871
|
+
header(name, value) {
|
872
|
+
if (value !== void 0) {
|
873
|
+
res.setHeader(name, value);
|
874
|
+
return res;
|
875
|
+
}
|
876
|
+
return res;
|
877
|
+
},
|
878
|
+
get(name) {
|
879
|
+
return res.getHeader(name);
|
880
|
+
},
|
881
|
+
type(contentType) {
|
882
|
+
if (contentType.includes("/")) {
|
883
|
+
res.setHeader("Content-Type", contentType);
|
884
|
+
} else {
|
885
|
+
const mime = extMime(`file.${contentType}`);
|
886
|
+
if (mime) res.setHeader("Content-Type", mime);
|
887
|
+
}
|
888
|
+
return res;
|
889
|
+
},
|
890
|
+
contentType(type) {
|
891
|
+
return res.type(type);
|
892
|
+
},
|
893
|
+
location(url2) {
|
894
|
+
res.setHeader("Location", url2);
|
895
|
+
return res;
|
896
|
+
},
|
897
|
+
redirect(arg1, arg2) {
|
898
|
+
let code = 302;
|
899
|
+
let location = "";
|
900
|
+
if (typeof arg1 === "number") {
|
901
|
+
code = arg1;
|
902
|
+
location = String(arg2);
|
903
|
+
} else {
|
904
|
+
location = String(arg1);
|
905
|
+
}
|
906
|
+
res.status(code);
|
907
|
+
res.setHeader("Location", location);
|
908
|
+
if (req.accepts("html")) {
|
909
|
+
return res.type("html").end(
|
910
|
+
`<p>Redirecting to <a href="${location}">${location}</a></p>`
|
911
|
+
);
|
912
|
+
} else {
|
913
|
+
return res.type("txt").end(`Redirecting to ${location}`);
|
914
|
+
}
|
915
|
+
},
|
916
|
+
json(obj) {
|
917
|
+
if (!headerMap.has("content-type"))
|
918
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
919
|
+
return res.end(JSON.stringify(obj));
|
920
|
+
},
|
921
|
+
jsonp(obj) {
|
922
|
+
const callback = req.query.callback || req.query.jsonp;
|
923
|
+
if (callback && typeof callback === "string") {
|
924
|
+
res.setHeader("Content-Type", "text/javascript; charset=utf-8");
|
925
|
+
const callbackName = callback.replace(/[^\w$]/g, "");
|
926
|
+
return res.end(
|
927
|
+
`/**/ typeof ${callbackName} === 'function' && ${callbackName}(${JSON.stringify(obj)});`
|
928
|
+
);
|
929
|
+
}
|
930
|
+
return res.json(obj);
|
931
|
+
},
|
932
|
+
send(body) {
|
933
|
+
if (body == null) return res.end();
|
934
|
+
if (Buffer.isBuffer(body)) return res.end(body);
|
935
|
+
if (typeof body === "object") {
|
936
|
+
if (!headerMap.has("content-type"))
|
937
|
+
res.type("application/json; charset=utf-8");
|
938
|
+
return res.end(JSON.stringify(body));
|
939
|
+
}
|
940
|
+
if (typeof body === "number") {
|
941
|
+
return res.sendStatus(body);
|
942
|
+
}
|
943
|
+
if (!headerMap.has("content-type"))
|
944
|
+
res.type("text/plain; charset=utf-8");
|
945
|
+
return res.end(String(body));
|
946
|
+
},
|
947
|
+
cookie(name, value, options2) {
|
948
|
+
const opts2 = options2 || {};
|
949
|
+
let cookieValue = value;
|
950
|
+
if (opts2.signed) {
|
951
|
+
cookieValue = signCookie(value, cookieSecret);
|
952
|
+
name = `s:${name}`;
|
953
|
+
}
|
954
|
+
headerMap.append(
|
955
|
+
"Set-Cookie",
|
956
|
+
serializeCookie(name, cookieValue, opts2)
|
957
|
+
);
|
958
|
+
return res;
|
959
|
+
},
|
960
|
+
clearCookie(name, options2) {
|
961
|
+
const opts2 = { ...options2 || {}, expires: /* @__PURE__ */ new Date(0), maxAge: 0 };
|
962
|
+
headerMap.append("Set-Cookie", serializeCookie(name, "", opts2));
|
963
|
+
return res;
|
964
|
+
},
|
965
|
+
sendFile: function(filePath, optionsOrFn, fn) {
|
966
|
+
let options2 = {};
|
967
|
+
let callback;
|
968
|
+
if (typeof optionsOrFn === "function") {
|
969
|
+
callback = optionsOrFn;
|
970
|
+
} else {
|
971
|
+
options2 = optionsOrFn || {};
|
972
|
+
callback = fn;
|
973
|
+
}
|
974
|
+
(async () => {
|
975
|
+
try {
|
976
|
+
const fullPath = options2.root ? path.join(options2.root, filePath) : filePath;
|
977
|
+
if (options2.dotfiles === "deny" && path.basename(fullPath).startsWith(".")) {
|
978
|
+
res.status(403);
|
979
|
+
res.end("Forbidden");
|
980
|
+
if (callback) callback(new Error("Forbidden"));
|
981
|
+
return;
|
982
|
+
}
|
983
|
+
const file = Bun.file(fullPath);
|
984
|
+
if (!await file.exists()) {
|
985
|
+
res.status(404);
|
986
|
+
res.end("Not Found");
|
987
|
+
if (callback) callback(new Error("Not Found"));
|
988
|
+
return;
|
989
|
+
}
|
990
|
+
const stats = await file.size;
|
991
|
+
const lastModified = new Date(file.lastModified);
|
992
|
+
if (options2.cacheControl !== false) {
|
993
|
+
const maxAge = options2.maxAge || 0;
|
994
|
+
res.setHeader("Cache-Control", `public, max-age=${maxAge}`);
|
995
|
+
}
|
996
|
+
if (options2.lastModified !== false) {
|
997
|
+
res.setHeader("Last-Modified", lastModified.toUTCString());
|
998
|
+
}
|
999
|
+
if (options2.etag !== false) {
|
1000
|
+
const etag = `"${stats}-${lastModified.getTime()}"`;
|
1001
|
+
res.setHeader("ETag", etag);
|
1002
|
+
}
|
1003
|
+
const ifModifiedSince = req.get("if-modified-since");
|
1004
|
+
const ifNoneMatch = req.get("if-none-match");
|
1005
|
+
if (ifModifiedSince && new Date(ifModifiedSince) >= lastModified) {
|
1006
|
+
res.status(304).end();
|
1007
|
+
if (callback) callback(void 0);
|
1008
|
+
return;
|
1009
|
+
}
|
1010
|
+
if (ifNoneMatch && ifNoneMatch === res.getHeader("ETag")) {
|
1011
|
+
res.status(304).end();
|
1012
|
+
if (callback) callback(void 0);
|
1013
|
+
return;
|
1014
|
+
}
|
1015
|
+
const mime = extMime(fullPath);
|
1016
|
+
if (mime) res.setHeader("Content-Type", mime);
|
1017
|
+
res.setHeader("Content-Length", stats.toString());
|
1018
|
+
if (options2.headers) {
|
1019
|
+
for (const [k, v] of Object.entries(options2.headers))
|
1020
|
+
if (v) res.setHeader(k, v);
|
1021
|
+
}
|
1022
|
+
headersSent = true;
|
1023
|
+
res.headersSent = true;
|
1024
|
+
ensureResponse();
|
1025
|
+
const reader = file.stream().getReader();
|
1026
|
+
try {
|
1027
|
+
while (true) {
|
1028
|
+
const { done, value } = await reader.read();
|
1029
|
+
if (done) break;
|
1030
|
+
if (value) await writer.write(value);
|
1031
|
+
}
|
1032
|
+
} finally {
|
1033
|
+
await writer.close();
|
1034
|
+
resEE.emit("finish");
|
1035
|
+
if (callback) callback(void 0);
|
1036
|
+
}
|
1037
|
+
} catch (error) {
|
1038
|
+
if (callback) callback(error);
|
1039
|
+
}
|
1040
|
+
})();
|
1041
|
+
},
|
1042
|
+
download(filePath, filename, options2, callback) {
|
1043
|
+
const downloadFilename = filename || path.basename(filePath);
|
1044
|
+
res.setHeader(
|
1045
|
+
"Content-Disposition",
|
1046
|
+
`attachment; filename="${downloadFilename}"`
|
1047
|
+
);
|
1048
|
+
if (typeof options2 === "function") {
|
1049
|
+
callback = options2;
|
1050
|
+
options2 = {};
|
1051
|
+
}
|
1052
|
+
res.sendFile(filePath, options2 || {});
|
1053
|
+
return res;
|
1054
|
+
},
|
1055
|
+
render: (view, locals, callback) => {
|
1056
|
+
const errback = typeof locals === "function" ? locals : callback;
|
1057
|
+
try {
|
1058
|
+
const templateLocals = {
|
1059
|
+
...res.locals,
|
1060
|
+
...typeof locals === "function" ? {} : locals
|
1061
|
+
};
|
1062
|
+
let html = `<!DOCTYPE html><html><head><title>${view}</title></head><body>`;
|
1063
|
+
html += `<h1>View: ${view}</h1>`;
|
1064
|
+
html += `<pre>${JSON.stringify(templateLocals, null, 2)}</pre>`;
|
1065
|
+
html += `</body></html>`;
|
1066
|
+
if (errback) {
|
1067
|
+
errback(new Error("Template rendering error"), html);
|
1068
|
+
} else {
|
1069
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
1070
|
+
res.send(html);
|
1071
|
+
}
|
1072
|
+
} catch (error) {
|
1073
|
+
if (errback) {
|
1074
|
+
errback(error, "");
|
1075
|
+
} else {
|
1076
|
+
res.status(500).send("Template rendering error");
|
1077
|
+
}
|
1078
|
+
}
|
1079
|
+
return res;
|
1080
|
+
},
|
1081
|
+
links(links) {
|
1082
|
+
const existing = res.getHeader("Link");
|
1083
|
+
const linkHeader = Object.entries(links).map(([rel, url2]) => `<${url2}>; rel="${rel}"`).join(", ");
|
1084
|
+
if (existing) {
|
1085
|
+
res.setHeader("Link", `${existing}, ${linkHeader}`);
|
1086
|
+
} else {
|
1087
|
+
res.setHeader("Link", linkHeader);
|
1088
|
+
}
|
1089
|
+
return res;
|
1090
|
+
},
|
1091
|
+
sendfile: function(path2, options2, callback) {
|
1092
|
+
if (!callback && !options2) {
|
1093
|
+
return res.sendFile(path2);
|
1094
|
+
} else if (!callback && options2) {
|
1095
|
+
return res.sendFile(path2, options2);
|
1096
|
+
} else if (callback && options2) {
|
1097
|
+
return res.sendFile(path2, options2, callback);
|
1098
|
+
}
|
1099
|
+
},
|
1100
|
+
attachment(filename) {
|
1101
|
+
if (filename) {
|
1102
|
+
res.setHeader(
|
1103
|
+
"Content-Disposition",
|
1104
|
+
`attachment; filename="${filename}"`
|
1105
|
+
);
|
1106
|
+
const mime = extMime(filename);
|
1107
|
+
if (mime) res.type(mime);
|
1108
|
+
} else {
|
1109
|
+
res.setHeader("Content-Disposition", "attachment");
|
1110
|
+
}
|
1111
|
+
return res;
|
1112
|
+
},
|
1113
|
+
format(obj) {
|
1114
|
+
const accepted = req.accepts(Object.keys(obj));
|
1115
|
+
if (accepted && obj[accepted]) {
|
1116
|
+
return obj[accepted]();
|
1117
|
+
}
|
1118
|
+
const defaultHandler = obj.default;
|
1119
|
+
if (defaultHandler) {
|
1120
|
+
return defaultHandler();
|
1121
|
+
}
|
1122
|
+
return res.status(406).send("Not Acceptable");
|
1123
|
+
},
|
1124
|
+
vary(field) {
|
1125
|
+
const fields = Array.isArray(field) ? field : [field];
|
1126
|
+
const existing = res.getHeader("Vary");
|
1127
|
+
const varyHeader = existing ? `${existing}, ${fields.join(", ")}` : fields.join(", ");
|
1128
|
+
res.setHeader("Vary", varyHeader);
|
1129
|
+
return res;
|
1130
|
+
},
|
1131
|
+
append(field, value) {
|
1132
|
+
const existing = res.getHeader(field);
|
1133
|
+
if (existing) {
|
1134
|
+
const newValue = Array.isArray(value) ? value.join(", ") : value;
|
1135
|
+
res.setHeader(field, `${existing}, ${newValue}`);
|
1136
|
+
} else {
|
1137
|
+
res.setHeader(field, value);
|
1138
|
+
}
|
1139
|
+
return res;
|
1140
|
+
},
|
1141
|
+
writeContinue: () => {
|
1142
|
+
return res;
|
1143
|
+
},
|
1144
|
+
writeEarlyHints: (hints) => {
|
1145
|
+
return res;
|
1146
|
+
},
|
1147
|
+
assignSocket: (socket2) => {
|
1148
|
+
Object.defineProperty(res, "socket", {
|
1149
|
+
value: socket2,
|
1150
|
+
writable: true,
|
1151
|
+
enumerable: false,
|
1152
|
+
configurable: true
|
1153
|
+
});
|
1154
|
+
return res;
|
1155
|
+
},
|
1156
|
+
detachSocket: (socket2) => {
|
1157
|
+
Object.defineProperty(res, "socket", {
|
1158
|
+
value: void 0,
|
1159
|
+
writable: true,
|
1160
|
+
enumerable: false,
|
1161
|
+
configurable: true
|
1162
|
+
});
|
1163
|
+
return res;
|
1164
|
+
},
|
1165
|
+
app
|
1166
|
+
});
|
1167
|
+
const reqEE = new EventEmitter();
|
1168
|
+
Object.getOwnPropertyNames(nodeReq).forEach((key) => {
|
1169
|
+
if (key !== "constructor") {
|
1170
|
+
try {
|
1171
|
+
const descriptor = Object.getOwnPropertyDescriptor(nodeReq, key);
|
1172
|
+
if (descriptor && descriptor.configurable !== false && descriptor.writable !== false) {
|
1173
|
+
Object.defineProperty(reqEE, key, descriptor);
|
1174
|
+
} else {
|
1175
|
+
openTelemetryCollector.info(
|
1176
|
+
"Property not configurable or writable:",
|
1177
|
+
key
|
1178
|
+
);
|
1179
|
+
}
|
1180
|
+
} catch (e) {
|
1181
|
+
openTelemetryCollector.error("Error defining property:", e);
|
1182
|
+
}
|
1183
|
+
}
|
1184
|
+
});
|
1185
|
+
const req = Object.assign(reqEE, {
|
1186
|
+
method: request.method,
|
1187
|
+
url: url.pathname + url.search,
|
1188
|
+
originalUrl: url.pathname + url.search,
|
1189
|
+
headers,
|
1190
|
+
httpVersion: "1.1",
|
1191
|
+
httpVersionMajor: 1,
|
1192
|
+
httpVersionMinor: 1,
|
1193
|
+
complete: false,
|
1194
|
+
socket,
|
1195
|
+
connection: socket,
|
1196
|
+
protocol: proto,
|
1197
|
+
secure: proto === "https",
|
1198
|
+
ip,
|
1199
|
+
ips: forwardedFor ? Array.isArray(forwardedFor) ? forwardedFor : forwardedFor.split(",").map((s) => s.trim()) : [],
|
1200
|
+
hostname: Array.isArray(headers["x-forwarded-host"] || headers["host"]) ? (headers["x-forwarded-host"] || headers["host"])[0].split(":")[0] : (headers["x-forwarded-host"] || headers["host"] || "").split(
|
1201
|
+
":"
|
1202
|
+
)[0],
|
1203
|
+
host: Array.isArray(headers["x-forwarded-host"] || headers["host"]) ? (headers["x-forwarded-host"] || headers["host"])[0] : headers["x-forwarded-host"] || headers["host"] || "",
|
1204
|
+
path: url.pathname,
|
1205
|
+
query,
|
1206
|
+
params: {},
|
1207
|
+
// Will be populated by route matching
|
1208
|
+
body: void 0,
|
1209
|
+
// Will be populated by body parsing middleware
|
1210
|
+
cookies,
|
1211
|
+
signedCookies,
|
1212
|
+
secret: cookieSecret,
|
1213
|
+
accepted: [],
|
1214
|
+
subdomains: (() => {
|
1215
|
+
const hostname = Array.isArray(
|
1216
|
+
headers["x-forwarded-host"] || headers["host"]
|
1217
|
+
) ? (headers["x-forwarded-host"] || headers["host"])[0].split(":")[0] : (headers["x-forwarded-host"] || headers["host"] || "").split(
|
1218
|
+
":"
|
1219
|
+
)[0];
|
1220
|
+
if (!hostname) return [];
|
1221
|
+
const parts = hostname.split(".");
|
1222
|
+
return parts.length > 2 ? parts.slice(0, -2).reverse() : [];
|
1223
|
+
})(),
|
1224
|
+
get fresh() {
|
1225
|
+
const method = request.method;
|
1226
|
+
const status = res.statusCode;
|
1227
|
+
if (method !== "GET" && method !== "HEAD") return false;
|
1228
|
+
if (status >= 200 && status < 300 || status === 304) {
|
1229
|
+
const modifiedSince = headers["if-modified-since"];
|
1230
|
+
const noneMatch = headers["if-none-match"];
|
1231
|
+
const lastModified = res.get("last-modified");
|
1232
|
+
const etag = res.get("etag");
|
1233
|
+
if (noneMatch && etag) {
|
1234
|
+
return noneMatch.includes(etag);
|
1235
|
+
}
|
1236
|
+
if (modifiedSince && lastModified) {
|
1237
|
+
return new Date(modifiedSince) >= new Date(lastModified);
|
1238
|
+
}
|
1239
|
+
}
|
1240
|
+
return false;
|
1241
|
+
},
|
1242
|
+
get stale() {
|
1243
|
+
return !this.fresh;
|
1244
|
+
},
|
1245
|
+
xhr: (() => {
|
1246
|
+
const requestedWith = headers["x-requested-with"];
|
1247
|
+
return requestedWith === "XMLHttpRequest";
|
1248
|
+
})(),
|
1249
|
+
get: ((name) => {
|
1250
|
+
const value = headers[name.toLowerCase()];
|
1251
|
+
if (!value) return void 0;
|
1252
|
+
return Array.isArray(value) ? value[0] : value;
|
1253
|
+
}),
|
1254
|
+
header: ((name) => {
|
1255
|
+
const value = headers[name.toLowerCase()];
|
1256
|
+
if (!value) return void 0;
|
1257
|
+
return Array.isArray(value) ? value[0] : value;
|
1258
|
+
}),
|
1259
|
+
is(types) {
|
1260
|
+
const contentType = headers["content-type"] || "";
|
1261
|
+
if (!contentType) return false;
|
1262
|
+
const typesToCheck = Array.isArray(types) ? types : [types];
|
1263
|
+
for (const type of typesToCheck) {
|
1264
|
+
if (type === "json" && contentType.includes("application/json"))
|
1265
|
+
return "json";
|
1266
|
+
if (type === "html" && contentType.includes("text/html"))
|
1267
|
+
return "html";
|
1268
|
+
if (type === "xml" && contentType.includes("application/xml"))
|
1269
|
+
return "xml";
|
1270
|
+
if (type === "text" && contentType.includes("text/")) return "text";
|
1271
|
+
if (contentType.includes(type)) return type;
|
1272
|
+
}
|
1273
|
+
return false;
|
1274
|
+
},
|
1275
|
+
accepts: ((types) => {
|
1276
|
+
const accept = headers.accept || "*/*";
|
1277
|
+
if (!types) {
|
1278
|
+
const acceptStr2 = Array.isArray(accept) ? accept.join(",") : accept;
|
1279
|
+
return acceptStr2.split(",").map((t) => t.trim().split(";")[0]);
|
1280
|
+
}
|
1281
|
+
const typesToCheck = Array.isArray(types) ? types : [types];
|
1282
|
+
const acceptStr = Array.isArray(accept) ? accept.join(",") : accept;
|
1283
|
+
const result = [];
|
1284
|
+
for (const type of typesToCheck) {
|
1285
|
+
if (acceptStr.includes(type) || acceptStr.includes("*/*")) {
|
1286
|
+
result.push(type);
|
1287
|
+
}
|
1288
|
+
if (type === "json" && acceptStr.includes("application/json"))
|
1289
|
+
result.push("json");
|
1290
|
+
if (type === "html" && acceptStr.includes("text/html"))
|
1291
|
+
result.push("html");
|
1292
|
+
if (type === "xml" && acceptStr.includes("application/xml"))
|
1293
|
+
result.push("xml");
|
1294
|
+
}
|
1295
|
+
return result;
|
1296
|
+
}),
|
1297
|
+
acceptsCharsets: ((charsets) => {
|
1298
|
+
const acceptCharset = headers["accept-charset"] || "*";
|
1299
|
+
const acceptCharsetStr = Array.isArray(acceptCharset) ? acceptCharset.join(",") : acceptCharset;
|
1300
|
+
if (!charsets)
|
1301
|
+
return acceptCharsetStr.split(",").map((c) => c.trim());
|
1302
|
+
const charsetsToCheck = Array.isArray(charsets) ? charsets : [charsets];
|
1303
|
+
const result = [];
|
1304
|
+
for (const charset of charsetsToCheck) {
|
1305
|
+
if (acceptCharsetStr.includes(charset) || acceptCharsetStr.includes("*")) {
|
1306
|
+
result.push(charset);
|
1307
|
+
}
|
1308
|
+
}
|
1309
|
+
return result;
|
1310
|
+
}),
|
1311
|
+
acceptsEncodings: ((encodings) => {
|
1312
|
+
const acceptEncoding = headers["accept-encoding"] || "identity";
|
1313
|
+
const acceptEncodingStr = Array.isArray(acceptEncoding) ? acceptEncoding.join(",") : acceptEncoding;
|
1314
|
+
if (!encodings)
|
1315
|
+
return acceptEncodingStr.split(",").map((e) => e.trim());
|
1316
|
+
const encodingsToCheck = Array.isArray(encodings) ? encodings : [encodings];
|
1317
|
+
const result = [];
|
1318
|
+
for (const encoding of encodingsToCheck) {
|
1319
|
+
if (acceptEncodingStr.includes(encoding) || acceptEncodingStr.includes("*")) {
|
1320
|
+
result.push(encoding);
|
1321
|
+
}
|
1322
|
+
}
|
1323
|
+
return result;
|
1324
|
+
}),
|
1325
|
+
acceptsLanguages: ((languages) => {
|
1326
|
+
const acceptLanguage = headers["accept-language"] || "*";
|
1327
|
+
const acceptLanguageStr = Array.isArray(acceptLanguage) ? acceptLanguage.join(",") : acceptLanguage;
|
1328
|
+
if (!languages)
|
1329
|
+
return acceptLanguageStr.split(",").map((l) => l.trim());
|
1330
|
+
const languagesToCheck = Array.isArray(languages) ? languages : [languages];
|
1331
|
+
const result = [];
|
1332
|
+
for (const language of languagesToCheck) {
|
1333
|
+
if (acceptLanguageStr.includes(language) || acceptLanguageStr.includes("*")) {
|
1334
|
+
result.push(language);
|
1335
|
+
}
|
1336
|
+
}
|
1337
|
+
return result;
|
1338
|
+
}),
|
1339
|
+
range(size, options2) {
|
1340
|
+
const rangeHeader = headers.range;
|
1341
|
+
if (!rangeHeader) return void 0;
|
1342
|
+
const rangeStr = Array.isArray(rangeHeader) ? rangeHeader[0] : rangeHeader;
|
1343
|
+
return RangeParser(size, rangeStr, options2);
|
1344
|
+
},
|
1345
|
+
param(name, defaultValue) {
|
1346
|
+
if (this.params && this.params[name] !== void 0) {
|
1347
|
+
return this.params[name];
|
1348
|
+
}
|
1349
|
+
if (this.query && this.query[name] !== void 0) {
|
1350
|
+
return this.query[name];
|
1351
|
+
}
|
1352
|
+
if (this.body && typeof this.body === "object" && this.body[name] !== void 0) {
|
1353
|
+
return this.body[name];
|
1354
|
+
}
|
1355
|
+
return defaultValue;
|
1356
|
+
},
|
1357
|
+
app,
|
1358
|
+
baseUrl: app.basePath || "",
|
1359
|
+
route: {
|
1360
|
+
path: pathname,
|
1361
|
+
stack: [],
|
1362
|
+
methods: { [request.method.toLowerCase()]: true }
|
1363
|
+
},
|
1364
|
+
res,
|
1365
|
+
next: (() => {
|
1366
|
+
}),
|
1367
|
+
rawHeaders: Object.entries(headers).flat(),
|
1368
|
+
trailers: {},
|
1369
|
+
rawTrailers: [],
|
1370
|
+
aborted: false,
|
1371
|
+
upgrade: false,
|
1372
|
+
readable: true,
|
1373
|
+
readableHighWaterMark: 16384,
|
1374
|
+
readableLength: 0,
|
1375
|
+
destroyed: false,
|
1376
|
+
closed: false,
|
1377
|
+
clearCookie: (name, options2) => {
|
1378
|
+
const opts2 = Object.assign({}, options2);
|
1379
|
+
opts2.expires = /* @__PURE__ */ new Date(1);
|
1380
|
+
opts2.path = opts2.path || "/";
|
1381
|
+
return res.cookie(name, "", opts2);
|
1382
|
+
},
|
1383
|
+
setTimeout: (msecs, callback) => {
|
1384
|
+
setTimeout(callback || (() => {
|
1385
|
+
}), msecs);
|
1386
|
+
return req;
|
1387
|
+
},
|
1388
|
+
headersDistinct: Object.fromEntries(
|
1389
|
+
Object.entries(headers).map(([key, value]) => [
|
1390
|
+
key,
|
1391
|
+
Array.isArray(value) ? value : [value]
|
1392
|
+
])
|
1393
|
+
),
|
1394
|
+
trailersDistinct: {}
|
1395
|
+
});
|
1396
|
+
Object.assign(req, {
|
1397
|
+
res,
|
1398
|
+
req
|
1399
|
+
});
|
1400
|
+
Object.assign(res, {
|
1401
|
+
req,
|
1402
|
+
res
|
1403
|
+
});
|
1404
|
+
const onAbort = () => {
|
1405
|
+
cleanup();
|
1406
|
+
Object.assign(req, {
|
1407
|
+
aborted: true,
|
1408
|
+
destroyed: true
|
1409
|
+
});
|
1410
|
+
resEE.emit("close");
|
1411
|
+
req.emit("close");
|
1412
|
+
req.emit("aborted");
|
1413
|
+
writer.abort("client aborted");
|
1414
|
+
};
|
1415
|
+
abort.signal.addEventListener("abort", onAbort);
|
1416
|
+
res.once("finish", cleanup);
|
1417
|
+
res.once("close", cleanup);
|
1418
|
+
res.once("finish", () => {
|
1419
|
+
abort.signal.removeEventListener("abort", onAbort);
|
1420
|
+
});
|
1421
|
+
res.once("close", () => {
|
1422
|
+
abort.signal.removeEventListener("abort", onAbort);
|
1423
|
+
});
|
1424
|
+
try {
|
1425
|
+
req.on("error", (error) => {
|
1426
|
+
openTelemetryCollector.error("Request error:", error);
|
1427
|
+
req.emit("aborted");
|
1428
|
+
if (!res.headersSent) {
|
1429
|
+
res.status(400).send("Bad Request: aborted");
|
1430
|
+
}
|
1431
|
+
});
|
1432
|
+
req.on("timeout", () => {
|
1433
|
+
openTelemetryCollector.warn("Request timeout:", req.method, req.path);
|
1434
|
+
if (!res.headersSent) {
|
1435
|
+
res.status(408).send("Request Timeout");
|
1436
|
+
}
|
1437
|
+
});
|
1438
|
+
res.on("error", (error) => {
|
1439
|
+
openTelemetryCollector.error("Response error:", error);
|
1440
|
+
res.emit("close");
|
1441
|
+
});
|
1442
|
+
const next = (error) => {
|
1443
|
+
if (error) {
|
1444
|
+
openTelemetryCollector.error("Express middleware error:", error);
|
1445
|
+
if (!res.headersSent) {
|
1446
|
+
const status = error?.status || error?.statusCode || 500;
|
1447
|
+
res.status(status);
|
1448
|
+
if (development) {
|
1449
|
+
const message = error?.message || String(error);
|
1450
|
+
res.send(`${status} ${message}`);
|
1451
|
+
} else {
|
1452
|
+
const message = status === 404 ? "Not Found" : status === 401 ? "Unauthorized" : status === 403 ? "Forbidden" : status === 400 ? "Bad Request" : "Internal Server Error";
|
1453
|
+
res.send(message);
|
1454
|
+
}
|
1455
|
+
}
|
1456
|
+
return;
|
1457
|
+
}
|
1458
|
+
};
|
1459
|
+
if (development) {
|
1460
|
+
openTelemetryCollector.info(
|
1461
|
+
`${request.method} ${pathname} - Started`
|
1462
|
+
);
|
1463
|
+
}
|
1464
|
+
try {
|
1465
|
+
app(req, res, next);
|
1466
|
+
} catch (syncError) {
|
1467
|
+
openTelemetryCollector.error(
|
1468
|
+
"Synchronous Express application error:",
|
1469
|
+
syncError
|
1470
|
+
);
|
1471
|
+
next(syncError);
|
1472
|
+
}
|
1473
|
+
} catch (error) {
|
1474
|
+
openTelemetryCollector.error("Request processing error:", error);
|
1475
|
+
const message = development ? error.message || String(error) : "Internal Server Error";
|
1476
|
+
return new Response(message, {
|
1477
|
+
status: 500,
|
1478
|
+
headers: { "content-type": "text/plain" }
|
1479
|
+
});
|
1480
|
+
}
|
1481
|
+
queueMicrotask(() => {
|
1482
|
+
if (!responded && (res.headersSent || res.statusCode !== 200)) {
|
1483
|
+
ensureResponse();
|
1484
|
+
}
|
1485
|
+
});
|
1486
|
+
if (development) {
|
1487
|
+
setTimeout(() => {
|
1488
|
+
if (!responded && !serverClosed) {
|
1489
|
+
try {
|
1490
|
+
openTelemetryCollector.warn(
|
1491
|
+
`Request timeout: ${request.method} ${pathname} - No response after 30s`
|
1492
|
+
);
|
1493
|
+
openTelemetryCollector.warn(
|
1494
|
+
`Headers sent: ${res.headersSent}, Status: ${res.statusCode}`
|
1495
|
+
);
|
1496
|
+
if (!res.headersSent) {
|
1497
|
+
res.status(504).send("Gateway Timeout");
|
1498
|
+
} else {
|
1499
|
+
writer.close();
|
1500
|
+
ensureResponse();
|
1501
|
+
}
|
1502
|
+
} catch (error) {
|
1503
|
+
openTelemetryCollector.error(
|
1504
|
+
"Error sending timeout response:",
|
1505
|
+
error
|
1506
|
+
);
|
1507
|
+
if (!responded) {
|
1508
|
+
ensureResponse();
|
1509
|
+
}
|
1510
|
+
}
|
1511
|
+
}
|
1512
|
+
}, 3e4);
|
1513
|
+
}
|
1514
|
+
return await responsePromise;
|
1515
|
+
}
|
1516
|
+
});
|
1517
|
+
const serverObj = {
|
1518
|
+
port: server.port,
|
1519
|
+
hostname: server.hostname,
|
1520
|
+
url: `${tls2 ? "https" : "http"}://${host === "0.0.0.0" ? "localhost" : host}:${server.port}`,
|
1521
|
+
stop(force = false) {
|
1522
|
+
serverClosed = true;
|
1523
|
+
openTelemetryCollector.info(`Stopping server on port ${server.port}...`);
|
1524
|
+
server.stop(force);
|
1525
|
+
},
|
1526
|
+
reload() {
|
1527
|
+
if (development) {
|
1528
|
+
openTelemetryCollector.info(
|
1529
|
+
"Server reload requested - restart server to apply changes"
|
1530
|
+
);
|
1531
|
+
}
|
1532
|
+
},
|
1533
|
+
address() {
|
1534
|
+
return {
|
1535
|
+
port: server.port,
|
1536
|
+
family: "IPv4",
|
1537
|
+
address: host
|
1538
|
+
};
|
1539
|
+
},
|
1540
|
+
listening: true,
|
1541
|
+
connections: 0,
|
1542
|
+
maxConnections: void 0,
|
1543
|
+
on: (event, listener) => {
|
1544
|
+
if (event === "listening") {
|
1545
|
+
process.nextTick(listener);
|
1546
|
+
}
|
1547
|
+
return serverObj;
|
1548
|
+
},
|
1549
|
+
once: (event, listener) => {
|
1550
|
+
if (event === "listening") {
|
1551
|
+
process.nextTick(listener);
|
1552
|
+
}
|
1553
|
+
return serverObj;
|
1554
|
+
},
|
1555
|
+
emit: (event, ...args) => {
|
1556
|
+
return true;
|
1557
|
+
},
|
1558
|
+
async close(callback) {
|
1559
|
+
try {
|
1560
|
+
serverClosed = true;
|
1561
|
+
server.stop(true);
|
1562
|
+
if (callback) callback();
|
1563
|
+
} catch (error) {
|
1564
|
+
if (callback) callback(error);
|
1565
|
+
}
|
1566
|
+
},
|
1567
|
+
getConnections(callback) {
|
1568
|
+
process.nextTick(() => callback(null, serverObj.connections));
|
1569
|
+
}
|
1570
|
+
};
|
1571
|
+
if (development) {
|
1572
|
+
openTelemetryCollector.info(
|
1573
|
+
`\u{1F680} Express server running on ${serverObj.url}`
|
1574
|
+
);
|
1575
|
+
openTelemetryCollector.info(
|
1576
|
+
`\u{1F4C1} Environment: ${development ? "development" : "production"}`
|
1577
|
+
);
|
1578
|
+
}
|
1579
|
+
return serverObj;
|
1580
|
+
}
|
1581
|
+
|
1582
|
+
// src/cluster/bun.cluster.ts
|
1583
|
+
function startBunCluster(config) {
|
1584
|
+
const WORKERS = config.workerCount;
|
1585
|
+
const PORT = config.port;
|
1586
|
+
const HOST = config.host;
|
1587
|
+
const openTelemetryCollector = config.openTelemetryCollector;
|
1588
|
+
if (WORKERS > os.cpus().length) {
|
1589
|
+
throw new Error("Worker count cannot be greater than the number of CPUs");
|
1590
|
+
}
|
1591
|
+
if (cluster.isPrimary) {
|
1592
|
+
let startWorker2 = function(i) {
|
1593
|
+
if (isShuttingDown) return;
|
1594
|
+
const worker = cluster.fork({
|
1595
|
+
WORKER_INDEX: String(i),
|
1596
|
+
PORT: String(PORT)
|
1597
|
+
});
|
1598
|
+
workers.set(worker.id, worker);
|
1599
|
+
worker.on("exit", () => {
|
1600
|
+
if (isShuttingDown) {
|
1601
|
+
return;
|
1602
|
+
}
|
1603
|
+
workers.delete(worker.id);
|
1604
|
+
openTelemetryCollector.warn(
|
1605
|
+
`worker ${worker.process.pid} died; restarting in 2s`
|
1606
|
+
);
|
1607
|
+
setTimeout(() => {
|
1608
|
+
if (!isShuttingDown) {
|
1609
|
+
startWorker2(i);
|
1610
|
+
}
|
1611
|
+
}, 2e3);
|
1612
|
+
});
|
1613
|
+
worker.on("message", (message) => {
|
1614
|
+
if (message && message.type === "worker-ready") {
|
1615
|
+
openTelemetryCollector.info(
|
1616
|
+
`Worker ${i} (PID: ${worker.process.pid}) ready`
|
1617
|
+
);
|
1618
|
+
}
|
1619
|
+
});
|
1620
|
+
};
|
1621
|
+
var startWorker = startWorker2;
|
1622
|
+
const workers = /* @__PURE__ */ new Map();
|
1623
|
+
let isShuttingDown = false;
|
1624
|
+
openTelemetryCollector.info(
|
1625
|
+
`[primary ${process.pid}] starting ${WORKERS} workers on :${PORT}. Ignoring routing strategy due to SO_REUSEPORT kernel level routing.`
|
1626
|
+
);
|
1627
|
+
for (let i = 0; i < WORKERS; i++) {
|
1628
|
+
startWorker2(i);
|
1629
|
+
}
|
1630
|
+
process.on("SIGINT", () => {
|
1631
|
+
isShuttingDown = true;
|
1632
|
+
openTelemetryCollector.info(
|
1633
|
+
"Received SIGINT, shutting down gracefully..."
|
1634
|
+
);
|
1635
|
+
workers.forEach((worker) => {
|
1636
|
+
worker.send({ type: "shutdown" });
|
1637
|
+
worker.kill("SIGTERM");
|
1638
|
+
});
|
1639
|
+
setTimeout(() => {
|
1640
|
+
workers.forEach((worker) => worker.kill("SIGKILL"));
|
1641
|
+
process.exit(0);
|
1642
|
+
}, 5e3);
|
1643
|
+
});
|
1644
|
+
} else {
|
1645
|
+
const PORT2 = Number(process.env.PORT || 6935);
|
1646
|
+
const IDX = process.env.WORKER_INDEX ?? "0";
|
1647
|
+
const server = serveExpress(
|
1648
|
+
config.expressApp,
|
1649
|
+
config.openTelemetryCollector,
|
1650
|
+
{
|
1651
|
+
port: PORT2,
|
1652
|
+
host: HOST,
|
1653
|
+
reusePort: true,
|
1654
|
+
development: false,
|
1655
|
+
tls: config.ssl
|
1656
|
+
}
|
1657
|
+
);
|
1658
|
+
if (process.send) {
|
1659
|
+
process.send({ type: "worker-ready", port: server.port, index: IDX });
|
1660
|
+
}
|
1661
|
+
setInterval(() => {
|
1662
|
+
const memUsage = process.memoryUsage();
|
1663
|
+
const heapUsedMB = Math.round(memUsage.heapUsed / 1024 / 1024);
|
1664
|
+
if (heapUsedMB > 100) {
|
1665
|
+
openTelemetryCollector.warn(
|
1666
|
+
`High memory usage on worker ${process.pid}: ${heapUsedMB} MB`
|
1667
|
+
);
|
1668
|
+
}
|
1669
|
+
}, 3e4);
|
1670
|
+
process.on("SIGINT", () => {
|
1671
|
+
openTelemetryCollector.info(
|
1672
|
+
`[worker ${process.pid}] shutting down gracefully...`
|
1673
|
+
);
|
1674
|
+
server.stop();
|
1675
|
+
process.exit(0);
|
1676
|
+
});
|
1677
|
+
process.on("message", (message) => {
|
1678
|
+
if (message?.type === "shutdown") {
|
1679
|
+
openTelemetryCollector.info(
|
1680
|
+
`[worker ${process.pid}] received shutdown signal from primary`
|
1681
|
+
);
|
1682
|
+
server.stop();
|
1683
|
+
process.exit(0);
|
1684
|
+
}
|
1685
|
+
});
|
1686
|
+
process.on("uncaughtException", (err) => {
|
1687
|
+
openTelemetryCollector.error(
|
1688
|
+
`Uncaught exception on worker ${process.pid}:`,
|
1689
|
+
err
|
1690
|
+
);
|
1691
|
+
process.exit(1);
|
1692
|
+
});
|
1693
|
+
process.on("unhandledRejection", (reason) => {
|
1694
|
+
openTelemetryCollector.error(
|
1695
|
+
`Unhandled rejection on worker ${process.pid}:`,
|
1696
|
+
reason
|
1697
|
+
);
|
1698
|
+
process.exit(1);
|
1699
|
+
});
|
1700
|
+
}
|
1701
|
+
}
|
1702
|
+
|
1703
|
+
// src/cluster/node.cluster.ts
|
1704
|
+
import { hashString } from "@forklaunch/common";
|
1705
|
+
import cluster2 from "cluster";
|
1706
|
+
import fs from "fs";
|
1707
|
+
import http from "http";
|
1708
|
+
import https from "https";
|
1709
|
+
import net from "net";
|
1710
|
+
import os2 from "os";
|
1711
|
+
import tls from "tls";
|
1712
|
+
function startNodeCluster(config) {
|
1713
|
+
const PORT = config.port;
|
1714
|
+
const HOST = config.host;
|
1715
|
+
const WORKERS = config.workerCount;
|
1716
|
+
const ROUTING_STRATEGY = config.routingStrategy || "round-robin";
|
1717
|
+
const app = config.expressApp;
|
1718
|
+
const openTelemetryCollector = config.openTelemetryCollector;
|
1719
|
+
const SSL_CONFIG = config.ssl;
|
1720
|
+
if (WORKERS > os2.cpus().length) {
|
1721
|
+
throw new Error("Worker count cannot be greater than the number of CPUs");
|
1722
|
+
}
|
1723
|
+
let sslOptions;
|
1724
|
+
if (SSL_CONFIG) {
|
1725
|
+
try {
|
1726
|
+
sslOptions = {
|
1727
|
+
key: fs.readFileSync(SSL_CONFIG.keyFile),
|
1728
|
+
cert: fs.readFileSync(SSL_CONFIG.certFile),
|
1729
|
+
ca: fs.readFileSync(SSL_CONFIG.caFile)
|
1730
|
+
};
|
1731
|
+
openTelemetryCollector.info(`SSL/TLS configuration loaded from files`);
|
1732
|
+
} catch (error) {
|
1733
|
+
openTelemetryCollector.error(
|
1734
|
+
"Failed to load SSL/TLS certificates:",
|
1735
|
+
error
|
1736
|
+
);
|
1737
|
+
throw new Error("SSL/TLS configuration failed");
|
1738
|
+
}
|
1739
|
+
}
|
1740
|
+
if (cluster2.isPrimary) {
|
1741
|
+
let getWorker2 = function(remoteAddress) {
|
1742
|
+
switch (ROUTING_STRATEGY) {
|
1743
|
+
case "sticky": {
|
1744
|
+
const ip = remoteAddress ?? "";
|
1745
|
+
const idx = hashString(ip) % workers.length;
|
1746
|
+
return workers[idx];
|
1747
|
+
}
|
1748
|
+
case "random": {
|
1749
|
+
return workers[Math.floor(Math.random() * workers.length)];
|
1750
|
+
}
|
1751
|
+
case "round-robin":
|
1752
|
+
default: {
|
1753
|
+
const worker = workers[currentWorkerIndex];
|
1754
|
+
currentWorkerIndex = (currentWorkerIndex + 1) % workers.length;
|
1755
|
+
return worker;
|
1756
|
+
}
|
1757
|
+
}
|
1758
|
+
};
|
1759
|
+
var getWorker = getWorker2;
|
1760
|
+
cluster2.setupPrimary({ execArgv: process.execArgv });
|
1761
|
+
const protocol = SSL_CONFIG ? "https" : "http";
|
1762
|
+
openTelemetryCollector.info(
|
1763
|
+
`[primary ${process.pid}] starting ${WORKERS} workers with ${ROUTING_STRATEGY} routing on ${protocol}://${HOST}:${PORT}`
|
1764
|
+
);
|
1765
|
+
const workers = Array.from({ length: WORKERS }, (_, i) => {
|
1766
|
+
const worker = cluster2.fork();
|
1767
|
+
worker.on(
|
1768
|
+
"message",
|
1769
|
+
(message) => {
|
1770
|
+
if (message && message.type === "worker-ready") {
|
1771
|
+
openTelemetryCollector.info(
|
1772
|
+
`Worker ${message.index || i} (PID: ${worker.process.pid}) ready`
|
1773
|
+
);
|
1774
|
+
}
|
1775
|
+
}
|
1776
|
+
);
|
1777
|
+
return worker;
|
1778
|
+
});
|
1779
|
+
let currentWorkerIndex = 0;
|
1780
|
+
const server = SSL_CONFIG ? tls.createServer(sslOptions, (socket) => {
|
1781
|
+
const w = getWorker2(socket.remoteAddress);
|
1782
|
+
w.send({ type: "sticky-connection" }, socket);
|
1783
|
+
}) : net.createServer({ pauseOnConnect: true }, (socket) => {
|
1784
|
+
const w = getWorker2(socket.remoteAddress);
|
1785
|
+
w.send({ type: "sticky-connection" }, socket);
|
1786
|
+
});
|
1787
|
+
server.on("error", (err) => {
|
1788
|
+
openTelemetryCollector.error("primary net server error:", err);
|
1789
|
+
});
|
1790
|
+
server.listen(PORT, HOST, () => {
|
1791
|
+
const protocol2 = SSL_CONFIG ? "https" : "http";
|
1792
|
+
openTelemetryCollector.info(
|
1793
|
+
`[primary ${process.pid}] listening on ${protocol2}://${HOST}:${PORT}`
|
1794
|
+
);
|
1795
|
+
});
|
1796
|
+
cluster2.on("exit", (worker) => {
|
1797
|
+
openTelemetryCollector.warn(
|
1798
|
+
`worker ${worker.process.pid} died; restarting`
|
1799
|
+
);
|
1800
|
+
const i = workers.findIndex((w) => w.id === worker.id);
|
1801
|
+
const replacement = cluster2.fork();
|
1802
|
+
replacement.on(
|
1803
|
+
"message",
|
1804
|
+
(message) => {
|
1805
|
+
if (message && message.type === "worker-ready") {
|
1806
|
+
openTelemetryCollector.info(
|
1807
|
+
`Worker ${message.index || i} (PID: ${replacement.process.pid}) ready`
|
1808
|
+
);
|
1809
|
+
}
|
1810
|
+
}
|
1811
|
+
);
|
1812
|
+
if (i !== -1) workers[i] = replacement;
|
1813
|
+
});
|
1814
|
+
let isShuttingDown = false;
|
1815
|
+
process.on("SIGINT", () => {
|
1816
|
+
if (isShuttingDown) return;
|
1817
|
+
isShuttingDown = true;
|
1818
|
+
openTelemetryCollector.info(
|
1819
|
+
"Received SIGINT, shutting down gracefully..."
|
1820
|
+
);
|
1821
|
+
workers.forEach((worker) => {
|
1822
|
+
worker.send({ type: "shutdown" });
|
1823
|
+
worker.kill("SIGTERM");
|
1824
|
+
});
|
1825
|
+
setTimeout(() => {
|
1826
|
+
workers.forEach((worker) => worker.kill("SIGKILL"));
|
1827
|
+
process.exit(0);
|
1828
|
+
}, 5e3);
|
1829
|
+
});
|
1830
|
+
} else {
|
1831
|
+
const server = SSL_CONFIG ? https.createServer(sslOptions, app) : http.createServer(app);
|
1832
|
+
const IDX = process.env.WORKER_INDEX ?? cluster2.worker?.id ?? "0";
|
1833
|
+
process.on("message", (msg, socket) => {
|
1834
|
+
if (msg?.type === "sticky-connection" && socket) {
|
1835
|
+
server.emit("connection", socket);
|
1836
|
+
socket.resume();
|
1837
|
+
} else if (msg?.type === "shutdown") {
|
1838
|
+
openTelemetryCollector.info(
|
1839
|
+
`[worker ${process.pid}] received shutdown signal from primary`
|
1840
|
+
);
|
1841
|
+
server.close(() => {
|
1842
|
+
process.exit(0);
|
1843
|
+
});
|
1844
|
+
}
|
1845
|
+
});
|
1846
|
+
server.on("clientError", (err, socket) => {
|
1847
|
+
try {
|
1848
|
+
const response = SSL_CONFIG ? "HTTP/1.1 400 Bad Request\r\n\r\n" : "HTTP/1.1 400 Bad Request\r\n\r\n";
|
1849
|
+
socket.end(response);
|
1850
|
+
} catch {
|
1851
|
+
openTelemetryCollector.error("clientError", err);
|
1852
|
+
}
|
1853
|
+
});
|
1854
|
+
server.listen(0, () => {
|
1855
|
+
if (process.send) {
|
1856
|
+
process.send({ type: "worker-ready", port: PORT, index: IDX });
|
1857
|
+
}
|
1858
|
+
});
|
1859
|
+
setInterval(() => {
|
1860
|
+
const memUsage = process.memoryUsage();
|
1861
|
+
const heapUsedMB = Math.round(memUsage.heapUsed / 1024 / 1024);
|
1862
|
+
if (heapUsedMB > 100) {
|
1863
|
+
openTelemetryCollector.warn(
|
1864
|
+
`High memory usage on worker ${process.pid}: ${heapUsedMB} MB`
|
1865
|
+
);
|
1866
|
+
}
|
1867
|
+
}, 3e4);
|
1868
|
+
process.on("uncaughtException", (err) => {
|
1869
|
+
openTelemetryCollector.error(
|
1870
|
+
`Uncaught exception on worker ${process.pid}:`,
|
1871
|
+
err
|
1872
|
+
);
|
1873
|
+
process.exit(1);
|
1874
|
+
});
|
1875
|
+
process.on("unhandledRejection", (reason) => {
|
1876
|
+
openTelemetryCollector.error(
|
1877
|
+
`Unhandled rejection on worker ${process.pid}:`,
|
1878
|
+
reason
|
1879
|
+
);
|
1880
|
+
process.exit(1);
|
1881
|
+
});
|
1882
|
+
const protocol = SSL_CONFIG ? "https" : "http";
|
1883
|
+
openTelemetryCollector.info(
|
1884
|
+
`[worker ${process.pid}] ready (awaiting sockets from primary) - ${protocol}`
|
1885
|
+
);
|
1886
|
+
}
|
1887
|
+
}
|
1888
|
+
|
18
1889
|
// src/middleware/content.parse.middleware.ts
|
19
1890
|
import { isNever } from "@forklaunch/common";
|
20
1891
|
import { discriminateBody } from "@forklaunch/core/http";
|
@@ -143,6 +2014,9 @@ function enrichResponseTransmission(req, res, next) {
|
|
143
2014
|
// src/expressApplication.ts
|
144
2015
|
var Application = class extends ForklaunchExpressLikeApplication {
|
145
2016
|
docsConfiguration;
|
2017
|
+
mcpConfiguration;
|
2018
|
+
openapiConfiguration;
|
2019
|
+
hostingConfiguration;
|
146
2020
|
/**
|
147
2021
|
* Creates an instance of Application.
|
148
2022
|
*
|
@@ -161,90 +2035,153 @@ var Application = class extends ForklaunchExpressLikeApplication {
|
|
161
2035
|
openTelemetryCollector,
|
162
2036
|
options2
|
163
2037
|
);
|
2038
|
+
console.log("this.routerOptions offa", this.routerOptions);
|
2039
|
+
this.hostingConfiguration = options2?.hosting;
|
164
2040
|
this.docsConfiguration = options2?.docs;
|
2041
|
+
this.mcpConfiguration = options2?.mcp;
|
2042
|
+
this.openapiConfiguration = options2?.openapi;
|
165
2043
|
}
|
166
2044
|
listen(...args) {
|
167
2045
|
const port = typeof args[0] === "number" ? args[0] : Number(process.env.PORT);
|
168
2046
|
const host = typeof args[1] === "string" ? args[1] : process.env.HOST ?? "localhost";
|
169
2047
|
const protocol = process.env.PROTOCOL ?? "http";
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
openApiServerDescriptions,
|
180
|
-
this
|
181
|
-
);
|
182
|
-
if (this.schemaValidator instanceof ZodSchemaValidator) {
|
2048
|
+
if (this.schemaValidator instanceof ZodSchemaValidator && this.mcpConfiguration !== false) {
|
2049
|
+
const {
|
2050
|
+
port: mcpPort,
|
2051
|
+
options: options2,
|
2052
|
+
path: mcpPath,
|
2053
|
+
version,
|
2054
|
+
additionalTools,
|
2055
|
+
contentTypeMapping
|
2056
|
+
} = this.mcpConfiguration ?? {};
|
183
2057
|
const zodSchemaValidator = this.schemaValidator;
|
2058
|
+
const finalMcpPort = mcpPort ?? port + 2e3;
|
184
2059
|
const mcpServer = generateMcpServer(
|
185
2060
|
zodSchemaValidator,
|
186
2061
|
protocol,
|
187
2062
|
host,
|
188
|
-
|
189
|
-
"1.0.0",
|
190
|
-
this
|
2063
|
+
finalMcpPort,
|
2064
|
+
version ?? "1.0.0",
|
2065
|
+
this,
|
2066
|
+
this.mcpConfiguration,
|
2067
|
+
options2,
|
2068
|
+
contentTypeMapping
|
191
2069
|
);
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
2070
|
+
if (additionalTools) {
|
2071
|
+
additionalTools(mcpServer);
|
2072
|
+
}
|
2073
|
+
if (this.hostingConfiguration?.workerCount && this.hostingConfiguration.workerCount > 1) {
|
2074
|
+
isPortBound(finalMcpPort, host).then((isBound) => {
|
2075
|
+
if (!isBound) {
|
2076
|
+
mcpServer.start({
|
2077
|
+
httpStream: {
|
2078
|
+
host,
|
2079
|
+
endpoint: mcpPath ?? "/mcp",
|
2080
|
+
port: finalMcpPort
|
2081
|
+
},
|
2082
|
+
transportType: "httpStream"
|
2083
|
+
});
|
2084
|
+
}
|
2085
|
+
});
|
2086
|
+
} else {
|
2087
|
+
mcpServer.start({
|
2088
|
+
httpStream: {
|
2089
|
+
host,
|
2090
|
+
endpoint: mcpPath ?? "/mcp",
|
2091
|
+
port: finalMcpPort
|
2092
|
+
},
|
2093
|
+
transportType: "httpStream"
|
2094
|
+
});
|
2095
|
+
}
|
200
2096
|
}
|
201
|
-
if (this.
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
})),
|
215
|
-
...this.docsConfiguration?.sources ?? []
|
216
|
-
]
|
217
|
-
})
|
2097
|
+
if (this.openapiConfiguration !== false) {
|
2098
|
+
const openApiServerUrls = getEnvVar("DOCS_SERVER_URLS")?.split(",") ?? [
|
2099
|
+
`${protocol}://${host}:${port}`
|
2100
|
+
];
|
2101
|
+
const openApiServerDescriptions = getEnvVar(
|
2102
|
+
"DOCS_SERVER_DESCRIPTIONS"
|
2103
|
+
)?.split(",") ?? ["Main Server"];
|
2104
|
+
const openApi = generateOpenApiSpecs(
|
2105
|
+
this.schemaValidator,
|
2106
|
+
openApiServerUrls,
|
2107
|
+
openApiServerDescriptions,
|
2108
|
+
this,
|
2109
|
+
this.openapiConfiguration
|
218
2110
|
);
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
2111
|
+
if (this.docsConfiguration == null || this.docsConfiguration.type === "scalar") {
|
2112
|
+
this.internal.use(
|
2113
|
+
`/api${process.env.VERSION ? `/${process.env.VERSION}` : ""}${process.env.DOCS_PATH ?? "/docs"}`,
|
2114
|
+
apiReference({
|
2115
|
+
...this.docsConfiguration,
|
2116
|
+
sources: [
|
2117
|
+
{
|
2118
|
+
content: openApi[OPENAPI_DEFAULT_VERSION],
|
2119
|
+
title: "API Reference"
|
2120
|
+
},
|
2121
|
+
...Object.entries(openApi).map(([version, spec]) => ({
|
2122
|
+
content: typeof this.openapiConfiguration === "boolean" ? spec : this.openapiConfiguration?.discreteVersions === false ? {
|
2123
|
+
...openApi[OPENAPI_DEFAULT_VERSION],
|
2124
|
+
...spec
|
2125
|
+
} : spec,
|
2126
|
+
title: `API Reference - ${version}`
|
2127
|
+
})),
|
2128
|
+
...this.docsConfiguration?.sources ?? []
|
2129
|
+
]
|
2130
|
+
})
|
2131
|
+
);
|
2132
|
+
} else if (this.docsConfiguration.type === "swagger") {
|
2133
|
+
this.internal.use(
|
2134
|
+
`/api${process.env.VERSION ? `/${process.env.VERSION}` : ""}${process.env.DOCS_PATH ?? "/docs"}`,
|
2135
|
+
swaggerUi.serveFiles(openApi),
|
2136
|
+
swaggerUi.setup(openApi)
|
2137
|
+
);
|
2138
|
+
}
|
2139
|
+
this.internal.get(
|
2140
|
+
`/api${process.env.VERSION ? `/${process.env.VERSION}` : ""}/openapi`,
|
2141
|
+
(_, res) => {
|
2142
|
+
res.type("application/json");
|
2143
|
+
res.json({
|
2144
|
+
latest: openApi[OPENAPI_DEFAULT_VERSION],
|
2145
|
+
...Object.fromEntries(
|
2146
|
+
Object.entries(openApi).map(([version, spec]) => [
|
2147
|
+
`v${version}`,
|
2148
|
+
typeof this.openapiConfiguration === "boolean" ? spec : this.openapiConfiguration?.discreteVersions === false ? {
|
2149
|
+
...openApi[OPENAPI_DEFAULT_VERSION],
|
2150
|
+
...spec
|
2151
|
+
} : spec
|
2152
|
+
])
|
2153
|
+
)
|
2154
|
+
});
|
2155
|
+
}
|
2156
|
+
);
|
2157
|
+
this.internal.get(
|
2158
|
+
`/api${process.env.VERSION ? `/${process.env.VERSION}` : ""}/openapi/:id`,
|
2159
|
+
async (req, res) => {
|
2160
|
+
res.type("application/json");
|
2161
|
+
if (req.params.id === "latest") {
|
2162
|
+
res.json(openApi[OPENAPI_DEFAULT_VERSION]);
|
2163
|
+
} else {
|
2164
|
+
if (openApi[req.params.id] == null) {
|
2165
|
+
res.status(404).send("Not Found");
|
2166
|
+
return;
|
2167
|
+
}
|
2168
|
+
res.json(
|
2169
|
+
typeof this.openapiConfiguration === "boolean" ? openApi[req.params.id] : this.openapiConfiguration?.discreteVersions === false ? {
|
2170
|
+
...openApi[OPENAPI_DEFAULT_VERSION],
|
2171
|
+
...openApi[req.params.id]
|
2172
|
+
} : openApi[req.params.id]
|
2173
|
+
);
|
2174
|
+
}
|
2175
|
+
}
|
2176
|
+
);
|
2177
|
+
this.internal.get(
|
2178
|
+
`/api/${process.env.VERSION ?? "v1"}/openapi-hash`,
|
2179
|
+
async (_, res) => {
|
2180
|
+
const hash = await crypto2.createHash("sha256").update(safeStringify2(openApi)).digest("hex");
|
2181
|
+
res.send(hash);
|
2182
|
+
}
|
224
2183
|
);
|
225
2184
|
}
|
226
|
-
this.internal.get(
|
227
|
-
`/api${process.env.VERSION ? `/${process.env.VERSION}` : ""}/openapi`,
|
228
|
-
(_, res) => {
|
229
|
-
res.type("application/json");
|
230
|
-
res.json({
|
231
|
-
latest: openApi[OPENAPI_DEFAULT_VERSION],
|
232
|
-
...Object.fromEntries(
|
233
|
-
Object.entries(openApi).map(([version, spec]) => [
|
234
|
-
`v${version}`,
|
235
|
-
spec
|
236
|
-
])
|
237
|
-
)
|
238
|
-
});
|
239
|
-
}
|
240
|
-
);
|
241
|
-
this.internal.get(
|
242
|
-
`/api/${process.env.VERSION ?? "v1"}/openapi-hash`,
|
243
|
-
async (_, res) => {
|
244
|
-
const hash = await crypto.createHash("sha256").update(safeStringify2(openApi)).digest("hex");
|
245
|
-
res.send(hash);
|
246
|
-
}
|
247
|
-
);
|
248
2185
|
this.internal.get("/health", (_, res) => {
|
249
2186
|
res.send("OK");
|
250
2187
|
});
|
@@ -266,6 +2203,36 @@ Correlation id: ${isForklaunchRequest(req) ? req.context.correlationId : "No cor
|
|
266
2203
|
);
|
267
2204
|
};
|
268
2205
|
this.internal.use(errorHandler);
|
2206
|
+
const { workerCount, ssl, routingStrategy } = this.hostingConfiguration ?? {};
|
2207
|
+
if (workerCount != null && workerCount > 1) {
|
2208
|
+
if (typeof Bun !== "undefined") {
|
2209
|
+
this.openTelemetryCollector.warn(
|
2210
|
+
`Note: bun clustering is experimental, shimmed, and only works on linux. You may have an inconsistent experience, consider creating multiple hosts.`
|
2211
|
+
);
|
2212
|
+
Object.assign(this.internal, {
|
2213
|
+
basePath: this.basePath
|
2214
|
+
});
|
2215
|
+
startBunCluster({
|
2216
|
+
expressApp: this.internal,
|
2217
|
+
openTelemetryCollector: this.openTelemetryCollector,
|
2218
|
+
port,
|
2219
|
+
host,
|
2220
|
+
workerCount,
|
2221
|
+
routingStrategy,
|
2222
|
+
ssl
|
2223
|
+
});
|
2224
|
+
} else {
|
2225
|
+
startNodeCluster({
|
2226
|
+
expressApp: this.internal,
|
2227
|
+
openTelemetryCollector: this.openTelemetryCollector,
|
2228
|
+
port,
|
2229
|
+
host,
|
2230
|
+
workerCount,
|
2231
|
+
routingStrategy,
|
2232
|
+
ssl
|
2233
|
+
});
|
2234
|
+
}
|
2235
|
+
}
|
269
2236
|
return this.internal.listen(...args);
|
270
2237
|
}
|
271
2238
|
};
|
@@ -291,14 +2258,15 @@ var Router = class _Router extends ForklaunchExpressLikeRouter {
|
|
291
2258
|
contentParse(options2),
|
292
2259
|
enrichResponseTransmission
|
293
2260
|
],
|
294
|
-
openTelemetryCollector
|
2261
|
+
openTelemetryCollector,
|
2262
|
+
options2
|
295
2263
|
);
|
296
2264
|
this.basePath = basePath;
|
297
2265
|
this.configOptions = options2;
|
298
2266
|
}
|
299
2267
|
configOptions;
|
300
|
-
route(
|
301
|
-
this.internal.route(
|
2268
|
+
route(path2) {
|
2269
|
+
this.internal.route(path2);
|
302
2270
|
return this;
|
303
2271
|
}
|
304
2272
|
param(name, _types, handler) {
|
@@ -481,229 +2449,233 @@ var Router = class _Router extends ForklaunchExpressLikeRouter {
|
|
481
2449
|
import {
|
482
2450
|
middleware
|
483
2451
|
} from "@forklaunch/core/http";
|
484
|
-
var checkout = (schemaValidator,
|
485
|
-
return middleware(schemaValidator,
|
2452
|
+
var checkout = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2453
|
+
return middleware(schemaValidator, path2, contractDetails, ...handlers2);
|
486
2454
|
};
|
487
2455
|
|
488
2456
|
// src/handlers/copy.ts
|
489
2457
|
import {
|
490
2458
|
middleware as middleware2
|
491
2459
|
} from "@forklaunch/core/http";
|
492
|
-
var copy = (schemaValidator,
|
493
|
-
return middleware2(schemaValidator,
|
2460
|
+
var copy = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2461
|
+
return middleware2(schemaValidator, path2, contractDetails, ...handlers2);
|
494
2462
|
};
|
495
2463
|
|
496
2464
|
// src/handlers/delete.ts
|
497
2465
|
import {
|
498
2466
|
delete_ as innerDelete
|
499
2467
|
} from "@forklaunch/core/http";
|
500
|
-
var delete_ = (schemaValidator,
|
501
|
-
return innerDelete(schemaValidator,
|
2468
|
+
var delete_ = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2469
|
+
return innerDelete(schemaValidator, path2, contractDetails, ...handlers2);
|
502
2470
|
};
|
503
2471
|
|
504
2472
|
// src/handlers/get.ts
|
505
2473
|
import {
|
506
2474
|
get as innerGet
|
507
2475
|
} from "@forklaunch/core/http";
|
508
|
-
var get = (schemaValidator,
|
509
|
-
return innerGet(schemaValidator,
|
2476
|
+
var get = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2477
|
+
return innerGet(schemaValidator, path2, contractDetails, ...handlers2);
|
510
2478
|
};
|
511
2479
|
|
512
2480
|
// src/handlers/head.ts
|
513
2481
|
import {
|
514
2482
|
head as innerHead
|
515
2483
|
} from "@forklaunch/core/http";
|
516
|
-
var head = (schemaValidator,
|
517
|
-
return innerHead(schemaValidator,
|
2484
|
+
var head = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2485
|
+
return innerHead(schemaValidator, path2, contractDetails, ...handlers2);
|
518
2486
|
};
|
519
2487
|
|
520
2488
|
// src/handlers/link.ts
|
521
2489
|
import {
|
522
2490
|
middleware as middleware3
|
523
2491
|
} from "@forklaunch/core/http";
|
524
|
-
var link = (schemaValidator,
|
525
|
-
return middleware3(schemaValidator,
|
2492
|
+
var link = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2493
|
+
return middleware3(schemaValidator, path2, contractDetails, ...handlers2);
|
526
2494
|
};
|
527
2495
|
|
528
2496
|
// src/handlers/lock.ts
|
529
2497
|
import {
|
530
2498
|
middleware as middleware4
|
531
2499
|
} from "@forklaunch/core/http";
|
532
|
-
var lock = (schemaValidator,
|
533
|
-
return middleware4(schemaValidator,
|
2500
|
+
var lock = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2501
|
+
return middleware4(schemaValidator, path2, contractDetails, ...handlers2);
|
534
2502
|
};
|
535
2503
|
|
536
2504
|
// src/handlers/m-search.ts
|
537
2505
|
import {
|
538
2506
|
middleware as middleware5
|
539
2507
|
} from "@forklaunch/core/http";
|
540
|
-
var mSearch = (schemaValidator,
|
541
|
-
return middleware5(schemaValidator,
|
2508
|
+
var mSearch = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2509
|
+
return middleware5(schemaValidator, path2, contractDetails, ...handlers2);
|
542
2510
|
};
|
543
2511
|
|
544
2512
|
// src/handlers/merge.ts
|
545
2513
|
import {
|
546
2514
|
middleware as middleware6
|
547
2515
|
} from "@forklaunch/core/http";
|
548
|
-
var merge = (schemaValidator,
|
549
|
-
return middleware6(schemaValidator,
|
2516
|
+
var merge = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2517
|
+
return middleware6(schemaValidator, path2, contractDetails, ...handlers2);
|
550
2518
|
};
|
551
2519
|
|
552
2520
|
// src/handlers/middleware.ts
|
553
2521
|
import {
|
554
2522
|
middleware as innerMiddleware
|
555
2523
|
} from "@forklaunch/core/http";
|
556
|
-
var middleware7 = (schemaValidator,
|
557
|
-
return innerMiddleware(schemaValidator,
|
2524
|
+
var middleware7 = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2525
|
+
return innerMiddleware(schemaValidator, path2, contractDetails, ...handlers2);
|
558
2526
|
};
|
559
2527
|
|
560
2528
|
// src/handlers/mkcactivity.ts
|
561
2529
|
import {
|
562
2530
|
middleware as middleware8
|
563
2531
|
} from "@forklaunch/core/http";
|
564
|
-
var mkcActivity = (schemaValidator,
|
565
|
-
return middleware8(schemaValidator,
|
2532
|
+
var mkcActivity = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2533
|
+
return middleware8(schemaValidator, path2, contractDetails, ...handlers2);
|
566
2534
|
};
|
567
2535
|
|
568
2536
|
// src/handlers/mkcol.ts
|
569
2537
|
import {
|
570
2538
|
middleware as middleware9
|
571
2539
|
} from "@forklaunch/core/http";
|
572
|
-
var mkcol = (schemaValidator,
|
573
|
-
return middleware9(schemaValidator,
|
2540
|
+
var mkcol = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2541
|
+
return middleware9(schemaValidator, path2, contractDetails, ...handlers2);
|
574
2542
|
};
|
575
2543
|
|
576
2544
|
// src/handlers/move.ts
|
577
2545
|
import {
|
578
2546
|
middleware as middleware10
|
579
2547
|
} from "@forklaunch/core/http";
|
580
|
-
var move = (schemaValidator,
|
581
|
-
return middleware10(schemaValidator,
|
2548
|
+
var move = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2549
|
+
return middleware10(schemaValidator, path2, contractDetails, ...handlers2);
|
582
2550
|
};
|
583
2551
|
|
584
2552
|
// src/handlers/notify.ts
|
585
2553
|
import {
|
586
2554
|
middleware as middleware11
|
587
2555
|
} from "@forklaunch/core/http";
|
588
|
-
var notify = (schemaValidator,
|
589
|
-
return middleware11(schemaValidator,
|
2556
|
+
var notify = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2557
|
+
return middleware11(schemaValidator, path2, contractDetails, ...handlers2);
|
590
2558
|
};
|
591
2559
|
|
592
2560
|
// src/handlers/options.ts
|
593
2561
|
import {
|
594
2562
|
options as innerOptions
|
595
2563
|
} from "@forklaunch/core/http";
|
596
|
-
var options = (schemaValidator,
|
597
|
-
return innerOptions(schemaValidator,
|
2564
|
+
var options = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2565
|
+
return innerOptions(schemaValidator, path2, contractDetails, ...handlers2);
|
598
2566
|
};
|
599
2567
|
|
600
2568
|
// src/handlers/patch.ts
|
601
2569
|
import {
|
602
2570
|
patch as innerPatch
|
603
2571
|
} from "@forklaunch/core/http";
|
604
|
-
var patch = (schemaValidator,
|
605
|
-
return innerPatch(schemaValidator,
|
2572
|
+
var patch = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2573
|
+
return innerPatch(schemaValidator, path2, contractDetails, ...handlers2);
|
606
2574
|
};
|
607
2575
|
|
608
2576
|
// src/handlers/post.ts
|
609
2577
|
import {
|
610
2578
|
post as innerPost
|
611
2579
|
} from "@forklaunch/core/http";
|
612
|
-
var post = (schemaValidator,
|
613
|
-
return innerPost(schemaValidator,
|
2580
|
+
var post = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2581
|
+
return innerPost(schemaValidator, path2, contractDetails, ...handlers2);
|
614
2582
|
};
|
615
2583
|
|
616
2584
|
// src/handlers/propfind.ts
|
617
2585
|
import {
|
618
2586
|
middleware as middleware12
|
619
2587
|
} from "@forklaunch/core/http";
|
620
|
-
var propfind = (schemaValidator,
|
621
|
-
return middleware12(schemaValidator,
|
2588
|
+
var propfind = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2589
|
+
return middleware12(schemaValidator, path2, contractDetails, ...handlers2);
|
622
2590
|
};
|
623
2591
|
|
624
2592
|
// src/handlers/proppatch.ts
|
625
2593
|
import {
|
626
2594
|
middleware as middleware13
|
627
2595
|
} from "@forklaunch/core/http";
|
628
|
-
var proppatch = (schemaValidator,
|
629
|
-
return middleware13(schemaValidator,
|
2596
|
+
var proppatch = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2597
|
+
return middleware13(schemaValidator, path2, contractDetails, ...handlers2);
|
630
2598
|
};
|
631
2599
|
|
632
2600
|
// src/handlers/purge.ts
|
633
2601
|
import {
|
634
2602
|
middleware as middleware14
|
635
2603
|
} from "@forklaunch/core/http";
|
636
|
-
var purge = (schemaValidator,
|
637
|
-
return middleware14(schemaValidator,
|
2604
|
+
var purge = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2605
|
+
return middleware14(schemaValidator, path2, contractDetails, ...handlers2);
|
638
2606
|
};
|
639
2607
|
|
640
2608
|
// src/handlers/put.ts
|
641
2609
|
import {
|
642
2610
|
put as innerPut
|
643
2611
|
} from "@forklaunch/core/http";
|
644
|
-
var put = (schemaValidator,
|
645
|
-
return innerPut(schemaValidator,
|
2612
|
+
var put = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2613
|
+
return innerPut(schemaValidator, path2, contractDetails, ...handlers2);
|
646
2614
|
};
|
647
2615
|
|
648
2616
|
// src/handlers/report.ts
|
649
2617
|
import {
|
650
2618
|
middleware as middleware15
|
651
2619
|
} from "@forklaunch/core/http";
|
652
|
-
var report = (schemaValidator,
|
653
|
-
return middleware15(schemaValidator,
|
2620
|
+
var report = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2621
|
+
return middleware15(schemaValidator, path2, contractDetails, ...handlers2);
|
654
2622
|
};
|
655
2623
|
|
656
2624
|
// src/handlers/search.ts
|
657
2625
|
import {
|
658
2626
|
middleware as middleware16
|
659
2627
|
} from "@forklaunch/core/http";
|
660
|
-
var search = (schemaValidator,
|
661
|
-
return middleware16(schemaValidator,
|
2628
|
+
var search = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2629
|
+
return middleware16(schemaValidator, path2, contractDetails, ...handlers2);
|
662
2630
|
};
|
663
2631
|
|
664
2632
|
// src/handlers/subscribe.ts
|
665
2633
|
import {
|
666
2634
|
middleware as middleware17
|
667
2635
|
} from "@forklaunch/core/http";
|
668
|
-
var subscribe = (schemaValidator,
|
669
|
-
return middleware17(schemaValidator,
|
2636
|
+
var subscribe = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2637
|
+
return middleware17(schemaValidator, path2, contractDetails, ...handlers2);
|
670
2638
|
};
|
671
2639
|
|
672
2640
|
// src/handlers/trace.ts
|
673
2641
|
import {
|
674
2642
|
trace as innerTrace
|
675
2643
|
} from "@forklaunch/core/http";
|
676
|
-
var trace = (schemaValidator,
|
677
|
-
return innerTrace(schemaValidator,
|
2644
|
+
var trace = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2645
|
+
return innerTrace(schemaValidator, path2, contractDetails, ...handlers2);
|
678
2646
|
};
|
679
2647
|
|
680
2648
|
// src/handlers/unlink.ts
|
681
2649
|
import {
|
682
2650
|
middleware as middleware18
|
683
2651
|
} from "@forklaunch/core/http";
|
684
|
-
var unlink = (schemaValidator,
|
685
|
-
return middleware18(schemaValidator,
|
2652
|
+
var unlink = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2653
|
+
return middleware18(schemaValidator, path2, contractDetails, ...handlers2);
|
686
2654
|
};
|
687
2655
|
|
688
2656
|
// src/handlers/unlock.ts
|
689
2657
|
import {
|
690
2658
|
middleware as middleware19
|
691
2659
|
} from "@forklaunch/core/http";
|
692
|
-
var unlock = (schemaValidator,
|
693
|
-
return middleware19(schemaValidator,
|
2660
|
+
var unlock = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2661
|
+
return middleware19(schemaValidator, path2, contractDetails, ...handlers2);
|
694
2662
|
};
|
695
2663
|
|
696
2664
|
// src/handlers/unsubscribe.ts
|
697
2665
|
import {
|
698
2666
|
middleware as middleware20
|
699
2667
|
} from "@forklaunch/core/http";
|
700
|
-
var unsubscribe = (schemaValidator,
|
701
|
-
return middleware20(schemaValidator,
|
2668
|
+
var unsubscribe = (schemaValidator, path2, contractDetails, ...handlers2) => {
|
2669
|
+
return middleware20(schemaValidator, path2, contractDetails, ...handlers2);
|
702
2670
|
};
|
703
2671
|
|
704
2672
|
// index.ts
|
705
2673
|
function forklaunchExpress(schemaValidator, openTelemetryCollector, options2) {
|
706
|
-
return new Application(
|
2674
|
+
return new Application(
|
2675
|
+
schemaValidator,
|
2676
|
+
openTelemetryCollector,
|
2677
|
+
options2
|
2678
|
+
);
|
707
2679
|
}
|
708
2680
|
function forklaunchRouter(basePath, schemaValidator, openTelemetryCollector, options2) {
|
709
2681
|
const router = new Router(
|