vibe-gx 1.0.4 → 1.0.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-gx",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "A lightweight, regex-based Node.js web framework built for speed and simplicity.",
5
5
  "type": "module",
6
6
  "main": "vibe.js",
@@ -140,21 +140,34 @@ export async function runIntercept(intercept, req, res, isRoute = true) {
140
140
  */
141
141
  export function handleError(error, req, res) {
142
142
  const isDev = process.env.NODE_ENV !== "production";
143
+ const message = error.message || "Unknown error";
143
144
 
144
145
  // Log error (full stack in dev, message only in production)
145
146
  if (isDev) {
146
147
  console.error("[VIBE ERROR]:", error);
147
148
  } else {
148
- console.error("[VIBE ERROR]:", error.message || "Unknown error");
149
+ console.error("[VIBE ERROR]:", message);
149
150
  }
150
151
 
151
152
  if (!res.headersSent) {
152
- res.writeHead(500, { "content-type": "application/json" });
153
+ // Determine status code based on error type
154
+ let statusCode = 500;
155
+ let errorType = "Internal Server Error";
156
+
157
+ if (message.includes("exceeds max size")) {
158
+ statusCode = 413;
159
+ errorType = "Payload Too Large";
160
+ } else if (message.includes("not allowed")) {
161
+ statusCode = 415;
162
+ errorType = "Unsupported Media Type";
163
+ }
164
+
165
+ res.writeHead(statusCode, { "content-type": "application/json" });
153
166
 
154
167
  // Only expose error details in development
155
168
  const responseBody = isDev
156
- ? { error: "Internal Server Error", message: error.message }
157
- : { error: "Internal Server Error" };
169
+ ? { error: errorType, message }
170
+ : { error: errorType };
158
171
 
159
172
  res.end(JSON.stringify(responseBody));
160
173
  }
@@ -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
- if (!media.allowedTypes.includes(mimeType)) {
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
 
@@ -136,18 +161,28 @@ function parseMultipart(req, res, media, options, resolve, reject) {
136
161
  );
137
162
  file.unpipe(writeStream);
138
163
  writeStream.end();
164
+ // IMPORTANT: Resume the file stream to drain remaining data
165
+ // This allows busboy to finish parsing the multipart request
166
+ file.resume();
139
167
  // Clean up partial file
140
- fs.unlink(filePath, () => {});
168
+ fs.unlink(filePath, () => {
169
+ pendingWrites--;
170
+ checkComplete();
171
+ });
141
172
  });
142
173
 
143
174
  file.on("error", (err) => {
144
175
  console.error("File stream error:", err);
145
176
  writeStream.end();
177
+ pendingWrites--;
178
+ checkComplete();
146
179
  });
147
180
 
148
181
  writeStream.on("error", (err) => {
149
182
  console.error("Write stream error:", err);
150
183
  file.resume();
184
+ pendingWrites--;
185
+ checkComplete();
151
186
  });
152
187
 
153
188
  writeStream.on("finish", () => {
@@ -160,6 +195,8 @@ function parseMultipart(req, res, media, options, resolve, reject) {
160
195
  size,
161
196
  });
162
197
  }
198
+ pendingWrites--;
199
+ checkComplete();
163
200
  });
164
201
 
165
202
  file.pipe(writeStream);
@@ -172,11 +209,8 @@ function parseMultipart(req, res, media, options, resolve, reject) {
172
209
  });
173
210
 
174
211
  bb.on("finish", () => {
175
- if (fileError) {
176
- reject(fileError);
177
- } else {
178
- resolve();
179
- }
212
+ busboyFinished = true;
213
+ checkComplete();
180
214
  });
181
215
 
182
216
  req.pipe(bb);