@trpc/server 10.41.0 → 10.43.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.
Files changed (35) hide show
  1. package/dist/adapters/fastify/index.js +1 -0
  2. package/dist/adapters/fastify/index.mjs +1 -0
  3. package/dist/adapters/node-http/content-type/form-data/index.d.ts +8 -4
  4. package/dist/adapters/node-http/content-type/form-data/index.d.ts.map +1 -1
  5. package/dist/adapters/node-http/content-type/form-data/index.js +127 -66
  6. package/dist/adapters/node-http/content-type/form-data/index.mjs +103 -63
  7. package/dist/adapters/node-http/content-type/form-data/memoryUploadHandler.d.ts.map +1 -1
  8. package/dist/adapters/node-http/content-type/form-data/uploadHandler.d.ts +5 -1
  9. package/dist/adapters/node-http/content-type/form-data/uploadHandler.d.ts.map +1 -1
  10. package/dist/adapters/ws.d.ts +0 -3
  11. package/dist/adapters/ws.d.ts.map +1 -1
  12. package/dist/adapters/ws.js +3 -55
  13. package/dist/adapters/ws.mjs +4 -55
  14. package/dist/core/internals/procedureBuilder.d.ts +1 -1
  15. package/dist/core/internals/procedureBuilder.d.ts.map +1 -1
  16. package/dist/core/middleware.d.ts +3 -3
  17. package/dist/core/middleware.d.ts.map +1 -1
  18. package/dist/parseTRPCMessage-1377f305.js +56 -0
  19. package/dist/parseTRPCMessage-95955211.js +63 -0
  20. package/dist/parseTRPCMessage-a0f17853.mjs +54 -0
  21. package/dist/rpc/index.d.ts +1 -0
  22. package/dist/rpc/index.d.ts.map +1 -1
  23. package/dist/rpc/index.js +2 -0
  24. package/dist/rpc/index.mjs +1 -0
  25. package/dist/rpc/parseTRPCMessage.d.ts +5 -0
  26. package/dist/rpc/parseTRPCMessage.d.ts.map +1 -0
  27. package/package.json +2 -2
  28. package/src/adapters/node-http/content-type/form-data/index.ts +71 -16
  29. package/src/adapters/node-http/content-type/form-data/memoryUploadHandler.ts +1 -5
  30. package/src/adapters/node-http/content-type/form-data/uploadHandler.ts +9 -7
  31. package/src/adapters/ws.ts +3 -79
  32. package/src/core/internals/procedureBuilder.ts +3 -1
  33. package/src/core/middleware.ts +7 -2
  34. package/src/rpc/index.ts +1 -0
  35. package/src/rpc/parseTRPCMessage.ts +84 -0
@@ -13,6 +13,7 @@ require('../../TRPCError-ae7b67e8.js');
13
13
  require('../../transformTRPCResponse-e65f34e9.js');
14
14
  require('../../contentType-90bad7bb.js');
15
15
  require('../../observable-464116ac.js');
16
+ require('../../parseTRPCMessage-1377f305.js');
16
17
 
