zerra-core 1.2.0 → 1.2.1

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.
Files changed (3) hide show
  1. package/index.d.ts +47 -0
  2. package/index.js +308 -49
  3. package/package.json +3 -2
package/index.d.ts ADDED
@@ -0,0 +1,47 @@
1
+ import { IncomingMessage, ServerResponse } from 'http';
2
+
3
+ export interface ZerraRequest extends IncomingMessage {
4
+ query: Record<string, string>;
5
+ path: string;
6
+ body: any;
7
+ files: Array<{
8
+ fieldname: string;
9
+ filename: string;
10
+ encoding: string;
11
+ mimetype: string;
12
+ buffer: Buffer;
13
+ }>;
14
+ params: Record<string, string>;
15
+ }
16
+
17
+ export interface ZerraResponse extends ServerResponse {
18
+ status(code: number): ZerraResponse;
19
+ json(data: any): void;
20
+ cors(options?: { origin?: string; methods?: string }): ZerraResponse;
21
+ }
22
+
23
+ export type ZerraHandler = (req: ZerraRequest, res: ZerraResponse) => void | Promise<void>;
24
+
25
+ export type ZerraMiddleware = (req: ZerraRequest, res: ZerraResponse, next: () => Promise<void>) => void | Promise<void>;
26
+
27
+ export interface ZerraConfig {
28
+ features: {
29
+ logging?: boolean;
30
+ dynamicRouting?: boolean;
31
+ middleware?: boolean;
32
+ dotenv?: boolean;
33
+ validation?: boolean;
34
+ multipart?: boolean;
35
+ errors?: boolean;
36
+ dashboard?: boolean;
37
+ };
38
+ plugins?: string[];
39
+ }
40
+
41
+ export interface ZerraApp {
42
+ use(fn: ZerraMiddleware): void;
43
+ decorate(target: 'req' | 'res', name: string, fn: Function): void;
44
+ config: ZerraConfig;
45
+ }
46
+
47
+ export function startServer(port?: number): void;
package/index.js CHANGED
@@ -3,6 +3,7 @@ const fs = require("fs");
3
3
  const path = require("path");
4
4
 
