@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.
- package/dist/adapters/fastify/index.js +1 -0
- package/dist/adapters/fastify/index.mjs +1 -0
- package/dist/adapters/node-http/content-type/form-data/index.d.ts +8 -4
- package/dist/adapters/node-http/content-type/form-data/index.d.ts.map +1 -1
- package/dist/adapters/node-http/content-type/form-data/index.js +127 -66
- package/dist/adapters/node-http/content-type/form-data/index.mjs +103 -63
- package/dist/adapters/node-http/content-type/form-data/memoryUploadHandler.d.ts.map +1 -1
- package/dist/adapters/node-http/content-type/form-data/uploadHandler.d.ts +5 -1
- package/dist/adapters/node-http/content-type/form-data/uploadHandler.d.ts.map +1 -1
- package/dist/adapters/ws.d.ts +0 -3
- package/dist/adapters/ws.d.ts.map +1 -1
- package/dist/adapters/ws.js +3 -55
- package/dist/adapters/ws.mjs +4 -55
- package/dist/core/internals/procedureBuilder.d.ts +1 -1
- package/dist/core/internals/procedureBuilder.d.ts.map +1 -1
- package/dist/core/middleware.d.ts +3 -3
- package/dist/core/middleware.d.ts.map +1 -1
- package/dist/parseTRPCMessage-1377f305.js +56 -0
- package/dist/parseTRPCMessage-95955211.js +63 -0
- package/dist/parseTRPCMessage-a0f17853.mjs +54 -0
- package/dist/rpc/index.d.ts +1 -0
- package/dist/rpc/index.d.ts.map +1 -1
- package/dist/rpc/index.js +2 -0
- package/dist/rpc/index.mjs +1 -0
- package/dist/rpc/parseTRPCMessage.d.ts +5 -0
- package/dist/rpc/parseTRPCMessage.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/adapters/node-http/content-type/form-data/index.ts +71 -16
- package/src/adapters/node-http/content-type/form-data/memoryUploadHandler.ts +1 -5
- package/src/adapters/node-http/content-type/form-data/uploadHandler.ts +9 -7
- package/src/adapters/ws.ts +3 -79
- package/src/core/internals/procedureBuilder.ts +3 -1
- package/src/core/middleware.ts +7 -2
- package/src/rpc/index.ts +1 -0
- 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;
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
part.filename
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
part.filename
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
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,
|
|
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 |
|
|
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,
|
|
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"}
|
package/dist/adapters/ws.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|