mikroserve 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +233 -0
- package/lib/MikroServe.d.mts +9 -0
- package/lib/MikroServe.d.ts +9 -0
- package/lib/MikroServe.js +302 -37
- package/lib/MikroServe.mjs +6 -5
- package/lib/RateLimiter.js +2 -1
- package/lib/RateLimiter.mjs +1 -1
- package/lib/Router.js +1 -1
- package/lib/Router.mjs +1 -1
- package/lib/{chunk-YKRH6T5M.mjs → chunk-7LU765PG.mjs} +2 -2
- package/lib/{chunk-N5ZQZGGT.mjs → chunk-C4IW4XUH.mjs} +176 -40
- package/lib/{chunk-JJX5XRNB.mjs → chunk-DMNHVQTU.mjs} +6 -0
- package/lib/{chunk-ZFBBESGU.mjs → chunk-OF5DEOIU.mjs} +2 -1
- package/lib/chunk-VLQ7ZZIU.mjs +105 -0
- package/lib/{chunk-YOHL3T54.mjs → chunk-ZT2UGCN5.mjs} +29 -4
- package/lib/config.js +32 -3
- package/lib/config.mjs +2 -2
- package/lib/index.d.mts +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +302 -37
- package/lib/index.mjs +6 -5
- package/lib/interfaces/index.d.mts +10 -0
- package/lib/interfaces/index.d.ts +10 -0
- package/lib/utils/configDefaults.d.mts +2 -0
- package/lib/utils/configDefaults.d.ts +2 -0
- package/lib/utils/configDefaults.js +6 -0
- package/lib/utils/configDefaults.mjs +1 -1
- package/lib/utils/multipartParser.d.mts +19 -0
- package/lib/utils/multipartParser.d.ts +19 -0
- package/lib/utils/multipartParser.js +129 -0
- package/lib/utils/multipartParser.mjs +6 -0
- package/package.json +4 -4
package/lib/MikroServe.js
CHANGED
|
@@ -74,7 +74,8 @@ var RateLimiter = class {
|
|
|
74
74
|
const now = Date.now();
|
|
75
75
|
const key = ip || "unknown";
|
|
76
76
|
const entry = this.requests.get(key);
|
|
77
|
-
if (!entry || entry.resetTime < now)
|
|
77
|
+
if (!entry || entry.resetTime < now)
|
|
78
|
+
return Math.floor((now + this.windowMs) / 1e3);
|
|
78
79
|
return Math.floor(entry.resetTime / 1e3);
|
|
79
80
|
}
|
|
80
81
|
cleanup() {
|
|
@@ -191,7 +192,7 @@ var Router = class {
|
|
|
191
192
|
res,
|
|
192
193
|
params,
|
|
193
194
|
query,
|
|
194
|
-
// @ts-
|
|
195
|
+
// @ts-expect-error
|
|
195
196
|
body: req.body || {},
|
|
196
197
|
headers: req.headers,
|
|
197
198
|
path,
|
|
@@ -332,6 +333,10 @@ var configDefaults = () => {
|
|
|
332
333
|
sslKey: "",
|
|
333
334
|
sslCa: "",
|
|
334
335
|
debug: getTruthyValue(process.env.DEBUG) || false,
|
|
336
|
+
maxBodySize: 1024 * 1024,
|
|
337
|
+
// 1MB
|
|
338
|
+
requestTimeout: 3e4,
|
|
339
|
+
// 30 seconds
|
|
335
340
|
rateLimit: {
|
|
336
341
|
enabled: true,
|
|
337
342
|
requestsPerMinute: 100
|
|
@@ -352,8 +357,18 @@ var baseConfig = (options) => ({
|
|
|
352
357
|
options: [
|
|
353
358
|
{ flag: "--port", path: "port", defaultValue: defaults.port },
|
|
354
359
|
{ flag: "--host", path: "host", defaultValue: defaults.host },
|
|
355
|
-
{
|
|
356
|
-
|
|
360
|
+
{
|
|
361
|
+
flag: "--https",
|
|
362
|
+
path: "useHttps",
|
|
363
|
+
defaultValue: defaults.useHttps,
|
|
364
|
+
isFlag: true
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
flag: "--http2",
|
|
368
|
+
path: "useHttp2",
|
|
369
|
+
defaultValue: defaults.useHttp2,
|
|
370
|
+
isFlag: true
|
|
371
|
+
},
|
|
357
372
|
{ flag: "--cert", path: "sslCert", defaultValue: defaults.sslCert },
|
|
358
373
|
{ flag: "--key", path: "sslKey", defaultValue: defaults.sslKey },
|
|
359
374
|
{ flag: "--ca", path: "sslCa", defaultValue: defaults.sslCa },
|
|
@@ -374,27 +389,148 @@ var baseConfig = (options) => ({
|
|
|
374
389
|
defaultValue: defaults.allowedDomains,
|
|
375
390
|
parser: import_mikroconf.parsers.array
|
|
376
391
|
},
|
|
377
|
-
{
|
|
392
|
+
{
|
|
393
|
+
flag: "--debug",
|
|
394
|
+
path: "debug",
|
|
395
|
+
defaultValue: defaults.debug,
|
|
396
|
+
isFlag: true
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
flag: "--max-body-size",
|
|
400
|
+
path: "maxBodySize",
|
|
401
|
+
defaultValue: defaults.maxBodySize
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
flag: "--request-timeout",
|
|
405
|
+
path: "requestTimeout",
|
|
406
|
+
defaultValue: defaults.requestTimeout
|
|
407
|
+
}
|
|
378
408
|
],
|
|
379
409
|
config: options
|
|
380
410
|
});
|
|
381
411
|
|
|
412
|
+
// src/utils/multipartParser.ts
|
|
413
|
+
function parseMultipartFormData(body, contentType) {
|
|
414
|
+
const boundaryMatch = contentType.match(/boundary=(?:"([^"]+)"|([^;]+))/i);
|
|
415
|
+
if (!boundaryMatch) {
|
|
416
|
+
throw new Error("Invalid multipart/form-data: missing boundary");
|
|
417
|
+
}
|
|
418
|
+
const boundary = boundaryMatch[1] || boundaryMatch[2];
|
|
419
|
+
const boundaryBuffer = Buffer.from(`--${boundary}`);
|
|
420
|
+
const endBoundaryBuffer = Buffer.from(`--${boundary}--`);
|
|
421
|
+
const fields = {};
|
|
422
|
+
const files = {};
|
|
423
|
+
const parts = splitByBoundary(body, boundaryBuffer);
|
|
424
|
+
for (const part of parts) {
|
|
425
|
+
if (part.length === 0) continue;
|
|
426
|
+
if (part.equals(endBoundaryBuffer.subarray(boundaryBuffer.length)))
|
|
427
|
+
continue;
|
|
428
|
+
const parsed = parsePart(part);
|
|
429
|
+
if (!parsed) continue;
|
|
430
|
+
const { name, filename, contentType: partContentType, data } = parsed;
|
|
431
|
+
if (filename) {
|
|
432
|
+
const file = {
|
|
433
|
+
filename,
|
|
434
|
+
contentType: partContentType || "application/octet-stream",
|
|
435
|
+
data,
|
|
436
|
+
size: data.length
|
|
437
|
+
};
|
|
438
|
+
if (files[name]) {
|
|
439
|
+
if (Array.isArray(files[name])) {
|
|
440
|
+
files[name].push(file);
|
|
441
|
+
} else {
|
|
442
|
+
files[name] = [files[name], file];
|
|
443
|
+
}
|
|
444
|
+
} else {
|
|
445
|
+
files[name] = file;
|
|
446
|
+
}
|
|
447
|
+
} else {
|
|
448
|
+
const value = data.toString("utf8");
|
|
449
|
+
if (fields[name]) {
|
|
450
|
+
if (Array.isArray(fields[name])) {
|
|
451
|
+
fields[name].push(value);
|
|
452
|
+
} else {
|
|
453
|
+
fields[name] = [fields[name], value];
|
|
454
|
+
}
|
|
455
|
+
} else {
|
|
456
|
+
fields[name] = value;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return { fields, files };
|
|
461
|
+
}
|
|
462
|
+
function splitByBoundary(buffer, boundary) {
|
|
463
|
+
const parts = [];
|
|
464
|
+
let start = 0;
|
|
465
|
+
while (start < buffer.length) {
|
|
466
|
+
const index = buffer.indexOf(boundary, start);
|
|
467
|
+
if (index === -1) break;
|
|
468
|
+
if (start !== index) {
|
|
469
|
+
parts.push(buffer.subarray(start, index));
|
|
470
|
+
}
|
|
471
|
+
start = index + boundary.length;
|
|
472
|
+
if (start < buffer.length && buffer[start] === 13 && buffer[start + 1] === 10) {
|
|
473
|
+
start += 2;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return parts;
|
|
477
|
+
}
|
|
478
|
+
function parsePart(part) {
|
|
479
|
+
const doubleCRLF = Buffer.from("\r\n\r\n");
|
|
480
|
+
const headerEndIndex = part.indexOf(doubleCRLF);
|
|
481
|
+
if (headerEndIndex === -1) return null;
|
|
482
|
+
const headersBuffer = part.subarray(0, headerEndIndex);
|
|
483
|
+
const dataBuffer = part.subarray(headerEndIndex + 4);
|
|
484
|
+
const headers = headersBuffer.toString("utf8");
|
|
485
|
+
const headerLines = headers.split("\r\n");
|
|
486
|
+
let disposition = "";
|
|
487
|
+
let name = "";
|
|
488
|
+
let filename;
|
|
489
|
+
let contentType;
|
|
490
|
+
for (const line of headerLines) {
|
|
491
|
+
const lowerLine = line.toLowerCase();
|
|
492
|
+
if (lowerLine.startsWith("content-disposition:")) {
|
|
493
|
+
disposition = line.substring("content-disposition:".length).trim();
|
|
494
|
+
const nameMatch = disposition.match(/name="([^"]+)"/);
|
|
495
|
+
if (nameMatch) {
|
|
496
|
+
name = nameMatch[1];
|
|
497
|
+
}
|
|
498
|
+
const filenameMatch = disposition.match(/filename="([^"]+)"/);
|
|
499
|
+
if (filenameMatch) {
|
|
500
|
+
filename = filenameMatch[1];
|
|
501
|
+
}
|
|
502
|
+
} else if (lowerLine.startsWith("content-type:")) {
|
|
503
|
+
contentType = line.substring("content-type:".length).trim();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (!name) return null;
|
|
507
|
+
let data = dataBuffer;
|
|
508
|
+
if (data.length >= 2 && data[data.length - 2] === 13 && data[data.length - 1] === 10) {
|
|
509
|
+
data = data.subarray(0, data.length - 2);
|
|
510
|
+
}
|
|
511
|
+
return { disposition, name, filename, contentType, data };
|
|
512
|
+
}
|
|
513
|
+
|
|
382
514
|
// src/MikroServe.ts
|
|
383
515
|
var MikroServe = class {
|
|
384
516
|
config;
|
|
385
517
|
rateLimiter;
|
|
386
518
|
router;
|
|
519
|
+
shutdownHandlers = [];
|
|
387
520
|
/**
|
|
388
521
|
* @description Creates a new MikroServe instance.
|
|
389
522
|
*/
|
|
390
523
|
constructor(options) {
|
|
391
|
-
const config = new import_mikroconf2.MikroConf(
|
|
524
|
+
const config = new import_mikroconf2.MikroConf(
|
|
525
|
+
baseConfig(options || {})
|
|
526
|
+
).get();
|
|
392
527
|
if (config.debug) console.log("Using configuration:", config);
|
|
393
528
|
this.config = config;
|
|
394
529
|
this.router = new Router();
|
|
395
530
|
const requestsPerMinute = config.rateLimit.requestsPerMinute || configDefaults().rateLimit.requestsPerMinute;
|
|
396
531
|
this.rateLimiter = new RateLimiter(requestsPerMinute, 60);
|
|
397
|
-
if (config.rateLimit.enabled === true)
|
|
532
|
+
if (config.rateLimit.enabled === true)
|
|
533
|
+
this.use(this.rateLimitMiddleware.bind(this));
|
|
398
534
|
}
|
|
399
535
|
/**
|
|
400
536
|
* @description Register a global middleware.
|
|
@@ -475,7 +611,9 @@ var MikroServe = class {
|
|
|
475
611
|
const boundRequestHandler = this.requestHandler.bind(this);
|
|
476
612
|
if (this.config.useHttp2) {
|
|
477
613
|
if (!this.config.sslCert || !this.config.sslKey)
|
|
478
|
-
throw new Error(
|
|
614
|
+
throw new Error(
|
|
615
|
+
"SSL certificate and key paths are required when useHttp2 is true"
|
|
616
|
+
);
|
|
479
617
|
try {
|
|
480
618
|
const httpsOptions = {
|
|
481
619
|
key: (0, import_node_fs.readFileSync)(this.config.sslKey),
|
|
@@ -485,12 +623,16 @@ var MikroServe = class {
|
|
|
485
623
|
return import_node_http2.default.createSecureServer(httpsOptions, boundRequestHandler);
|
|
486
624
|
} catch (error) {
|
|
487
625
|
if (error.message.includes("key values mismatch"))
|
|
488
|
-
throw new Error(
|
|
626
|
+
throw new Error(
|
|
627
|
+
`SSL certificate and key do not match: ${error.message}`
|
|
628
|
+
);
|
|
489
629
|
throw error;
|
|
490
630
|
}
|
|
491
631
|
} else if (this.config.useHttps) {
|
|
492
632
|
if (!this.config.sslCert || !this.config.sslKey)
|
|
493
|
-
throw new Error(
|
|
633
|
+
throw new Error(
|
|
634
|
+
"SSL certificate and key paths are required when useHttps is true"
|
|
635
|
+
);
|
|
494
636
|
try {
|
|
495
637
|
const httpsOptions = {
|
|
496
638
|
key: (0, import_node_fs.readFileSync)(this.config.sslKey),
|
|
@@ -500,7 +642,9 @@ var MikroServe = class {
|
|
|
500
642
|
return import_node_https.default.createServer(httpsOptions, boundRequestHandler);
|
|
501
643
|
} catch (error) {
|
|
502
644
|
if (error.message.includes("key values mismatch"))
|
|
503
|
-
throw new Error(
|
|
645
|
+
throw new Error(
|
|
646
|
+
`SSL certificate and key do not match: ${error.message}`
|
|
647
|
+
);
|
|
504
648
|
throw error;
|
|
505
649
|
}
|
|
506
650
|
}
|
|
@@ -511,12 +655,18 @@ var MikroServe = class {
|
|
|
511
655
|
*/
|
|
512
656
|
async rateLimitMiddleware(context, next) {
|
|
513
657
|
const ip = context.req.socket.remoteAddress || "unknown";
|
|
514
|
-
context.res.setHeader(
|
|
658
|
+
context.res.setHeader(
|
|
659
|
+
"X-RateLimit-Limit",
|
|
660
|
+
this.rateLimiter.getLimit().toString()
|
|
661
|
+
);
|
|
515
662
|
context.res.setHeader(
|
|
516
663
|
"X-RateLimit-Remaining",
|
|
517
664
|
this.rateLimiter.getRemainingRequests(ip).toString()
|
|
518
665
|
);
|
|
519
|
-
context.res.setHeader(
|
|
666
|
+
context.res.setHeader(
|
|
667
|
+
"X-RateLimit-Reset",
|
|
668
|
+
this.rateLimiter.getResetTime(ip).toString()
|
|
669
|
+
);
|
|
520
670
|
if (!this.rateLimiter.isAllowed(ip)) {
|
|
521
671
|
return {
|
|
522
672
|
statusCode: 429,
|
|
@@ -603,44 +753,87 @@ var MikroServe = class {
|
|
|
603
753
|
return new Promise((resolve, reject) => {
|
|
604
754
|
const bodyChunks = [];
|
|
605
755
|
let bodySize = 0;
|
|
606
|
-
const
|
|
607
|
-
let
|
|
756
|
+
const maxBodySize = this.config.maxBodySize;
|
|
757
|
+
let settled = false;
|
|
758
|
+
let timeoutId = null;
|
|
608
759
|
const isDebug = this.config.debug;
|
|
609
760
|
const contentType = req.headers["content-type"] || "";
|
|
610
761
|
if (isDebug) {
|
|
611
762
|
console.log("Content-Type:", contentType);
|
|
612
763
|
}
|
|
764
|
+
if (this.config.requestTimeout > 0) {
|
|
765
|
+
timeoutId = setTimeout(() => {
|
|
766
|
+
if (!settled) {
|
|
767
|
+
settled = true;
|
|
768
|
+
if (isDebug) console.log("Request timeout exceeded");
|
|
769
|
+
reject(new Error("Request timeout"));
|
|
770
|
+
}
|
|
771
|
+
}, this.config.requestTimeout);
|
|
772
|
+
}
|
|
773
|
+
const cleanup = () => {
|
|
774
|
+
if (timeoutId) {
|
|
775
|
+
clearTimeout(timeoutId);
|
|
776
|
+
timeoutId = null;
|
|
777
|
+
}
|
|
778
|
+
};
|
|
613
779
|
req.on("data", (chunk) => {
|
|
780
|
+
if (settled) {
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
614
783
|
bodySize += chunk.length;
|
|
615
|
-
if (isDebug)
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
784
|
+
if (isDebug)
|
|
785
|
+
console.log(
|
|
786
|
+
`Received chunk: ${chunk.length} bytes, total size: ${bodySize}`
|
|
787
|
+
);
|
|
788
|
+
if (bodySize > maxBodySize) {
|
|
789
|
+
settled = true;
|
|
790
|
+
cleanup();
|
|
791
|
+
if (isDebug)
|
|
792
|
+
console.log(
|
|
793
|
+
`Body size exceeded limit: ${bodySize} > ${maxBodySize}`
|
|
794
|
+
);
|
|
619
795
|
reject(new Error("Request body too large"));
|
|
620
796
|
return;
|
|
621
797
|
}
|
|
622
|
-
|
|
798
|
+
bodyChunks.push(chunk);
|
|
623
799
|
});
|
|
624
800
|
req.on("end", () => {
|
|
625
|
-
if (
|
|
801
|
+
if (settled) return;
|
|
802
|
+
settled = true;
|
|
803
|
+
cleanup();
|
|
626
804
|
if (isDebug) console.log(`Request body complete: ${bodySize} bytes`);
|
|
627
805
|
try {
|
|
628
806
|
if (bodyChunks.length > 0) {
|
|
629
|
-
const
|
|
807
|
+
const bodyBuffer = Buffer.concat(bodyChunks);
|
|
630
808
|
if (contentType.includes("application/json")) {
|
|
631
809
|
try {
|
|
810
|
+
const bodyString = bodyBuffer.toString("utf8");
|
|
632
811
|
resolve(JSON.parse(bodyString));
|
|
633
812
|
} catch (error) {
|
|
634
|
-
reject(
|
|
813
|
+
reject(
|
|
814
|
+
new Error(`Invalid JSON in request body: ${error.message}`)
|
|
815
|
+
);
|
|
635
816
|
}
|
|
636
817
|
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
818
|
+
const bodyString = bodyBuffer.toString("utf8");
|
|
637
819
|
const formData = {};
|
|
638
820
|
new URLSearchParams(bodyString).forEach((value, key) => {
|
|
639
821
|
formData[key] = value;
|
|
640
822
|
});
|
|
641
823
|
resolve(formData);
|
|
824
|
+
} else if (contentType.includes("multipart/form-data")) {
|
|
825
|
+
try {
|
|
826
|
+
const parsed = parseMultipartFormData(bodyBuffer, contentType);
|
|
827
|
+
resolve(parsed);
|
|
828
|
+
} catch (error) {
|
|
829
|
+
reject(
|
|
830
|
+
new Error(`Invalid multipart form data: ${error.message}`)
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
} else if (this.isBinaryContentType(contentType)) {
|
|
834
|
+
resolve(bodyBuffer);
|
|
642
835
|
} else {
|
|
643
|
-
resolve(
|
|
836
|
+
resolve(bodyBuffer.toString("utf8"));
|
|
644
837
|
}
|
|
645
838
|
} else {
|
|
646
839
|
resolve({});
|
|
@@ -650,24 +843,61 @@ var MikroServe = class {
|
|
|
650
843
|
}
|
|
651
844
|
});
|
|
652
845
|
req.on("error", (error) => {
|
|
653
|
-
if (!
|
|
846
|
+
if (!settled) {
|
|
847
|
+
settled = true;
|
|
848
|
+
cleanup();
|
|
849
|
+
reject(new Error(`Error reading request body: ${error.message}`));
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
req.on("close", () => {
|
|
853
|
+
cleanup();
|
|
654
854
|
});
|
|
655
855
|
});
|
|
656
856
|
}
|
|
857
|
+
/**
|
|
858
|
+
* @description Checks if a content type is binary.
|
|
859
|
+
*/
|
|
860
|
+
isBinaryContentType(contentType) {
|
|
861
|
+
const binaryTypes = [
|
|
862
|
+
"application/octet-stream",
|
|
863
|
+
"application/pdf",
|
|
864
|
+
"application/zip",
|
|
865
|
+
"application/gzip",
|
|
866
|
+
"application/x-tar",
|
|
867
|
+
"application/x-rar-compressed",
|
|
868
|
+
"application/x-7z-compressed",
|
|
869
|
+
"image/",
|
|
870
|
+
"video/",
|
|
871
|
+
"audio/",
|
|
872
|
+
"application/vnd.ms-excel",
|
|
873
|
+
"application/vnd.openxmlformats-officedocument",
|
|
874
|
+
"application/msword",
|
|
875
|
+
"application/vnd.ms-powerpoint"
|
|
876
|
+
];
|
|
877
|
+
return binaryTypes.some((type) => contentType.includes(type));
|
|
878
|
+
}
|
|
657
879
|
/**
|
|
658
880
|
* @description CORS middleware.
|
|
659
881
|
*/
|
|
660
882
|
setCorsHeaders(res, req) {
|
|
661
883
|
const origin = req.headers.origin;
|
|
662
884
|
const { allowedDomains = ["*"] } = this.config;
|
|
663
|
-
if (!origin || allowedDomains.length === 0)
|
|
664
|
-
|
|
885
|
+
if (!origin || allowedDomains.length === 0)
|
|
886
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
887
|
+
else if (allowedDomains.includes("*"))
|
|
888
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
665
889
|
else if (allowedDomains.includes(origin)) {
|
|
666
890
|
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
667
891
|
res.setHeader("Vary", "Origin");
|
|
668
892
|
}
|
|
669
|
-
res.setHeader(
|
|
670
|
-
|
|
893
|
+
res.setHeader(
|
|
894
|
+
"Access-Control-Allow-Methods",
|
|
895
|
+
"GET, POST, PUT, DELETE, PATCH, OPTIONS"
|
|
896
|
+
);
|
|
897
|
+
res.setHeader(
|
|
898
|
+
"Access-Control-Allow-Headers",
|
|
899
|
+
"Content-Type, Authorization"
|
|
900
|
+
);
|
|
671
901
|
res.setHeader("Access-Control-Max-Age", "86400");
|
|
672
902
|
}
|
|
673
903
|
/**
|
|
@@ -710,11 +940,15 @@ var MikroServe = class {
|
|
|
710
940
|
else if (typeof response.body === "string") res.end(response.body);
|
|
711
941
|
else res.end(JSON.stringify(response.body));
|
|
712
942
|
} else {
|
|
713
|
-
console.warn(
|
|
943
|
+
console.warn(
|
|
944
|
+
"Unexpected response object type without writeHead/end methods"
|
|
945
|
+
);
|
|
714
946
|
res.writeHead?.(response.statusCode, headers);
|
|
715
|
-
if (response.body === null || response.body === void 0)
|
|
947
|
+
if (response.body === null || response.body === void 0)
|
|
948
|
+
res.end?.();
|
|
716
949
|
else if (response.isRaw) res.end?.(response.body);
|
|
717
|
-
else if (typeof response.body === "string")
|
|
950
|
+
else if (typeof response.body === "string")
|
|
951
|
+
res.end?.(response.body);
|
|
718
952
|
else res.end?.(JSON.stringify(response.body));
|
|
719
953
|
}
|
|
720
954
|
}
|
|
@@ -725,15 +959,46 @@ var MikroServe = class {
|
|
|
725
959
|
const shutdown = (error) => {
|
|
726
960
|
console.log("Shutting down MikroServe server...");
|
|
727
961
|
if (error) console.error("Error:", error);
|
|
962
|
+
this.cleanupShutdownHandlers();
|
|
728
963
|
server.close(() => {
|
|
729
964
|
console.log("Server closed successfully");
|
|
730
|
-
|
|
965
|
+
if (process.env.NODE_ENV !== "test" && process.env.VITEST !== "true") {
|
|
966
|
+
setImmediate(() => process.exit(error ? 1 : 0));
|
|
967
|
+
}
|
|
731
968
|
});
|
|
732
969
|
};
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
970
|
+
const sigintHandler = () => shutdown();
|
|
971
|
+
const sigtermHandler = () => shutdown();
|
|
972
|
+
const uncaughtExceptionHandler = (error) => shutdown(error);
|
|
973
|
+
const unhandledRejectionHandler = (error) => shutdown(error);
|
|
974
|
+
this.shutdownHandlers = [
|
|
975
|
+
sigintHandler,
|
|
976
|
+
sigtermHandler,
|
|
977
|
+
uncaughtExceptionHandler,
|
|
978
|
+
unhandledRejectionHandler
|
|
979
|
+
];
|
|
980
|
+
process.on("SIGINT", sigintHandler);
|
|
981
|
+
process.on("SIGTERM", sigtermHandler);
|
|
982
|
+
process.on("uncaughtException", uncaughtExceptionHandler);
|
|
983
|
+
process.on("unhandledRejection", unhandledRejectionHandler);
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* @description Cleans up shutdown event listeners to prevent memory leaks.
|
|
987
|
+
*/
|
|
988
|
+
cleanupShutdownHandlers() {
|
|
989
|
+
if (this.shutdownHandlers.length > 0) {
|
|
990
|
+
const [
|
|
991
|
+
sigintHandler,
|
|
992
|
+
sigtermHandler,
|
|
993
|
+
uncaughtExceptionHandler,
|
|
994
|
+
unhandledRejectionHandler
|
|
995
|
+
] = this.shutdownHandlers;
|
|
996
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
997
|
+
process.removeListener("SIGTERM", sigtermHandler);
|
|
998
|
+
process.removeListener("uncaughtException", uncaughtExceptionHandler);
|
|
999
|
+
process.removeListener("unhandledRejection", unhandledRejectionHandler);
|
|
1000
|
+
this.shutdownHandlers = [];
|
|
1001
|
+
}
|
|
737
1002
|
}
|
|
738
1003
|
};
|
|
739
1004
|
// Annotate the CommonJS export names for ESM import in node:
|
package/lib/MikroServe.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MikroServe
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-C4IW4XUH.mjs";
|
|
4
|
+
import "./chunk-OF5DEOIU.mjs";
|
|
5
|
+
import "./chunk-7LU765PG.mjs";
|
|
6
|
+
import "./chunk-ZT2UGCN5.mjs";
|
|
7
|
+
import "./chunk-DMNHVQTU.mjs";
|
|
8
|
+
import "./chunk-VLQ7ZZIU.mjs";
|
|
8
9
|
export {
|
|
9
10
|
MikroServe
|
|
10
11
|
};
|
package/lib/RateLimiter.js
CHANGED
|
@@ -57,7 +57,8 @@ var RateLimiter = class {
|
|
|
57
57
|
const now = Date.now();
|
|
58
58
|
const key = ip || "unknown";
|
|
59
59
|
const entry = this.requests.get(key);
|
|
60
|
-
if (!entry || entry.resetTime < now)
|
|
60
|
+
if (!entry || entry.resetTime < now)
|
|
61
|
+
return Math.floor((now + this.windowMs) / 1e3);
|
|
61
62
|
return Math.floor(entry.resetTime / 1e3);
|
|
62
63
|
}
|
|
63
64
|
cleanup() {
|
package/lib/RateLimiter.mjs
CHANGED
package/lib/Router.js
CHANGED
package/lib/Router.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/Router.ts
|
|
2
|
-
import { URL } from "
|
|
2
|
+
import { URL } from "url";
|
|
3
3
|
var Router = class {
|
|
4
4
|
routes = [];
|
|
5
5
|
globalMiddlewares = [];
|
|
@@ -104,7 +104,7 @@ var Router = class {
|
|
|
104
104
|
res,
|
|
105
105
|
params,
|
|
106
106
|
query,
|
|
107
|
-
// @ts-
|
|
107
|
+
// @ts-expect-error
|
|
108
108
|
body: req.body || {},
|
|
109
109
|
headers: req.headers,
|
|
110
110
|
path,
|