@syntay/fastay 0.1.8 → 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.js +3 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/utils/formDataMiddleware.d.ts +17 -0
- package/dist/utils/formDataMiddleware.js +122 -0
- package/package.json +3 -1
- package/src/app.ts +7 -3
- package/src/types/index.ts +1 -0
- package/src/utils/formDataMiddleware.ts +155 -0
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');
|
package/dist/types/index.d.ts
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,6 +28,7 @@
|
|
|
28
28
|
"access": "public"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
+
"busboy": "^1.6.0",
|
|
31
32
|
"chokidar": "^4.0.3",
|
|
32
33
|
"express": "^5.1.0",
|
|
33
34
|
"import-fresh": "^3.3.1",
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
"pino-pretty": "^13.1.2"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
39
|
+
"@types/busboy": "^1.5.4",
|
|
38
40
|
"@types/express": "^5.0.5",
|
|
39
41
|
"@types/node": "^20.19.25",
|
|
40
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
|
|
@@ -248,6 +249,9 @@ export async function createApp(opts?: CreateAppOptions) {
|
|
|
248
249
|
}
|
|
249
250
|
}
|
|
250
251
|
|
|
252
|
+
// FormData middleware
|
|
253
|
+
app.use(formDataMiddleware());
|
|
254
|
+
|
|
251
255
|
// Fastay middlewares
|
|
252
256
|
if (opts?.middlewares) {
|
|
253
257
|
logger.group('Fastay Middlewares');
|
|
@@ -261,7 +265,7 @@ export async function createApp(opts?: CreateAppOptions) {
|
|
|
261
265
|
|
|
262
266
|
// health check
|
|
263
267
|
app.get('/_health', (_, res) => res.json({ ok: true }));
|
|
264
|
-
app.use((req: Request, res: Response, next:
|
|
268
|
+
app.use((req: Request, res: Response, next: NextFunction) => {
|
|
265
269
|
res.setHeader('X-Powered-By', 'Syntay Engine');
|
|
266
270
|
(req as any).cookies = new RequestCookies(req.headers.cookie);
|
|
267
271
|
|
package/src/types/index.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
|
+
}
|