@syntay/fastay 0.1.7 → 0.1.9
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/app.d.ts +39 -0
- package/dist/app.js +31 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/utils/cookies.d.ts +1 -0
- package/dist/utils/cookies.js +1 -0
- package/dist/utils/formDataMiddleware.d.ts +17 -0
- package/dist/utils/formDataMiddleware.js +122 -0
- package/package.json +5 -1
- package/src/app.ts +96 -3
- package/src/index.ts +1 -0
- package/src/types/index.ts +1 -0
- package/src/utils/cookies.ts +2 -0
- package/src/utils/formDataMiddleware.ts +155 -0
package/dist/app.d.ts
CHANGED
|
@@ -75,6 +75,45 @@ export type CreateAppOptions = {
|
|
|
75
75
|
* Default: "/api"
|
|
76
76
|
*/
|
|
77
77
|
baseRoute?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Configuration to enable CORS (Cross-Origin Resource Sharing) in Fastay.
|
|
80
|
+
*/
|
|
81
|
+
enableCors?: {
|
|
82
|
+
/**
|
|
83
|
+
* If true, permite requisições de qualquer origem.
|
|
84
|
+
* Default: false
|
|
85
|
+
*/
|
|
86
|
+
allowAnyOrigin?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Lista de origens específicas permitidas para envio de cookies.
|
|
89
|
+
* Exemplo: ["http://localhost:3000", "https://meusite.com"]
|
|
90
|
+
*/
|
|
91
|
+
cookieOrigins?: string[];
|
|
92
|
+
/**
|
|
93
|
+
* Se true, habilita envio de cookies cross-origin.
|
|
94
|
+
* Default: false
|
|
95
|
+
*/
|
|
96
|
+
credentials?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Lista de métodos HTTP permitidos, separados por vírgula.
|
|
99
|
+
* Default: "GET,POST,PUT,PATCH,DELETE,OPTIONS"
|
|
100
|
+
*/
|
|
101
|
+
methods?: string;
|
|
102
|
+
/**
|
|
103
|
+
* Lista de cabeçalhos permitidos na requisição.
|
|
104
|
+
* Default: "Content-Type, Authorization"
|
|
105
|
+
*/
|
|
106
|
+
headers?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Cabeçalhos expostos ao cliente.
|
|
109
|
+
* Exemplo: ["X-Custom-Header"]
|
|
110
|
+
*/
|
|
111
|
+
exposedHeaders?: string;
|
|
112
|
+
/**
|
|
113
|
+
* Tempo máximo de cache para requisições prévias (preflight), em segundos.
|
|
114
|
+
*/
|
|
115
|
+
maxAge?: number;
|
|
116
|
+
};
|
|
78
117
|
/**
|
|
79
118
|
* Port on which `.listen()` will run the server.
|
|
80
119
|
* Default: 3000
|
package/dist/app.js
CHANGED
|
@@ -5,6 +5,7 @@ import { loadFastayMiddlewares, createMiddleware, } from './middleware.js';
|
|
|
5
5
|
import { logger } from './logger.js';
|
|
6
6
|
import { printBanner } from './banner.js';
|
|
7
7
|
import { RequestCookies } from './utils/cookies.js';
|
|
8
|
+
import { formDataMiddleware } from './utils/formDataMiddleware.js';
|
|
8
9
|
/**
|
|
9
10
|
* Bootstraps and configures a Fastay application.
|
|
10
11
|
*
|
|
@@ -81,6 +82,8 @@ export async function createApp(opts) {
|
|
|
81
82
|
app.use(mw);
|
|
82
83
|
}
|
|
83
84
|
}
|
|
85
|
+
// FormData middleware
|
|
86
|
+
app.use(formDataMiddleware());
|
|
84
87
|
// Fastay middlewares
|
|
85
88
|
if (opts?.middlewares) {
|
|
86
89
|
logger.group('Fastay Middlewares');
|
|
@@ -95,6 +98,34 @@ export async function createApp(opts) {
|
|
|
95
98
|
app.use((req, res, next) => {
|
|
96
99
|
res.setHeader('X-Powered-By', 'Syntay Engine');
|
|
97
100
|
req.cookies = new RequestCookies(req.headers.cookie);
|
|
101
|
+
const corsOpts = opts?.enableCors || {};
|
|
102
|
+
// Determina a origem
|
|
103
|
+
let origin = '*';
|
|
104
|
+
if (corsOpts.credentials && corsOpts.cookieOrigins?.length) {
|
|
105
|
+
// Se a origem estiver na lista de cookieOrigins, permite cookies
|
|
106
|
+
if (req.headers.origin &&
|
|
107
|
+
corsOpts.cookieOrigins.includes(req.headers.origin)) {
|
|
108
|
+
origin = req.headers.origin;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
origin = ''; // bloqueia cookies para outras origens
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else if (!corsOpts.credentials && corsOpts.allowAnyOrigin) {
|
|
115
|
+
origin = '*';
|
|
116
|
+
}
|
|
117
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
118
|
+
res.setHeader('Access-Control-Allow-Credentials', corsOpts.credentials ? 'true' : 'false');
|
|
119
|
+
res.setHeader('Access-Control-Allow-Methods', corsOpts.methods || 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
|
|
120
|
+
res.setHeader('Access-Control-Allow-Headers', corsOpts.headers || 'Content-Type, Authorization');
|
|
121
|
+
if (corsOpts.exposedHeaders) {
|
|
122
|
+
res.setHeader('Access-Control-Expose-Headers', corsOpts.exposedHeaders);
|
|
123
|
+
}
|
|
124
|
+
if (corsOpts.maxAge) {
|
|
125
|
+
res.setHeader('Access-Control-Max-Age', corsOpts.maxAge.toString());
|
|
126
|
+
}
|
|
127
|
+
if (req.method === 'OPTIONS')
|
|
128
|
+
return res.sendStatus(204);
|
|
98
129
|
next();
|
|
99
130
|
});
|
|
100
131
|
// load routes
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/types/index.d.ts
CHANGED
package/dist/utils/cookies.d.ts
CHANGED
package/dist/utils/cookies.js
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type FileInfo = {
|
|
2
|
+
filename: string;
|
|
3
|
+
encoding: string;
|
|
4
|
+
mimeType: string;
|
|
5
|
+
size: number;
|
|
6
|
+
buffer: Buffer;
|
|
7
|
+
};
|
|
8
|
+
export declare class File {
|
|
9
|
+
name: string;
|
|
10
|
+
type: string;
|
|
11
|
+
size: number;
|
|
12
|
+
buffer: Buffer;
|
|
13
|
+
constructor(file: FileInfo);
|
|
14
|
+
arrayBuffer(): ArrayBuffer | SharedArrayBuffer;
|
|
15
|
+
}
|
|
16
|
+
export declare function formDataMiddleware(): (req: any, res: any, next: any) => any;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import Busboy from 'busboy';
|
|
2
|
+
// Classe File compatível com a do browser
|
|
3
|
+
export class File {
|
|
4
|
+
constructor(file) {
|
|
5
|
+
this.name = file.filename;
|
|
6
|
+
this.type = file.mimeType;
|
|
7
|
+
this.size = file.size;
|
|
8
|
+
this.buffer = file.buffer;
|
|
9
|
+
}
|
|
10
|
+
arrayBuffer() {
|
|
11
|
+
return this.buffer.buffer.slice(this.buffer.byteOffset, this.buffer.byteOffset + this.buffer.byteLength);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function formDataMiddleware() {
|
|
15
|
+
return function (req, res, next) {
|
|
16
|
+
if (typeof req.formData === 'function')
|
|
17
|
+
return next();
|
|
18
|
+
req.formData = () => new Promise((resolve, reject) => {
|
|
19
|
+
const bb = Busboy({ headers: req.headers });
|
|
20
|
+
const fields = {};
|
|
21
|
+
const files = {};
|
|
22
|
+
bb.on('field', (name, value) => {
|
|
23
|
+
if (name.endsWith('[]')) {
|
|
24
|
+
const key = name.slice(0, -2);
|
|
25
|
+
if (!fields[key])
|
|
26
|
+
fields[key] = [];
|
|
27
|
+
fields[key].push(value);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
fields[name] = value;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
bb.on('file', (name, file, info) => {
|
|
34
|
+
const chunks = [];
|
|
35
|
+
file.on('data', (chunk) => chunks.push(chunk));
|
|
36
|
+
file.on('end', () => {
|
|
37
|
+
const buffer = Buffer.concat(chunks);
|
|
38
|
+
const fileObj = new File({
|
|
39
|
+
filename: info.filename,
|
|
40
|
+
encoding: info.encoding,
|
|
41
|
+
mimeType: info.mimeType,
|
|
42
|
+
size: buffer.length,
|
|
43
|
+
buffer,
|
|
44
|
+
});
|
|
45
|
+
if (!files[name])
|
|
46
|
+
files[name] = [];
|
|
47
|
+
files[name].push(fileObj);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
bb.on('finish', () => {
|
|
51
|
+
resolve(createFormDataLike(fields, files));
|
|
52
|
+
});
|
|
53
|
+
bb.on('error', reject);
|
|
54
|
+
req.pipe(bb);
|
|
55
|
+
});
|
|
56
|
+
next();
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function createFormDataLike(fields, files) {
|
|
60
|
+
return {
|
|
61
|
+
get(key) {
|
|
62
|
+
if (files[key])
|
|
63
|
+
return files[key][0];
|
|
64
|
+
return fields[key] ?? null;
|
|
65
|
+
},
|
|
66
|
+
getAll(key) {
|
|
67
|
+
if (files[key])
|
|
68
|
+
return files[key];
|
|
69
|
+
const val = fields[key];
|
|
70
|
+
return Array.isArray(val) ? val : val ? [val] : [];
|
|
71
|
+
},
|
|
72
|
+
has(key) {
|
|
73
|
+
return !!fields[key] || !!files[key];
|
|
74
|
+
},
|
|
75
|
+
append(key, value) {
|
|
76
|
+
if (value instanceof File) {
|
|
77
|
+
if (!files[key])
|
|
78
|
+
files[key] = [];
|
|
79
|
+
files[key].push(value);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (!fields[key]) {
|
|
83
|
+
fields[key] = value;
|
|
84
|
+
}
|
|
85
|
+
else if (Array.isArray(fields[key])) {
|
|
86
|
+
fields[key].push(value);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
fields[key] = [fields[key], value];
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
set(key, value) {
|
|
93
|
+
if (value instanceof File) {
|
|
94
|
+
files[key] = [value];
|
|
95
|
+
delete fields[key];
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
fields[key] = value;
|
|
99
|
+
delete files[key];
|
|
100
|
+
},
|
|
101
|
+
delete(key) {
|
|
102
|
+
delete fields[key];
|
|
103
|
+
delete files[key];
|
|
104
|
+
},
|
|
105
|
+
*entries() {
|
|
106
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
107
|
+
if (Array.isArray(v)) {
|
|
108
|
+
for (const item of v)
|
|
109
|
+
yield [k, item];
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
yield [k, v];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
for (const [k, arr] of Object.entries(files)) {
|
|
116
|
+
for (const file of arr)
|
|
117
|
+
yield [k, file];
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
raw: { fields, files },
|
|
121
|
+
};
|
|
122
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syntay/fastay",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Framework backend moderno baseado em Express.js, para criar APIs rapidamente",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -28,11 +28,15 @@
|
|
|
28
28
|
"access": "public"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
+
"busboy": "^1.6.0",
|
|
32
|
+
"chokidar": "^4.0.3",
|
|
31
33
|
"express": "^5.1.0",
|
|
34
|
+
"import-fresh": "^3.3.1",
|
|
32
35
|
"pino": "^10.1.0",
|
|
33
36
|
"pino-pretty": "^13.1.2"
|
|
34
37
|
},
|
|
35
38
|
"devDependencies": {
|
|
39
|
+
"@types/busboy": "^1.5.4",
|
|
36
40
|
"@types/express": "^5.0.5",
|
|
37
41
|
"@types/node": "^20.19.25",
|
|
38
42
|
"ts-node": "^10.9.2",
|
package/src/app.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import express from 'express';
|
|
1
|
+
import express, { Request, Response, NextFunction } from 'express';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { loadApiRoutes } from './router.js';
|
|
4
4
|
import {
|
|
@@ -9,8 +9,9 @@ import {
|
|
|
9
9
|
import { logger } from './logger.js';
|
|
10
10
|
import { printBanner } from './banner.js';
|
|
11
11
|
import type { ServeStaticOptions } from 'serve-static';
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
import { RequestCookies } from './utils/cookies.js';
|
|
14
|
+
import { formDataMiddleware } from './utils/formDataMiddleware.js';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Express configuration options applied automatically by Fastay
|
|
@@ -96,6 +97,52 @@ export type CreateAppOptions = {
|
|
|
96
97
|
*/
|
|
97
98
|
baseRoute?: string;
|
|
98
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Configuration to enable CORS (Cross-Origin Resource Sharing) in Fastay.
|
|
102
|
+
*/
|
|
103
|
+
enableCors?: {
|
|
104
|
+
/**
|
|
105
|
+
* If true, permite requisições de qualquer origem.
|
|
106
|
+
* Default: false
|
|
107
|
+
*/
|
|
108
|
+
allowAnyOrigin?: boolean;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Lista de origens específicas permitidas para envio de cookies.
|
|
112
|
+
* Exemplo: ["http://localhost:3000", "https://meusite.com"]
|
|
113
|
+
*/
|
|
114
|
+
cookieOrigins?: string[];
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Se true, habilita envio de cookies cross-origin.
|
|
118
|
+
* Default: false
|
|
119
|
+
*/
|
|
120
|
+
credentials?: boolean;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Lista de métodos HTTP permitidos, separados por vírgula.
|
|
124
|
+
* Default: "GET,POST,PUT,PATCH,DELETE,OPTIONS"
|
|
125
|
+
*/
|
|
126
|
+
methods?: string;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Lista de cabeçalhos permitidos na requisição.
|
|
130
|
+
* Default: "Content-Type, Authorization"
|
|
131
|
+
*/
|
|
132
|
+
headers?: string;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Cabeçalhos expostos ao cliente.
|
|
136
|
+
* Exemplo: ["X-Custom-Header"]
|
|
137
|
+
*/
|
|
138
|
+
exposedHeaders?: string;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Tempo máximo de cache para requisições prévias (preflight), em segundos.
|
|
142
|
+
*/
|
|
143
|
+
maxAge?: number;
|
|
144
|
+
};
|
|
145
|
+
|
|
99
146
|
/**
|
|
100
147
|
* Port on which `.listen()` will run the server.
|
|
101
148
|
* Default: 3000
|
|
@@ -202,6 +249,9 @@ export async function createApp(opts?: CreateAppOptions) {
|
|
|
202
249
|
}
|
|
203
250
|
}
|
|
204
251
|
|
|
252
|
+
// FormData middleware
|
|
253
|
+
app.use(formDataMiddleware());
|
|
254
|
+
|
|
205
255
|
// Fastay middlewares
|
|
206
256
|
if (opts?.middlewares) {
|
|
207
257
|
logger.group('Fastay Middlewares');
|
|
@@ -215,9 +265,52 @@ export async function createApp(opts?: CreateAppOptions) {
|
|
|
215
265
|
|
|
216
266
|
// health check
|
|
217
267
|
app.get('/_health', (_, res) => res.json({ ok: true }));
|
|
218
|
-
app.use((req: Request, res: Response, next:
|
|
268
|
+
app.use((req: Request, res: Response, next: NextFunction) => {
|
|
219
269
|
res.setHeader('X-Powered-By', 'Syntay Engine');
|
|
220
270
|
(req as any).cookies = new RequestCookies(req.headers.cookie);
|
|
271
|
+
|
|
272
|
+
const corsOpts = opts?.enableCors || {};
|
|
273
|
+
|
|
274
|
+
// Determina a origem
|
|
275
|
+
let origin = '*';
|
|
276
|
+
|
|
277
|
+
if (corsOpts.credentials && corsOpts.cookieOrigins?.length) {
|
|
278
|
+
// Se a origem estiver na lista de cookieOrigins, permite cookies
|
|
279
|
+
if (
|
|
280
|
+
req.headers.origin &&
|
|
281
|
+
corsOpts.cookieOrigins.includes(req.headers.origin)
|
|
282
|
+
) {
|
|
283
|
+
origin = req.headers.origin;
|
|
284
|
+
} else {
|
|
285
|
+
origin = ''; // bloqueia cookies para outras origens
|
|
286
|
+
}
|
|
287
|
+
} else if (!corsOpts.credentials && corsOpts.allowAnyOrigin) {
|
|
288
|
+
origin = '*';
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
292
|
+
res.setHeader(
|
|
293
|
+
'Access-Control-Allow-Credentials',
|
|
294
|
+
corsOpts.credentials ? 'true' : 'false'
|
|
295
|
+
);
|
|
296
|
+
res.setHeader(
|
|
297
|
+
'Access-Control-Allow-Methods',
|
|
298
|
+
corsOpts.methods || 'GET,POST,PUT,PATCH,DELETE,OPTIONS'
|
|
299
|
+
);
|
|
300
|
+
res.setHeader(
|
|
301
|
+
'Access-Control-Allow-Headers',
|
|
302
|
+
corsOpts.headers || 'Content-Type, Authorization'
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
if (corsOpts.exposedHeaders) {
|
|
306
|
+
res.setHeader('Access-Control-Expose-Headers', corsOpts.exposedHeaders);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (corsOpts.maxAge) {
|
|
310
|
+
res.setHeader('Access-Control-Max-Age', corsOpts.maxAge.toString());
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (req.method === 'OPTIONS') return res.sendStatus(204);
|
|
221
314
|
next();
|
|
222
315
|
});
|
|
223
316
|
|
package/src/index.ts
CHANGED
package/src/types/index.ts
CHANGED
package/src/utils/cookies.ts
CHANGED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import Busboy from 'busboy';
|
|
2
|
+
|
|
3
|
+
type FieldMap = Record<string, string | string[]>;
|
|
4
|
+
type FileInfo = {
|
|
5
|
+
filename: string;
|
|
6
|
+
encoding: string;
|
|
7
|
+
mimeType: string;
|
|
8
|
+
size: number;
|
|
9
|
+
buffer: Buffer;
|
|
10
|
+
};
|
|
11
|
+
type FileMap = Record<string, File[]>;
|
|
12
|
+
|
|
13
|
+
// Classe File compatível com a do browser
|
|
14
|
+
export class File {
|
|
15
|
+
name: string;
|
|
16
|
+
type: string;
|
|
17
|
+
size: number;
|
|
18
|
+
buffer: Buffer;
|
|
19
|
+
|
|
20
|
+
constructor(file: FileInfo) {
|
|
21
|
+
this.name = file.filename;
|
|
22
|
+
this.type = file.mimeType;
|
|
23
|
+
this.size = file.size;
|
|
24
|
+
this.buffer = file.buffer;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
arrayBuffer() {
|
|
28
|
+
return this.buffer.buffer.slice(
|
|
29
|
+
this.buffer.byteOffset,
|
|
30
|
+
this.buffer.byteOffset + this.buffer.byteLength
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function formDataMiddleware() {
|
|
36
|
+
return function (req: any, res: any, next: any) {
|
|
37
|
+
if (typeof req.formData === 'function') return next();
|
|
38
|
+
|
|
39
|
+
req.formData = () =>
|
|
40
|
+
new Promise((resolve, reject) => {
|
|
41
|
+
const bb = Busboy({ headers: req.headers });
|
|
42
|
+
|
|
43
|
+
const fields: FieldMap = {};
|
|
44
|
+
const files: FileMap = {};
|
|
45
|
+
|
|
46
|
+
bb.on('field', (name, value) => {
|
|
47
|
+
if (name.endsWith('[]')) {
|
|
48
|
+
const key = name.slice(0, -2);
|
|
49
|
+
if (!fields[key]) fields[key] = [];
|
|
50
|
+
(fields[key] as string[]).push(value);
|
|
51
|
+
} else {
|
|
52
|
+
fields[name] = value;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
bb.on('file', (name, file, info) => {
|
|
57
|
+
const chunks: Buffer[] = [];
|
|
58
|
+
|
|
59
|
+
file.on('data', (chunk) => chunks.push(chunk));
|
|
60
|
+
|
|
61
|
+
file.on('end', () => {
|
|
62
|
+
const buffer = Buffer.concat(chunks);
|
|
63
|
+
|
|
64
|
+
const fileObj = new File({
|
|
65
|
+
filename: info.filename,
|
|
66
|
+
encoding: info.encoding,
|
|
67
|
+
mimeType: info.mimeType,
|
|
68
|
+
size: buffer.length,
|
|
69
|
+
buffer,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (!files[name]) files[name] = [];
|
|
73
|
+
files[name].push(fileObj);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
bb.on('finish', () => {
|
|
78
|
+
resolve(createFormDataLike(fields, files));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
bb.on('error', reject);
|
|
82
|
+
|
|
83
|
+
req.pipe(bb);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
next();
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function createFormDataLike(fields: FieldMap, files: FileMap) {
|
|
91
|
+
return {
|
|
92
|
+
get(key: string) {
|
|
93
|
+
if (files[key]) return files[key][0];
|
|
94
|
+
return fields[key] ?? null;
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
getAll(key: string) {
|
|
98
|
+
if (files[key]) return files[key];
|
|
99
|
+
const val = fields[key];
|
|
100
|
+
return Array.isArray(val) ? val : val ? [val] : [];
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
has(key: string) {
|
|
104
|
+
return !!fields[key] || !!files[key];
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
append(key: string, value: any) {
|
|
108
|
+
if (value instanceof File) {
|
|
109
|
+
if (!files[key]) files[key] = [];
|
|
110
|
+
files[key].push(value);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!fields[key]) {
|
|
115
|
+
fields[key] = value;
|
|
116
|
+
} else if (Array.isArray(fields[key])) {
|
|
117
|
+
(fields[key] as string[]).push(value);
|
|
118
|
+
} else {
|
|
119
|
+
fields[key] = [fields[key] as string, value];
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
set(key: string, value: any) {
|
|
124
|
+
if (value instanceof File) {
|
|
125
|
+
files[key] = [value];
|
|
126
|
+
delete fields[key];
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fields[key] = value;
|
|
131
|
+
delete files[key];
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
delete(key: string) {
|
|
135
|
+
delete fields[key];
|
|
136
|
+
delete files[key];
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
*entries() {
|
|
140
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
141
|
+
if (Array.isArray(v)) {
|
|
142
|
+
for (const item of v) yield [k, item];
|
|
143
|
+
} else {
|
|
144
|
+
yield [k, v];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
for (const [k, arr] of Object.entries(files)) {
|
|
149
|
+
for (const file of arr) yield [k, file];
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
raw: { fields, files },
|
|
154
|
+
};
|
|
155
|
+
}
|