vibe-gx 1.0.3 → 1.0.5
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/package.json +1 -1
- package/utils/core/parser.js +39 -8
- package/vibe.d.ts +80 -8
package/package.json
CHANGED
package/utils/core/parser.js
CHANGED
|
@@ -54,6 +54,19 @@ function parseMultipart(req, res, media, options, resolve, reject) {
|
|
|
54
54
|
let bb;
|
|
55
55
|
let fileError = null;
|
|
56
56
|
const streaming = media.streaming === true;
|
|
57
|
+
let pendingWrites = 0;
|
|
58
|
+
let busboyFinished = false;
|
|
59
|
+
|
|
60
|
+
// Helper to check if we're done
|
|
61
|
+
const checkComplete = () => {
|
|
62
|
+
if (busboyFinished && pendingWrites === 0) {
|
|
63
|
+
if (fileError) {
|
|
64
|
+
reject(fileError);
|
|
65
|
+
} else {
|
|
66
|
+
resolve();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
57
70
|
|
|
58
71
|
try {
|
|
59
72
|
bb = busboy({
|
|
@@ -75,9 +88,15 @@ function parseMultipart(req, res, media, options, resolve, reject) {
|
|
|
75
88
|
const { filename, mimeType } = info;
|
|
76
89
|
if (!filename) return file.resume();
|
|
77
90
|
|
|
78
|
-
// File type validation
|
|
91
|
+
// File type validation - support wildcards like "image/*"
|
|
79
92
|
if (media.allowedTypes && Array.isArray(media.allowedTypes)) {
|
|
80
|
-
|
|
93
|
+
const isAllowed = media.allowedTypes.some((allowed) => {
|
|
94
|
+
if (allowed.endsWith("/*")) {
|
|
95
|
+
return mimeType.startsWith(allowed.slice(0, -1));
|
|
96
|
+
}
|
|
97
|
+
return allowed === mimeType;
|
|
98
|
+
});
|
|
99
|
+
if (!isAllowed) {
|
|
81
100
|
fileError = new Error(
|
|
82
101
|
`File type '${mimeType}' not allowed. Allowed: ${media.allowedTypes.join(", ")}`,
|
|
83
102
|
);
|
|
@@ -92,6 +111,8 @@ function parseMultipart(req, res, media, options, resolve, reject) {
|
|
|
92
111
|
}
|
|
93
112
|
|
|
94
113
|
// BUFFERING MODE: Write to disk
|
|
114
|
+
pendingWrites++;
|
|
115
|
+
|
|
95
116
|
const parent = media.public ? options.publicFolder || "" : "";
|
|
96
117
|
const dest = path.resolve(
|
|
97
118
|
path.join(parent, media.dest || (media.public ? "uploads" : "private")),
|
|
@@ -103,6 +124,8 @@ function parseMultipart(req, res, media, options, resolve, reject) {
|
|
|
103
124
|
!dest.startsWith(path.resolve(options.publicFolder || ""))
|
|
104
125
|
) {
|
|
105
126
|
console.warn("Attempted upload outside public folder, skipping");
|
|
127
|
+
pendingWrites--;
|
|
128
|
+
checkComplete();
|
|
106
129
|
return file.resume();
|
|
107
130
|
}
|
|
108
131
|
|
|
@@ -110,6 +133,8 @@ function parseMultipart(req, res, media, options, resolve, reject) {
|
|
|
110
133
|
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
111
134
|
} catch (err) {
|
|
112
135
|
console.error("Failed to create upload folder:", err);
|
|
136
|
+
pendingWrites--;
|
|
137
|
+
checkComplete();
|
|
113
138
|
return file.resume();
|
|
114
139
|
}
|
|
115
140
|
|
|
@@ -137,17 +162,24 @@ function parseMultipart(req, res, media, options, resolve, reject) {
|
|
|
137
162
|
file.unpipe(writeStream);
|
|
138
163
|
writeStream.end();
|
|
139
164
|
// Clean up partial file
|
|
140
|
-
fs.unlink(filePath, () => {
|
|
165
|
+
fs.unlink(filePath, () => {
|
|
166
|
+
pendingWrites--;
|
|
167
|
+
checkComplete();
|
|
168
|
+
});
|
|
141
169
|
});
|
|
142
170
|
|
|
143
171
|
file.on("error", (err) => {
|
|
144
172
|
console.error("File stream error:", err);
|
|
145
173
|
writeStream.end();
|
|
174
|
+
pendingWrites--;
|
|
175
|
+
checkComplete();
|
|
146
176
|
});
|
|
147
177
|
|
|
148
178
|
writeStream.on("error", (err) => {
|
|
149
179
|
console.error("Write stream error:", err);
|
|
150
180
|
file.resume();
|
|
181
|
+
pendingWrites--;
|
|
182
|
+
checkComplete();
|
|
151
183
|
});
|
|
152
184
|
|
|
153
185
|
writeStream.on("finish", () => {
|
|
@@ -160,6 +192,8 @@ function parseMultipart(req, res, media, options, resolve, reject) {
|
|
|
160
192
|
size,
|
|
161
193
|
});
|
|
162
194
|
}
|
|
195
|
+
pendingWrites--;
|
|
196
|
+
checkComplete();
|
|
163
197
|
});
|
|
164
198
|
|
|
165
199
|
file.pipe(writeStream);
|
|
@@ -172,11 +206,8 @@ function parseMultipart(req, res, media, options, resolve, reject) {
|
|
|
172
206
|
});
|
|
173
207
|
|
|
174
208
|
bb.on("finish", () => {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
} else {
|
|
178
|
-
resolve();
|
|
179
|
-
}
|
|
209
|
+
busboyFinished = true;
|
|
210
|
+
checkComplete();
|
|
180
211
|
});
|
|
181
212
|
|
|
182
213
|
req.pipe(bb);
|
package/vibe.d.ts
CHANGED
|
@@ -26,15 +26,26 @@ export interface UploadedFile {
|
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Configuration for file uploads on a specific route.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* {
|
|
32
|
+
* dest: "uploads",
|
|
33
|
+
* maxSize: 5 * 1024 * 1024, // 5MB
|
|
34
|
+
* allowedTypes: ["image/jpeg", "image/png"],
|
|
35
|
+
* public: true
|
|
36
|
+
* }
|
|
29
37
|
*/
|
|
30
38
|
export interface MediaOptions {
|
|
31
39
|
/** Save file inside the configured public folder. Default: true */
|
|
32
40
|
public?: boolean;
|
|
33
41
|
/** Subfolder destination for uploads (e.g., "uploads/avatars") */
|
|
34
42
|
dest?: string;
|
|
35
|
-
/** Maximum allowed file size in bytes. Default: 10 MB */
|
|
43
|
+
/** Maximum allowed file size in bytes. Default: 10 MB (10485760) */
|
|
36
44
|
maxSize?: number;
|
|
37
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* Allowed MIME types. Supports wildcards like "image/*"
|
|
47
|
+
* @example ["image/jpeg", "image/png", "application/pdf"]
|
|
48
|
+
*/
|
|
38
49
|
allowedTypes?: string[];
|
|
39
50
|
/** Enable streaming mode for large files. Use req.on('file', ...) */
|
|
40
51
|
streaming?: boolean;
|
|
@@ -42,11 +53,40 @@ export interface MediaOptions {
|
|
|
42
53
|
|
|
43
54
|
/**
|
|
44
55
|
* Options for registering a route.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // With interceptor only
|
|
59
|
+
* { intercept: authMiddleware }
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // With file upload
|
|
63
|
+
* {
|
|
64
|
+
* intercept: authMiddleware,
|
|
65
|
+
* media: {
|
|
66
|
+
* dest: "uploads",
|
|
67
|
+
* maxSize: 10 * 1024 * 1024,
|
|
68
|
+
* allowedTypes: ["image/*"]
|
|
69
|
+
* }
|
|
70
|
+
* }
|
|
45
71
|
*/
|
|
46
72
|
export interface RouteOptions {
|
|
47
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* Middleware function(s) to run before the handler.
|
|
75
|
+
* Return false to stop execution.
|
|
76
|
+
* @example
|
|
77
|
+
* intercept: (req, res) => {
|
|
78
|
+
* if (!req.headers.authorization) {
|
|
79
|
+
* res.unauthorized();
|
|
80
|
+
* return false;
|
|
81
|
+
* }
|
|
82
|
+
* return true;
|
|
83
|
+
* }
|
|
84
|
+
*/
|
|
48
85
|
intercept?: Interceptor | Interceptor[];
|
|
49
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Configuration for file uploads (multipart/form-data).
|
|
88
|
+
* Files will be available in req.files array.
|
|
89
|
+
*/
|
|
50
90
|
media?: MediaOptions;
|
|
51
91
|
}
|
|
52
92
|
|
|
@@ -175,13 +215,45 @@ export const color: Record<ColorName, (text: string) => string>;
|
|
|
175
215
|
|
|
176
216
|
/**
|
|
177
217
|
* Route registration function.
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* // Simple handler
|
|
221
|
+
* app.get("/path", (req, res) => { ... });
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* // Static response
|
|
225
|
+
* app.get("/", "Hello World");
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* // With options (interceptor + file upload)
|
|
229
|
+
* app.post("/upload", {
|
|
230
|
+
* intercept: authMiddleware,
|
|
231
|
+
* media: {
|
|
232
|
+
* dest: "uploads",
|
|
233
|
+
* maxSize: 10 * 1024 * 1024,
|
|
234
|
+
* allowedTypes: ["image/*"]
|
|
235
|
+
* }
|
|
236
|
+
* }, handler);
|
|
181
237
|
*/
|
|
182
238
|
export interface RouteRegistrar {
|
|
239
|
+
/**
|
|
240
|
+
* Register a route with a handler or static response.
|
|
241
|
+
* @param path - Route path (e.g., "/users/:id")
|
|
242
|
+
* @param handler - Handler function, string, number, or object
|
|
243
|
+
*/
|
|
183
244
|
(path: string, handler: Handler | string | number | object): void;
|
|
184
|
-
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Register a route with options and handler.
|
|
248
|
+
* @param path - Route path (e.g., "/upload")
|
|
249
|
+
* @param options - Route options (intercept, media)
|
|
250
|
+
* @param handler - Handler function
|
|
251
|
+
*/
|
|
252
|
+
(
|
|
253
|
+
path: string,
|
|
254
|
+
options: RouteOptions,
|
|
255
|
+
handler: Handler | string | number | object,
|
|
256
|
+
): void;
|
|
185
257
|
}
|
|
186
258
|
|
|
187
259
|
/** Sub-router or prefixed router instance */
|