17
18
  async function fastifyRequestHandler(opts) {
18
19
  const createContext = async function _createContext() {
@@ -9,6 +9,7 @@ import '../../TRPCError-816ff32e.mjs';
9
9
  import '../../transformTRPCResponse-1153b421.mjs';
10
10
  import '../../contentType-93515a46.mjs';
11
11
  import '../../observable-ade1bad8.mjs';
12
+ import '../../parseTRPCMessage-a0f17853.mjs';
12
13
 
13
14
  async function fastifyRequestHandler(opts) {
14
15
  const createContext = async function _createContext() {
@@ -8,17 +8,21 @@ import { NodeHTTPRequest } from '../../types';
8
8
  import { UploadHandler } from './uploadHandler';
9
9
  /**
10
10
  * Allows you to handle multipart forms (file uploads) for your app.
11
+ * Request body parts with a 'filename' property will be treated as files.
12
+ * The rest will be treated as text.
13
+ * @param request The incoming Node HTTP request
14
+ * @param uploadHandler A function that handles file uploads and returns a value to be used in the request body. If uploaded to disk, the returned value is a NodeOnDiskFile. If uploaded to memory, the returned value is a File.
15
+ * @param maxBodySize The maximum size of the request body in bytes. Defaults to Infinity.
11
16
  *
12
- * TODO: Update this comment
13
17
  * @see https://remix.run/utils/parse-multipart-form-data
14
18
  */
15
- declare function parseMultipartFormData(request: NodeHTTPRequest, uploadHandler: UploadHandler): Promise<FormData>;
19
+ declare function parseMultipartFormData(request: NodeHTTPRequest, uploadHandler: UploadHandler, maxBodySize?: number): Promise<FormData>;
16
20
  declare function isMultipartFormDataRequest(req: NodeHTTPRequest): boolean;
17
21
  export declare const nodeHTTPFormDataContentTypeHandler: <TRequest extends NodeHTTPRequest, TResponse extends import("../../types").NodeHTTPResponse>() => import("../../internals/contentType").NodeHTTPContentTypeHandler<TRequest, TResponse>;
18
22
  export { parseMultipartFormData as experimental_parseMultipartFormData };
19
23
  export { createMemoryUploadHandler as experimental_createMemoryUploadHandler } from './memoryUploadHandler';
20
- export { createFileUploadHandler as experimental_createFileUploadHandler } from './fileUploadHandler';
21
- export { composeUploadHandlers as experimental_composeUploadHandlers, MaxPartSizeExceededError, } from './uploadHandler';
24
+ export { createFileUploadHandler as experimental_createFileUploadHandler, NodeOnDiskFile as experimental_NodeOnDiskFile, } from './fileUploadHandler';
25
+ export { composeUploadHandlers as experimental_composeUploadHandlers, MaxPartSizeExceededError, MaxBodySizeExceededError, } from './uploadHandler';
22
26
  export { type UploadHandler } from './uploadHandler';
23
27
  export { isMultipartFormDataRequest as experimental_isMultipartFormDataRequest };
24
28
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/adapters/node-http/content-type/form-data/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAqB,MAAM,iBAAiB,CAAC;AAEnE;;;;;GAKG;AACH,iBAAe,sBAAsB,CACnC,OAAO,EAAE,eAAe,EACxB,aAAa,EAAE,aAAa,GAC3B,OAAO,CAAC,QAAQ,CAAC,CA6BnB;AAED,iBAAS,0BAA0B,CAAC,GAAG,EAAE,eAAe,WAMvD;AAED,eAAO,MAAM,kCAAkC,yLAgC3C,CAAC;AAEL,OAAO,EAAE,sBAAsB,IAAI,mCAAmC,EAAE,CAAC;AACzE,OAAO,EAAE,yBAAyB,IAAI,sCAAsC,EAAE,MAAM,uBAAuB,CAAC;AAC5G,OAAO,EAAE,uBAAuB,IAAI,oCAAoC,EAAE,MAAM,qBAAqB,CAAC;AACtG,OAAO,EACL,qBAAqB,IAAI,kCAAkC,EAC3D,wBAAwB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,0BAA0B,IAAI,uCAAuC,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/adapters/node-http/content-type/form-data/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAEL,aAAa,EAEd,MAAM,iBAAiB,CAAC;AAIzB;;;;;;;;;GASG;AACH,iBAAe,sBAAsB,CACnC,OAAO,EAAE,eAAe,EACxB,aAAa,EAAE,aAAa,EAC5B,WAAW,SAAW,qBAoEvB;AAED,iBAAS,0BAA0B,CAAC,GAAG,EAAE,eAAe,WAMvD;AAED,eAAO,MAAM,kCAAkC,yLAgC3C,CAAC;AAEL,OAAO,EAAE,sBAAsB,IAAI,mCAAmC,EAAE,CAAC;AACzE,OAAO,EAAE,yBAAyB,IAAI,sCAAsC,EAAE,MAAM,uBAAuB,CAAC;AAC5G,OAAO,EACL,uBAAuB,IAAI,oCAAoC,EAC/D,cAAc,IAAI,2BAA2B,GAC9C,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,qBAAqB,IAAI,kCAAkC,EAC3D,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,0BAA0B,IAAI,uCAAuC,EAAE,CAAC"}
@@ -2,15 +2,35 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var fs = require('node:fs/promises');
5
6
  var node_stream = require('node:stream');
6
7
  var contentType = require('../../../../contentType-8c16408e.js');
7
8
  var node_crypto = require('node:crypto');
8
9
  var node_fs = require('node:fs');
9
- var promises = require('node:fs/promises');
10
10
  var node_os = require('node:os');
11
11
  var node_path = require('node:path');
12
12
  var node_util = require('node:util');
13
13
 
14
+ function _interopNamespace(e) {
15
+ if (e && e.__esModule) return e;
16
+ var n = Object.create(null);
17
+ if (e) {
18
+ Object.keys(e).forEach(function (k) {
19
+ if (k !== 'default') {
20
+ var d = Object.getOwnPropertyDescriptor(e, k);
21
+ Object.defineProperty(n, k, d.get ? d : {
22
+ enumerable: true,
23
+ get: function () { return e[k]; }
24
+ });
25
+ }
26
+ });
27
+ }
28
+ n["default"] = e;
29
+ return Object.freeze(n);
30
+ }
31
+
32
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
33
+
14
34
  function stringToArray(s) {
15
35
  const utf8 = unescape(encodeURIComponent(s));
16
36
  return Uint8Array.from(utf8, (_, i) => utf8.charCodeAt(i));
@@ -397,54 +417,6 @@ async function* streamMultipart(body, boundary) {
397
417
  }
398
418
  }
399
419
 
400
- function composeUploadHandlers(...handlers) {
401
- return async (part)=>{
402
- for (const handler of handlers){
403
- const value = await handler(part);
404
- if (typeof value !== 'undefined' && value !== null) {
405
- return value;
406
- }
407
- }
408
- return undefined;
409
- };
410
- }
411
- class MaxPartSizeExceededError extends Error {
412
- constructor(field, maxBytes){
413
- super(`Field "${field}" exceeded upload size of ${maxBytes} bytes.`);
414
- this.field = field;
415
- this.maxBytes = maxBytes;
416
- }
417
- }
418
-
419
- function createMemoryUploadHandler({ filter , maxPartSize =3000000 } = {}) {
420
- return async ({ filename , contentType , name , data })=>{
421
- if (filter && !await filter({
422
- filename,
423
- contentType,
424
- name
425
- })) {
426
- return undefined;
427
- }
428
- let size = 0;
429
- const chunks = [];
430
- for await (const chunk of data){
431
- size += chunk.byteLength;
432
- if (size > maxPartSize) {
433
- throw new MaxPartSizeExceededError(name, maxPartSize);
434
- }
435
- chunks.push(chunk);
436
- }
437
- if (typeof filename === 'string') {
438
- return new File(chunks, filename, {
439
- type: contentType
440
- });
441
- }
442
- return await new Blob(chunks, {
443
- type: contentType
444
- }).text();
445
- };
446
- }
447
-
448
420
  class SliceStream extends node_stream.Transform {
449
421
  _transform(chunk, _, done) {
450
422
  this.indexOffset += chunk.length;
@@ -486,6 +458,31 @@ function streamSlice(startIndex = 0, endIndex = Infinity) {
486
458
  return new SliceStream(startIndex, endIndex);
487
459
  }
488
460
 
461
+ function composeUploadHandlers(...handlers) {
462
+ return async (part)=>{
463
+ for (const handler of handlers){
464
+ const value = await handler(part);
465
+ if (typeof value !== 'undefined' && value !== null) {
466
+ return value;
467
+ }
468
+ }
469
+ return undefined;
470
+ };
471
+ }
472
+ class MaxPartSizeExceededError extends Error {
473
+ constructor(field, maxBytes){
474
+ super(`Field "${field}" exceeded upload size of ${maxBytes} bytes.`);
475
+ this.field = field;
476
+ this.maxBytes = maxBytes;
477
+ }
478
+ }
479
+ class MaxBodySizeExceededError extends Error {
480
+ constructor(maxBytes){
481
+ super(`Body exceeded upload size of ${maxBytes} bytes.`);
482
+ this.maxBytes = maxBytes;
483
+ }
484
+ }
485
+
489
486
  async function readableStreamToString(stream, encoding) {
490
487
  const reader = stream.getReader();
491
488
  const chunks = [];
@@ -508,7 +505,7 @@ const defaultFilePathResolver = ({ filename , })=>{
508
505
  async function uniqueFile(filepath) {
509
506
  const ext = node_path.extname(filepath);
510
507
  let uniqueFilepath = filepath;
511
- for(let i = 1; await promises.stat(uniqueFilepath).then(()=>true).catch(()=>false); i++){
508
+ for(let i = 1; await fs.stat(uniqueFilepath).then(()=>true).catch(()=>false); i++){
512
509
  uniqueFilepath = (ext ? filepath.slice(0, -ext.length) : filepath) + `-${new Date().getTime()}${ext}`;
513
510
  }
514
511
  return uniqueFilepath;
@@ -543,7 +540,7 @@ function createFileUploadHandler({ directory =node_os.tmpdir() , avoidFileConfli
543
540
  if (avoidFileConflicts) {
544
541
  filepath = await uniqueFile(filepath);
545
542
  }
546
- await promises.mkdir(node_path.dirname(filepath), {
543
+ await fs.mkdir(node_path.dirname(filepath), {
547
544
  recursive: true
548
545
  }).catch(()=>{});
549
546
  const writeFileStream = node_fs.createWriteStream(filepath);
@@ -562,7 +559,7 @@ function createFileUploadHandler({ directory =node_os.tmpdir() , avoidFileConfli
562
559
  writeFileStream.end();
563
560
  await node_util.promisify(node_stream.finished)(writeFileStream);
564
561
  if (deleteFile) {
565
- await promises.rm(filepath).catch(()=>{});
562
+ await fs.rm(filepath).catch(()=>{});
566
563
  }
567
564
  }
568
565
  return new NodeOnDiskFile(filepath, contentType);
@@ -616,7 +613,7 @@ class NodeOnDiskFile {
616
613
  return readableStreamToString(this.stream());
617
614
  }
618
615
  remove() {
619
- return promises.unlink(this.filepath);
616
+ return fs.unlink(this.filepath);
620
617
  }
621
618
  getFilePath() {
622
619
  return this.filepath;
@@ -632,12 +629,41 @@ class NodeOnDiskFile {
632
629
  }
633
630
  }
634
631
 
632
+ function createMemoryUploadHandler({ filter , maxPartSize =3000000 } = {}) {
633
+ return async ({ filename , contentType , name , data })=>{
634
+ if (filter && !await filter({
635
+ filename,
636
+ contentType,
637
+ name
638
+ })) {
639
+ return undefined;
640
+ }
641
+ let size = 0;
642
+ const chunks = [];
643
+ for await (const chunk of data){
644
+ size += chunk.byteLength;
645
+ if (size > maxPartSize) {
646
+ throw new MaxPartSizeExceededError(name, maxPartSize);
647
+ }
648
+ chunks.push(chunk);
649
+ }
650
+ return new File(chunks, filename, {
651
+ type: contentType
652
+ });
653
+ };
654
+ }
655
+
656
+ const utfTextDecoder = new TextDecoder('utf-8');
635
657
  /**
636
658
  * Allows you to handle multipart forms (file uploads) for your app.
659
+ * Request body parts with a 'filename' property will be treated as files.
660
+ * The rest will be treated as text.
661
+ * @param request The incoming Node HTTP request
662
+ * @param uploadHandler A function that handles file uploads and returns a value to be used in the request body. If uploaded to disk, the returned value is a NodeOnDiskFile. If uploaded to memory, the returned value is a File.
663
+ * @param maxBodySize The maximum size of the request body in bytes. Defaults to Infinity.
637
664
  *
638
- * TODO: Update this comment
639
665
  * @see https://remix.run/utils/parse-multipart-form-data
640
- */ async function parseMultipartFormData(request, uploadHandler) {
666
+ */ async function parseMultipartFormData(request, uploadHandler, maxBodySize = Infinity) {
641
667
  const contentType = request.headers['content-type'] ?? '';
642
668
  const [type, boundary] = contentType.split(/\s*;\s*boundary=/);
643
669
  if (!boundary || type !== 'multipart/form-data') {
@@ -645,19 +671,52 @@ class NodeOnDiskFile {
645
671
  }
646
672
  const formData = new FormData();
647
673
  const parts = streamMultipart(node_stream.Readable.toWeb(request), boundary);
648
- for await (const part of parts){
649
- if (part.done) break;
650
- if (typeof part.filename === 'string') {
651
- // only pass basename as the multipart/form-data spec recommends
652
- // https://datatracker.ietf.org/doc/html/rfc7578#section-4.2
653
- part.filename = part.filename.split(/[/\\]/).pop();
654
- }
655
- const value = await uploadHandler(part);
656
- if (typeof value !== 'undefined' && value !== null) {
657
- formData.append(part.name, value);
674
+ let currentBodySize = 0;
675
+ const nodeOnDiskFiles = [];
676
+ try {
677
+ for await (const part of parts){
678
+ if (part.done) break;
679
+ if (typeof part.filename === 'string') {
680
+ // This is a file, so the uploadHandler function will be called
681
+ // only pass basename as the multipart/form-data spec recommends
682
+ // https://datatracker.ietf.org/doc/html/rfc7578#section-4.2
683
+ part.filename = part.filename.split(/[/\\]/).pop();
684
+ const value = await uploadHandler(part);
685
+ if (typeof value === 'undefined' || value === null) {
686
+ continue;
687
+ }
688
+ // add to cleanup array in case of error
689
+ if (value instanceof NodeOnDiskFile) {
690
+ nodeOnDiskFiles.push(value);
691
+ }
692
+ // if the combined size of the body exceeds the max size, throw an error
693
+ currentBodySize += value.size;
694
+ if (currentBodySize > maxBodySize) {
695
+ throw new MaxBodySizeExceededError(maxBodySize);
696
+ }
697
+ // add the file to the form data
698
+ formData.append(part.name, value);
699
+ } else {
700
+ // This is text, so we'll decode it and add it to the form data
701
+ let textualPart = '';
702
+ for await (const chunk of part.data){
703
+ // if the combined size of the body exceeds the max size, throw an error
704
+ currentBodySize += chunk.length;
705
+ if (currentBodySize > maxBodySize) {
706
+ throw new MaxBodySizeExceededError(maxBodySize);
707
+ }
708
+ textualPart += utfTextDecoder.decode(chunk);
709
+ }
710
+ // add the text to the form data
711
+ formData.append(part.name, textualPart);
712
+ }
658
713
  }
714
+ return formData;
715
+ } catch (e) {
716
+ // clean up any files that were uploaded to disk if an error occurs
717
+ await Promise.all(nodeOnDiskFiles.map((file)=>fs__namespace.unlink(file.getFilePath())));
718
+ throw e;
659
719
  }
660
- return formData;
661
720
  }
662
721
  function isMultipartFormDataRequest(req) {
663
722
  const contentTypeHeader = req.headers['content-type'];
@@ -691,7 +750,9 @@ const nodeHTTPFormDataContentTypeHandler = contentType.createNodeHTTPContentType
691
750
  }
692
751
  });
693
752
 
753
+ exports.MaxBodySizeExceededError = MaxBodySizeExceededError;
694
754
  exports.MaxPartSizeExceededError = MaxPartSizeExceededError;
755
+ exports.experimental_NodeOnDiskFile = NodeOnDiskFile;
695
756
  exports.experimental_composeUploadHandlers = composeUploadHandlers;
696
757
  exports.experimental_createFileUploadHandler = createFileUploadHandler;
697
758
  exports.experimental_createMemoryUploadHandler = createMemoryUploadHandler;
@@ -1,8 +1,9 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import { mkdir, rm, unlink, stat } from 'node:fs/promises';
1
3
  import { Transform, finished, Readable } from 'node:stream';
2
4
  import { c as createNodeHTTPContentTypeHandler } from '../../../../contentType-3194ed5f.mjs';
3
5
  import { randomBytes } from 'node:crypto';
4
6
  import { createWriteStream, statSync, createReadStream } from 'node:fs';
5
- import { mkdir, rm, unlink, stat } from 'node:fs/promises';
6
7
  import { tmpdir } from 'node:os';
7
8
  import { resolve, dirname, basename, extname } from 'node:path';
8
9
  import { promisify } from 'node:util';
@@ -393,54 +394,6 @@ async function* streamMultipart(body, boundary) {
393
394
  }
394
395
  }
395
396
 
396
- function composeUploadHandlers(...handlers) {
397
- return async (part)=>{
398
- for (const handler of handlers){
399
- const value = await handler(part);
400
- if (typeof value !== 'undefined' && value !== null) {
401
- return value;
402
- }
403
- }
404
- return undefined;
405
- };
406
- }
407
- class MaxPartSizeExceededError extends Error {
408
- constructor(field, maxBytes){
409
- super(`Field "${field}" exceeded upload size of ${maxBytes} bytes.`);
410
- this.field = field;
411
- this.maxBytes = maxBytes;
412
- }
413
- }
414
-
415
- function createMemoryUploadHandler({ filter , maxPartSize =3000000 } = {}) {
416
- return async ({ filename , contentType , name , data })=>{
417
- if (filter && !await filter({
418
- filename,
419
- contentType,
420
- name
421
- })) {
422
- return undefined;
423
- }
424
- let size = 0;
425
- const chunks = [];
426
- for await (const chunk of data){
427
- size += chunk.byteLength;
428
- if (size > maxPartSize) {
429
- throw new MaxPartSizeExceededError(name, maxPartSize);
430
- }
431
- chunks.push(chunk);
432
- }
433
- if (typeof filename === 'string') {
434
- return new File(chunks, filename, {
435
- type: contentType
436
- });
437
- }
438
- return await new Blob(chunks, {
439
- type: contentType
440
- }).text();
441
- };
442
- }
443
-
444
397
  class SliceStream extends Transform {
445
398
  _transform(chunk, _, done) {
446
399
  this.indexOffset += chunk.length;
@@ -482,6 +435,31 @@ function streamSlice(startIndex = 0, endIndex = Infinity) {
482
435
  return new SliceStream(startIndex, endIndex);
483
436
  }
484
437
 
438
+ function composeUploadHandlers(...handlers) {
439
+ return async (part)=>{
440
+ for (const handler of handlers){
441
+ const value = await handler(part);
442
+ if (typeof value !== 'undefined' && value !== null) {
443
+ return value;
444
+ }
445
+ }
446
+ return undefined;
447
+ };
448
+ }
449
+ class MaxPartSizeExceededError extends Error {
450
+ constructor(field, maxBytes){
451
+ super(`Field "${field}" exceeded upload size of ${maxBytes} bytes.`);
452
+ this.field = field;
453
+ this.maxBytes = maxBytes;
454
+ }
455
+ }
456
+ class MaxBodySizeExceededError extends Error {
457
+ constructor(maxBytes){
458
+ super(`Body exceeded upload size of ${maxBytes} bytes.`);
459
+ this.maxBytes = maxBytes;
460
+ }
461
+ }
462
+
485
463
  async function readableStreamToString(stream, encoding) {
486
464
  const reader = stream.getReader();
487
465
  const chunks = [];
@@ -628,12 +606,41 @@ class NodeOnDiskFile {
628
606
  }
629
607
  }
630
608
 
609
+ function createMemoryUploadHandler({ filter , maxPartSize =3000000 } = {}) {
610
+ return async ({ filename , contentType , name , data })=>{
611
+ if (filter && !await filter({
612
+ filename,
613
+ contentType,
614
+ name
615
+ })) {
616
+ return undefined;
617
+ }
618
+ let size = 0;
619
+ const chunks = [];
620
+ for await (const chunk of data){
621
+ size += chunk.byteLength;
622
+ if (size > maxPartSize) {
623
+ throw new MaxPartSizeExceededError(name, maxPartSize);
624
+ }
625
+ chunks.push(chunk);
626
+ }
627
+ return new File(chunks, filename, {
628
+ type: contentType
629
+ });
630
+ };
631
+ }
632
+
633
+ const utfTextDecoder = new TextDecoder('utf-8');
631
634
  /**
632
635
  * Allows you to handle multipart forms (file uploads) for your app.
636
+ * Request body parts with a 'filename' property will be treated as files.
637
+ * The rest will be treated as text.
638
+ * @param request The incoming Node HTTP request
639
+ * @param uploadHandler A function that handles file uploads and returns a value to be used in the request body. If uploaded to disk, the returned value is a NodeOnDiskFile. If uploaded to memory, the returned value is a File.
640
+ * @param maxBodySize The maximum size of the request body in bytes. Defaults to Infinity.
633
641
  *
634
- * TODO: Update this comment
635
642
  * @see https://remix.run/utils/parse-multipart-form-data
636
- */ async function parseMultipartFormData(request, uploadHandler) {
643
+ */ async function parseMultipartFormData(request, uploadHandler, maxBodySize = Infinity) {
637
644
  const contentType = request.headers['content-type'] ?? '';
638
645
  const [type, boundary] = contentType.split(/\s*;\s*boundary=/);
639
646
  if (!boundary || type !== 'multipart/form-data') {
@@ -641,19 +648,52 @@ class NodeOnDiskFile {
641
648
  }
642
649
  const formData = new FormData();
643
650
  const parts = streamMultipart(Readable.toWeb(request), boundary);
644
- for await (const part of parts){
645
- if (part.done) break;
646
- if (typeof part.filename === 'string') {
647
- // only pass basename as the multipart/form-data spec recommends
648
- // https://datatracker.ietf.org/doc/html/rfc7578#section-4.2
649
- part.filename = part.filename.split(/[/\\]/).pop();
650
- }
651
- const value = await uploadHandler(part);
652
- if (typeof value !== 'undefined' && value !== null) {
653
- formData.append(part.name, value);
651
+ let currentBodySize = 0;
652
+ const nodeOnDiskFiles = [];
653
+ try {
654
+ for await (const part of parts){
655
+ if (part.done) break;
656
+ if (typeof part.filename === 'string') {
657
+ // This is a file, so the uploadHandler function will be called
658
+ // only pass basename as the multipart/form-data spec recommends
659
+ // https://datatracker.ietf.org/doc/html/rfc7578#section-4.2
660
+ part.filename = part.filename.split(/[/\\]/).pop();
661
+ const value = await uploadHandler(part);
662
+ if (typeof value === 'undefined' || value === null) {
663
+ continue;
664
+ }
665
+ // add to cleanup array in case of error
666
+ if (value instanceof NodeOnDiskFile) {
667
+ nodeOnDiskFiles.push(value);
668
+ }
669
+ // if the combined size of the body exceeds the max size, throw an error
670
+ currentBodySize += value.size;
671
+ if (currentBodySize > maxBodySize) {
672
+ throw new MaxBodySizeExceededError(maxBodySize);
673
+ }
674
+ // add the file to the form data
675
+ formData.append(part.name, value);
676
+ } else {
677
+ // This is text, so we'll decode it and add it to the form data
678
+ let textualPart = '';
679
+ for await (const chunk of part.data){
680
+ // if the combined size of the body exceeds the max size, throw an error
681
+ currentBodySize += chunk.length;
682
+ if (currentBodySize > maxBodySize) {
683
+ throw new MaxBodySizeExceededError(maxBodySize);
684
+ }
685
+ textualPart += utfTextDecoder.decode(chunk);
686
+ }
687
+ // add the text to the form data
688
+ formData.append(part.name, textualPart);
689
+ }
654
690
  }
691
+ return formData;
692
+ } catch (e) {
693
+ // clean up any files that were uploaded to disk if an error occurs
694
+ await Promise.all(nodeOnDiskFiles.map((file)=>fs.unlink(file.getFilePath())));
695
+ throw e;
655
696
  }
656
- return formData;
657
697
  }
658
698
  function isMultipartFormDataRequest(req) {
659
699
  const contentTypeHeader = req.headers['content-type'];
@@ -687,4 +727,4 @@ const nodeHTTPFormDataContentTypeHandler = createNodeHTTPContentTypeHandler({
687
727
  }
688
728
  });
689
729
 
690
- export { MaxPartSizeExceededError, composeUploadHandlers as experimental_composeUploadHandlers, createFileUploadHandler as experimental_createFileUploadHandler, createMemoryUploadHandler as experimental_createMemoryUploadHandler, isMultipartFormDataRequest as experimental_isMultipartFormDataRequest, parseMultipartFormData as experimental_parseMultipartFormData, nodeHTTPFormDataContentTypeHandler };
730
+ export { MaxBodySizeExceededError, MaxPartSizeExceededError, NodeOnDiskFile as experimental_NodeOnDiskFile, composeUploadHandlers as experimental_composeUploadHandlers, createFileUploadHandler as experimental_createFileUploadHandler, createMemoryUploadHandler as experimental_createMemoryUploadHandler, isMultipartFormDataRequest as experimental_isMultipartFormDataRequest, parseMultipartFormData as experimental_parseMultipartFormData, nodeHTTPFormDataContentTypeHandler };
@@ -1 +1 @@
1
- {"version":3,"file":"memoryUploadHandler.d.ts","sourceRoot":"","sources":["../../../../../src/adapters/node-http/content-type/form-data/memoryUploadHandler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,OAAO,EAA4B,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE1E,MAAM,MAAM,6BAA6B,GAAG;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,MAAM,CAAC,CAAC,IAAI,EAAE,6BAA6B,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CAC1E,CAAC;AAEF,wBAAgB,yBAAyB,CAAC,EACxC,MAAM,EACN,WAAqB,GACtB,GAAE,0BAA+B,GAAG,aAAa,CAsBjD"}
1
+ {"version":3,"file":"memoryUploadHandler.d.ts","sourceRoot":"","sources":["../../../../../src/adapters/node-http/content-type/form-data/memoryUploadHandler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,OAAO,EAA4B,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE1E,MAAM,MAAM,6BAA6B,GAAG;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,MAAM,CAAC,CAAC,IAAI,EAAE,6BAA6B,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CAC1E,CAAC;AAEF,wBAAgB,yBAAyB,CAAC,EACxC,MAAM,EACN,WAAqB,GACtB,GAAE,0BAA+B,GAAG,aAAa,CAkBjD"}
@@ -8,13 +8,17 @@ export type UploadHandlerPart = {
8
8
  contentType: string;
9
9
  data: AsyncIterable<Uint8Array>;
10
10
  };
11
- export type UploadHandler = (part: UploadHandlerPart) => Promise<File | NodeOnDiskFile | string | null | undefined>;
11
+ export type UploadHandler = (part: Required<UploadHandlerPart>) => Promise<File | NodeOnDiskFile | null | undefined>;
12
12
  export declare function composeUploadHandlers(...handlers: UploadHandler[]): UploadHandler;
13
13
  export declare class MaxPartSizeExceededError extends Error {
14
14
  field: string;
15
15
  maxBytes: number;
16
16
  constructor(field: string, maxBytes: number);
17
17
  }
18
+ export declare class MaxBodySizeExceededError extends Error {
19
+ maxBytes: number;
20
+ constructor(maxBytes: number);
21
+ }
18
22
  export type MemoryUploadHandlerFilterArgs = {
19
23
  filename?: string;
20
24
  contentType: string;
@@ -1 +1 @@
1
- {"version":3,"file":"uploadHandler.d.ts","sourceRoot":"","sources":["../../../../../src/adapters/node-http/content-type/form-data/uploadHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,CAC1B,IAAI,EAAE,iBAAiB,KACpB,OAAO,CAAC,IAAI,GAAG,cAAc,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAEhE,wBAAgB,qBAAqB,CACnC,GAAG,QAAQ,EAAE,aAAa,EAAE,GAC3B,aAAa,CAWf;AAED,qBAAa,wBAAyB,SAAQ,KAAK;IAC9B,KAAK,EAAE,MAAM;IAAS,QAAQ,EAAE,MAAM;gBAAtC,KAAK,EAAE,MAAM,EAAS,QAAQ,EAAE,MAAM;CAG1D;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,MAAM,CAAC,CAAC,IAAI,EAAE,6BAA6B,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CAC1E,CAAC;AAEF;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,MAAM,EACN,WAAqB,GACtB,GAAE,0BAA+B,GAAG,aAAa,CAsBjD"}
1
+ {"version":3,"file":"uploadHandler.d.ts","sourceRoot":"","sources":["../../../../../src/adapters/node-http/content-type/form-data/uploadHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,CAC1B,IAAI,EAAE,QAAQ,CAAC,iBAAiB,CAAC,KAC9B,OAAO,CAAC,IAAI,GAAG,cAAc,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAEvD,wBAAgB,qBAAqB,CACnC,GAAG,QAAQ,EAAE,aAAa,EAAE,GAC3B,aAAa,CAWf;AAED,qBAAa,wBAAyB,SAAQ,KAAK;IAC9B,KAAK,EAAE,MAAM;IAAS,QAAQ,EAAE,MAAM;gBAAtC,KAAK,EAAE,MAAM,EAAS,QAAQ,EAAE,MAAM;CAG1D;AAED,qBAAa,wBAAyB,SAAQ,KAAK;IAC9B,QAAQ,EAAE,MAAM;gBAAhB,QAAQ,EAAE,MAAM;CAGpC;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,MAAM,CAAC,CAAC,IAAI,EAAE,6BAA6B,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CAC1E,CAAC;AAEF;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,MAAM,EACN,WAAqB,GACtB,GAAE,0BAA+B,GAAG,aAAa,CAkBjD"}
@@ -4,10 +4,7 @@ import { IncomingMessage } from 'http';
4
4
  import ws from 'ws';
5
5
  import { AnyRouter } from '../core';
6
6
  import { BaseHandlerOptions } from '../internals/types';
7
- import { TRPCClientOutgoingMessage } from '../rpc';
8
- import { CombinedDataTransformer } from '../transformer';
9
7
  import { NodeHTTPCreateContextFnOptions, NodeHTTPCreateContextOption } from './node-http';
10
- export declare function parseMessage(obj: unknown, transformer: CombinedDataTransformer): TRPCClientOutgoingMessage;
11
8
  /**
12
9
  * Web socket server handler
13
10
  */
@@ -1 +1 @@
1
- {"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../../src/adapters/ws.ts"],"names":[],"mappings":";;AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EACL,SAAS,EAIV,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,EAEL,yBAAyB,EAG1B,MAAM,QAAQ,CAAC;AAGhB,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,EAC5B,MAAM,aAAa,CAAC;AAyCrB,wBAAgB,YAAY,CAC1B,GAAG,EAAE,OAAO,EACZ,WAAW,EAAE,uBAAuB,GACnC,yBAAyB,CA2B3B;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,CAAC,OAAO,SAAS,SAAS,IAAI,kBAAkB,CAC3E,OAAO,EACP,eAAe,CAChB,GACC,2BAA2B,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,CAAC,GAAG;IAC1D,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAC1B,CAAC;AAEJ,MAAM,MAAM,yBAAyB,GAAG,8BAA8B,CACpE,eAAe,EACf,EAAE,CACH,CAAC;AAEF,wBAAgB,eAAe,CAAC,OAAO,SAAS,SAAS,EACvD,IAAI,EAAE,iBAAiB,CAAC,OAAO,CAAC;;EAwQjC"}
1
+ {"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../../src/adapters/ws.ts"],"names":[],"mappings":";;AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,SAAS,EAAqC,MAAM,SAAS,CAAC;AAEvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAWxD,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,EAC5B,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,MAAM,MAAM,iBAAiB,CAAC,OAAO,SAAS,SAAS,IAAI,kBAAkB,CAC3E,OAAO,EACP,eAAe,CAChB,GACC,2BAA2B,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,CAAC,GAAG;IAC1D,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAC1B,CAAC;AAEJ,MAAM,MAAM,yBAAyB,GAAG,8BAA8B,CACpE,eAAe,EACf,EAAE,CACH,CAAC;AAEF,wBAAgB,eAAe,CAAC,OAAO,SAAS,SAAS,EACvD,IAAI,EAAE,iBAAiB,CAAC,OAAO,CAAC;;EAwQjC"}