@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 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');
@@ -26,6 +26,7 @@ export interface Request extends ExpressRequest {
26
26
  * Represents the cookies sent in a request.
27
27
  */
28
28
  cookies: RequestCookies;
29
+ formData: () => Promise<FormData>;
29
30
  }
30
31
  export type Response = ExpressResponse;
31
32
  export type Next = NextFunction;
@@ -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.8",
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
- import { Next, Request, Response } from './types/index.js';
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: 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
 
@@ -35,6 +35,7 @@ export interface Request extends ExpressRequest {
35
35
  * Represents the cookies sent in a request.
36
36
  */
37
37
  cookies: RequestCookies;
38
+ formData: () => Promise<FormData>;
38
39
  // params:
39
40
  // query:
40
41
  }
@@ -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
+ }