5
5
  function startServer(port = 3000) {
6
+ const jiti = require("jiti")(__filename);
6
7
  const configPath = path.join(process.cwd(), 'zerra.config.json');
7
8
  let config = {
8
9
  features: {
@@ -26,6 +27,10 @@ function startServer(port = 3000) {
26
27
  }
27
28
  }
28
29
 
30
+ const customEnvKeys = new Set();
31
+ const recentRequests = [];
32
+ const MAX_LOGS = 20;
33
+
29
34
  // 8. Enhanced DX: Auto-load .env files
30
35
  if (config.features.dotenv) {
31
36
  const envPath = path.join(process.cwd(), '.env');
@@ -39,7 +44,11 @@ function startServer(port = 3000) {
39
44
  // Remove quotes
40
45
  if (value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
41
46
  else if (value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
42
- if (!process.env.hasOwnProperty(key)) process.env[key] = value;
47
+
48
+ if (!process.env.hasOwnProperty(key)) {
49
+ process.env[key] = value;
50
+ }
51
+ customEnvKeys.add(key);
43
52
  }
44
53
  });
45
54
  }
@@ -62,7 +71,7 @@ function startServer(port = 3000) {
62
71
  if (config.plugins && Array.isArray(config.plugins)) {
63
72
  config.plugins.forEach(pluginPath => {
64
73
  try {
65
- const plugin = require(path.isAbsolute(pluginPath) ? pluginPath : path.join(process.cwd(), pluginPath));
74
+ const plugin = jiti(path.isAbsolute(pluginPath) ? pluginPath : path.join(process.cwd(), pluginPath));
66
75
  if (typeof plugin === 'function') plugin(zerra);
67
76
  } catch (e) {
68
77
  console.error(`❌ Failed to load plugin: ${pluginPath}`, e);
@@ -79,12 +88,28 @@ function startServer(port = 3000) {
79
88
  // 1. Enhanced DX: Beautiful Request Logging
80
89
  const originalEnd = res.end;
81
90
  res.end = function (...args) {
91
+ const duration = Date.now() - startTime;
92
+ const path = req.path || url;
93
+
94
+ // Log to terminal
82
95
  if (config.features.logging) {
83
- const duration = Date.now() - startTime;
84
96
  const statusColor = res.statusCode >= 500 ? '\x1b[31m' : res.statusCode >= 400 ? '\x1b[33m' : '\x1b[32m';
85
97
  const resetColor = '\x1b[0m';
86
- console.log(`${statusColor}[${method}]${resetColor} ${req.path || url} ➜ ${statusColor}${res.statusCode}${resetColor} (${duration}ms)`);
98
+ console.log(`${statusColor}[${method}]${resetColor} ${path} ➜ ${statusColor}${res.statusCode}${resetColor} (${duration}ms)`);
99
+ }
100
+
101
+ // Store for dashboard (exclude the dashboard itself)
102
+ if (path !== '/__zerra' && path !== '/favicon.ico') {
103
+ recentRequests.unshift({
104
+ method,
105
+ path,
106
+ statusCode: res.statusCode,
107
+ duration,
108
+ timestamp: new Date().toLocaleTimeString()
109
+ });
110
+ if (recentRequests.length > MAX_LOGS) recentRequests.pop();
87
111
  }
112
+
88
113
  return originalEnd.apply(this, args);
89
114
  };
90
115
 
@@ -191,9 +216,18 @@ function startServer(port = 3000) {
191
216
  const stat = fs.statSync(filePath);
192
217
  if (stat && stat.isDirectory()) {
193
218
  results = results.concat(getRoutes(filePath, path.join(base, file)));
194
- } else if (file.endsWith('.js') && !file.startsWith('_')) {
195
- const route = path.join(base, file).replace(/\\/g, '/').replace('.js', '');
196
- results.push(route === 'index' ? '/' : `/${route}`);
219
+ } else if ((file.endsWith('.js') || file.endsWith('.ts')) && !file.startsWith('_')) {
220
+ const route = path.join(base, file).replace(/\\/g, '/').replace(/\.(js|ts)$/, '');
221
+ const fullPath = `/${route === 'index' ? '' : route}`;
222
+
223
+ // Try to extract schema for playground presets
224
+ let schema = null;
225
+ try {
226
+ const mod = jiti(filePath);
227
+ schema = mod.schema || (mod.default && mod.default.schema);
228
+ } catch (e) {}
229
+
230
+ results.push({ path: fullPath, schema });
197
231
  }
198
232
  });
199
233
  return results;
@@ -202,51 +236,265 @@ function startServer(port = 3000) {
202
236
  const routes = getRoutes(apiDir);
203
237
  const featureList = Object.entries(config.features)
204
238
  .map(([k, v]) => `<li><strong>${k}</strong>: ${v ? '✅' : '❌'}</li>`).join('');
205
- const routeList = routes.map(r => `<li><a href="${r}">${r}</a></li>`).join('');
239
+ const routeList = routes.map(r => `<li><a href="${r.path}">${r.path}</a></li>`).join('');
206
240
 
207
- res.setHeader('Content-Type', 'text/html');
241
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
208
242
  return res.end(`
209
243
  <!DOCTYPE html>
210
- <html>
244
+ <html lang="en">
211
245
  <head>
212
- <title>Zerra Dashboard</title>
246
+ <meta charset="UTF-8">
247
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
248
+ <title>Zerra Dev Dashboard</title>
213
249
  <style>
214
- body { font-family: sans-serif; line-height: 1.6; max-width: 800px; margin: 40px auto; padding: 0 20px; color: #333; background: #f9f9f9; }
215
- h1 { color: #000; border-bottom: 2px solid #eee; padding-bottom: 10px; }
216
- section { background: #fff; padding: 20px; border: 1px solid #ddd; border-radius: 5px; margin-bottom: 20px; }
217
- h2 { margin-top: 0; font-size: 1.2rem; }
218
- ul { padding-left: 20px; }
219
- li { margin-bottom: 5px; }
220
- a { color: #0070f3; text-decoration: none; }
221
- a:hover { text-decoration: underline; }
222
- .badge { font-size: 0.8rem; background: #000; color: #fff; padding: 2px 6px; border-radius: 3px; vertical-align: middle; }
250
+ :root {
251
+ --primary: #0070f3;
252
+ --bg: #fafafa;
253
+ --card-bg: #ffffff;
254
+ --text: #171717;
255
+ --text-light: #666;
256
+ --border: #eaeaea;
257
+ --success: #0070f3;
258
+ --warning: #f5a623;
259
+ --error: #ff0000;
260
+ }
261
+ body {
262
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
263
+ line-height: 1.5;
264
+ background: var(--bg);
265
+ color: var(--text);
266
+ margin: 0;
267
+ padding: 0;
268
+ }
269
+ header {
270
+ background: linear-gradient(135deg, #000000 0%, #1a1a1a 100%);
271
+ color: #fff;
272
+ padding: 16px 40px;
273
+ display: flex;
274
+ align-items: center;
275
+ justify-content: space-between;
276
+ box-shadow: 0 4px 12px rgba(0,0,0,0.08);
277
+ border-bottom: 1px solid rgba(255,255,255,0.1);
278
+ position: sticky;
279
+ top: 0;
280
+ z-index: 100;
281
+ }
282
+ header h1 {
283
+ margin: 0;
284
+ font-size: 1.2rem;
285
+ display: flex;
286
+ align-items: center;
287
+ gap: 12px;
288
+ letter-spacing: 2px;
289
+ font-weight: 800;
290
+ }
291
+ header h1 span.console-text {
292
+ font-weight: 300;
293
+ opacity: 0.6;
294
+ font-size: 0.9rem;
295
+ letter-spacing: 0;
296
+ border-left: 1px solid rgba(255,255,255,0.2);
297
+ padding-left: 12px;
298
+ }
299
+ .badge { font-size: 0.75rem; background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 20px; font-weight: normal; }
300
+
301
+ main { max-width: 1300px; margin: 40px auto; padding: 0 20px; }
302
+
303
+ .grid { display: grid; grid-template-columns: 2.5fr 1fr; gap: 25px; margin-bottom: 20px; }
304
+ @media (max-width: 1024px) { .grid { grid-template-columns: 1fr; } }
305
+
306
+ section {
307
+ background: var(--card-bg);
308
+ padding: 24px;
309
+ border-radius: 12px;
310
+ border: 1px solid var(--border);
311
+ box-shadow: 0 4px 6px rgba(0,0,0,0.02);
312
+ }
313
+ h2 { margin-top: 0; font-size: 1.1rem; margin-bottom: 20px; display: flex; align-items: center; gap: 8px; color: var(--text-light); text-transform: uppercase; letter-spacing: 1px; }
314
+
315
+ ul { list-style: none; padding: 0; margin: 0; }
316
+ li { margin-bottom: 10px; display: flex; align-items: center; justify-content: space-between; }
317
+
318
+ .route-link { color: var(--primary); text-decoration: none; font-weight: 500; font-family: monospace; font-size: 1rem; }
319
+ .route-link:hover { text-decoration: underline; }
320
+
321
+ table { width: 100%; border-collapse: collapse; margin-top: 10px; }
322
+ th { text-align: left; padding: 12px 8px; border-bottom: 2px solid var(--border); font-size: 0.85rem; color: var(--text-light); }
323
+ td { padding: 12px 8px; border-bottom: 1px solid var(--border); font-size: 0.9rem; }
324
+
325
+ .status-badge {
326
+ padding: 4px 10px;
327
+ border-radius: 6px;
328
+ font-size: 0.75rem;
329
+ font-weight: bold;
330
+ color: #fff;
331
+ }
332
+
333
+ .env-item { background: #f3f4f6; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 0.85rem; color: #444; }
334
+
335
+ /* Fix for long error messages/stacks */
336
+ pre {
337
+ white-space: pre-wrap !important;
338
+ word-break: break-all !important;
339
+ max-height: 300px !important;
340
+ overflow-y: auto !important;
341
+ margin: 0 !important;
342
+ }
223
343
  </style>
224
344
  </head>
225
345
  <body>
226
- <h1>🚀 Zerra Dev Dashboard <span class="badge">v1.1.1</span></h1>
227
-
228
- <section>
229
- <h2>📂 Active Routes</h2>
230
- <ul>${routeList || '<li>No routes found in /api</li>'}</ul>
231
- </section>
232
-
233
- <section>
234
- <h2>⚙️ Enabled Features</h2>
235
- <ul>${featureList}</ul>
236
- </section>
237
-
238
- <section>
239
- <h2>🔐 Environment Variables</h2>
240
- <ul>${Object.keys(process.env).filter(k => !k.startsWith('npm_') && !k.startsWith('NODE_')).map(k => `<li>${k}</li>`).join('') || '<li>No custom env vars loaded</li>'}</ul>
241
- </section>
242
-
243
- <p><small>Zerra Engine is running in development mode.</small></p>
346
+ <main style="margin-top: 20px;">
347
+ <div class="grid">
348
+ <section>
349
+ <h2>📂 Active Routes & Playground</h2>
350
+ <div style="display: flex; flex-direction: column; gap: 15px;">
351
+ ${routes.length > 0 ? routes.map(r => {
352
+ const sampleBody = r.schema ? JSON.stringify(Object.fromEntries(
353
+ Object.entries(r.schema).map(([k, t]) => [k, t === 'number' ? 0 : t === 'boolean' ? false : 'text'])
354
+ )) : '{}';
355
+
356
+ return `
357
+ <div style="border: 1px solid var(--border); border-radius: 8px; padding: 15px;">
358
+ <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;">
359
+ <a href="${r.path}" class="route-link" style="font-size: 1.1rem;">${r.path}</a>
360
+ ${r.schema ? '<span class="badge" style="background:#eee; color:#666;">Has Schema</span>' : ''}
361
+ </div>
362
+
363
+ <div style="display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px;">
364
+ <select id="method-${r.path}" style="padding: 4px 8px; border-radius: 4px; border: 1px solid var(--border); background: #fff; font-family: inherit;">
365
+ <option value="GET">GET</option>
366
+ <option value="POST">POST</option>
367
+ <option value="PUT">PUT</option>
368
+ <option value="PATCH">PATCH</option>
369
+ <option value="DELETE">DELETE</option>
370
+ </select>
371
+ <input type="text" id="body-${r.path}" value='${sampleBody}' placeholder='{"key": "value"}' style="flex-grow: 1; padding: 4px 10px; border-radius: 4px; border: 1px solid var(--border); font-family: monospace; font-size: 0.8rem;">
372
+ <button onclick="testRoute('${r.path}')" style="background: var(--primary); color: #fff; border: none; padding: 5px 15px; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 0.8rem;">SEND</button>
373
+ </div>
374
+
375
+ <div id="res-${r.path}" style="display: none; background: #1e1e1e; color: #d4d4d4; padding: 12px; border-radius: 6px; font-family: monospace; font-size: 0.85rem; overflow-x: auto; margin-top: 10px; position: relative;">
376
+ <div id="status-${r.path}" style="position: absolute; top: 8px; right: 8px; font-size: 0.7rem; font-weight: bold;"></div>
377
+ <pre style="margin: 0;"></pre>
378
+ </div>
379
+ </div>
380
+ `}).join('') : '<div style="color:#999">No routes found in /api</div>'}
381
+ </div>
382
+ </section>
383
+
384
+ <section>
385
+ <h2>⚙️ Features</h2>
386
+ <div style="display: flex; flex-direction: column; gap: 12px;">
387
+ ${Object.entries(config.features).map(([k, v]) => `
388
+ <div style="display: flex; align-items: center; justify-content: space-between; font-size: 0.9rem; border-bottom: 1px solid #f9f9f9; padding-bottom: 8px;">
389
+ <span style="color: ${v ? 'inherit' : '#999'}; font-weight: 500;">${k}</span>
390
+ <span>${v ? '✅' : '❌'}</span>
391
+ </div>
392
+ `).join('')}
393
+ </div>
394
+ </section>
395
+ </div>
396
+
397
+ <section style="margin-bottom: 20px;">
398
+ <h2>📊 Recent Activity</h2>
399
+ <table>
400
+ <thead>
401
+ <tr>
402
+ <th>METHOD</th>
403
+ <th>PATH</th>
404
+ <th>STATUS</th>
405
+ <th>TIME</th>
406
+ <th>DURATION</th>
407
+ </tr>
408
+ </thead>
409
+ <tbody>
410
+ ${recentRequests.map(req => {
411
+ const color = req.statusCode >= 500 ? 'var(--error)' : req.statusCode >= 400 ? 'var(--warning)' : 'var(--success)';
412
+ return `
413
+ <tr>
414
+ <td><strong>${req.method}</strong></td>
415
+ <td style="font-family: monospace; color: #444;">${req.path}</td>
416
+ <td><span class="status-badge" style="background: ${color}">${req.statusCode}</span></td>
417
+ <td style="color: #888;">${req.timestamp}</td>
418
+ <td style="color: #888;">${req.duration}ms</td>
419
+ </tr>
420
+ `;
421
+ }).join('') || '<tr><td colspan="5" style="text-align: center; padding: 40px; color: #999;">Waiting for requests...</td></tr>'}
422
+ </tbody>
423
+ </table>
424
+ </section>
425
+
426
+ <section>
427
+ <h2>🔐 Environment</h2>
428
+ <div style="display: flex; flex-wrap: wrap; gap: 10px;">
429
+ ${Object.keys(process.env).filter(k => {
430
+ const isCustom = customEnvKeys.has(k);
431
+ const isImportant = ['PORT', 'NODE_ENV'].includes(k);
432
+ const isSystem = /^(ALLUSERSPROFILE|APPDATA|COMPUTERNAME|ComSpec|Common|DriverData|HOMEDRIVE|HOMEPATH|LOCALAPPDATA|LOGONSERVER|NUMBER_OF_PROCESSORS|OS|Path|PATHEXT|PROCESSOR|Program|PSModulePath|PUBLIC|System|TEMP|TMP|USER|windir|ZES_|VSCODE_|ANTIGRAVITY_)/i.test(k);
433
+ return (isCustom || isImportant) && !isSystem;
434
+ }).map(k => `
435
+ <div style="background: #f9f9f9; border: 1px solid #eee; padding: 10px 15px; border-radius: 8px;">
436
+ <div style="font-size: 0.7rem; color: #999; margin-bottom: 4px; font-weight: bold;">${k}</div>
437
+ <div class="env-item">${process.env[k]}</div>
438
+ </div>
439
+ `).join('') || '<div style="color:#999">No custom variables loaded</div>'}
440
+ </div>
441
+ </section>
442
+ </main>
443
+
444
+ <script>
445
+ async function testRoute(path) {
446
+ const method = document.getElementById('method-' + path).value;
447
+ const bodyStr = document.getElementById('body-' + path).value;
448
+ const resDiv = document.getElementById('res-' + path);
449
+ const statusDiv = document.getElementById('status-' + path);
450
+ const pre = resDiv.querySelector('pre');
451
+
452
+ resDiv.style.display = 'block';
453
+ pre.innerText = 'Sending request...';
454
+ statusDiv.innerText = '';
455
+
456
+ try {
457
+ const options = { method, headers: {} };
458
+ if (['POST', 'PUT', 'PATCH'].includes(method) && bodyStr) {
459
+ options.headers['Content-Type'] = 'application/json';
460
+ options.body = bodyStr;
461
+ }
462
+
463
+ const start = Date.now();
464
+ const response = await fetch(path, options);
465
+ const duration = Date.now() - start;
466
+ const data = await response.json().catch(() => null);
467
+
468
+ statusDiv.innerText = response.status + ' (' + duration + 'ms)';
469
+ statusDiv.style.color = response.status >= 400 ? '#ff4d4f' : '#52c41a';
470
+ pre.innerText = JSON.stringify(data, null, 2) || 'No response body';
471
+ } catch (err) {
472
+ statusDiv.innerText = 'ERROR';
473
+ statusDiv.style.color = '#ff4d4f';
474
+ pre.innerText = err.message;
475
+ }
476
+ }
477
+
478
+ // Soft refresh every 5 seconds
479
+ let refreshTimeout = setTimeout(() => {
480
+ window.location.reload();
481
+ }, 5000);
482
+
483
+ // Pause refresh if user is interacting with playground
484
+ document.addEventListener('mousedown', () => {
485
+ clearTimeout(refreshTimeout);
486
+ refreshTimeout = setTimeout(() => window.location.reload(), 15000);
487
+ });
488
+ </script>
244
489
  </body>
245
490
  </html>
246
491
  `);
247
492
  }
248
493
 
249
494
  let filePath = path.join(apiDir, `${cleanPath}.js`);
495
+ if (!fs.existsSync(filePath)) {
496
+ filePath = path.join(apiDir, `${cleanPath}.ts`);
497
+ }
250
498
 
251
499
  // 6. Enhanced DX: Dynamic Routing ([id].js)
252
500
  if (config.features.dynamicRouting && !fs.existsSync(filePath)) {
@@ -266,9 +514,9 @@ function startServer(port = 3000) {
266
514
 
267
515
  // Look for dynamic parameter [param]
268
516
  if (!match) {
269
- match = files.find(f => isLast ? (f.startsWith('[') && f.endsWith('].js')) : (f.startsWith('[') && f.endsWith(']') && fs.statSync(path.join(currentDir, f)).isDirectory()));
517
+ match = files.find(f => isLast ? (f.startsWith('[') && (f.endsWith('].js') || f.endsWith('].ts'))) : (f.startsWith('[') && f.endsWith(']') && fs.statSync(path.join(currentDir, f)).isDirectory()));
270
518
  if (match) {
271
- const paramName = isLast ? match.slice(1, -4) : match.slice(1, -1);
519
+ const paramName = isLast ? match.slice(1, match.lastIndexOf('].')) : match.slice(1, -1);
272
520
  req.params[paramName] = part;
273
521
  }
274
522
  }
@@ -301,7 +549,9 @@ function startServer(port = 3000) {
301
549
 
302
550
  if (config.features.middleware) {
303
551
  while (currentPath.length >= apiDir.length && currentPath.startsWith(apiDir)) {
304
- const mwPath = path.join(currentPath, '_middleware.js');
552
+ let mwPath = path.join(currentPath, '_middleware.js');
553
+ if (!fs.existsSync(mwPath)) mwPath = path.join(currentPath, '_middleware.ts');
554
+
305
555
  if (fs.existsSync(mwPath)) {
306
556
  middlewarePaths.unshift(mwPath); // Run top-down
307
557
  }
@@ -326,22 +576,30 @@ function startServer(port = 3000) {
326
576
  if (middlewareIndex < middlewarePaths.length) {
327
577
  const mwPath = middlewarePaths[middlewareIndex++];
328
578
  delete require.cache[require.resolve(mwPath)];
329
- const mw = require(mwPath);
330
- if (typeof mw === 'function') {
331
- await mw(req, res, runNext);
579
+ const mw = jiti(mwPath);
580
+ if (typeof mw === 'function' || (mw && typeof mw.default === 'function')) {
581
+ const actualMw = mw.default || mw;
582
+ await actualMw(req, res, runNext);
332
583
  } else {
333
584
  await runNext();
334
585
  }
335
586
  } else {
336
587
  delete require.cache[require.resolve(filePath)];
337
- const handler = require(filePath);
588
+ const handler = jiti(filePath);
338
589
 
339
590
  if (typeof handler === "function" || (handler && typeof handler.default === "function")) {
340
591
  const actualHandler = handler.default || handler;
341
592
 
342
593
  // 9. Enhanced DX: Input Validation
343
594
  const schema = handler.schema;
344
- if (config.features.validation && schema && typeof req.body === 'object' && req.body !== null) {
595
+ if (config.features.validation && schema) {
596
+ if (!req.body || typeof req.body !== 'object') {
597
+ return res.status(400).json({
598
+ error: "Validation Failed",
599
+ details: ["Request body is required for this route."]
600
+ });
601
+ }
602
+
345
603
  const errors = [];
346
604
  for (const [key, type] of Object.entries(schema)) {
347
605
  if (typeof req.body[key] !== type) {
@@ -379,9 +637,10 @@ function startServer(port = 3000) {
379
637
  if (fs.existsSync(errorHandlerPath)) {
380
638
  try {
381
639
  delete require.cache[require.resolve(errorHandlerPath)];
382
- const errorHandler = require(errorHandlerPath);
383
- if (typeof errorHandler === 'function') {
384
- return await errorHandler(err, req, res);
640
+ const errorHandler = jiti(errorHandlerPath);
641
+ const actualErrorHandler = errorHandler.default || errorHandler;
642
+ if (typeof actualErrorHandler === 'function') {
643
+ return await actualErrorHandler(err, req, res);
385
644
  }
386
645
  } catch (e) {
387
646
  console.error("❌ Error in custom error handler:", e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zerra-core",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -11,6 +11,7 @@
11
11
  "license": "ISC",
12
12
  "type": "commonjs",
13
13
  "dependencies": {
14
- "busboy": "^1.6.0"
14
+ "busboy": "^1.6.0",
15
+ "jiti": "^2.6.1"
15
16
  }
16
17
  }