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