zerra-core 1.1.0 → 1.2.0

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 (2) hide show
  1. package/index.js +138 -3
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -11,8 +11,11 @@ function startServer(port = 3000) {
11
11
  middleware: true,
12
12
  dotenv: true,
13
13
  validation: true,
14
- multipart: true
15
- }
14
+ multipart: true,
15
+ errors: true,
16
+ dashboard: true
17
+ },
18
+ plugins: []
16
19
  };
17
20
  if (fs.existsSync(configPath)) {
18
21
  try {
@@ -42,6 +45,31 @@ function startServer(port = 3000) {
42
45
  }
43
46
  }
44
47
 
48
+ const globalMiddleware = [];
49
+ const resDecorators = {};
50
+ const reqDecorators = {};
51
+
52
+ const zerra = {
53
+ use: (fn) => globalMiddleware.push(fn),
54
+ decorate: (target, name, fn) => {
55
+ if (target === 'res') resDecorators[name] = fn;
56
+ if (target === 'req') reqDecorators[name] = fn;
57
+ },
58
+ config
59
+ };
60
+
61
+ // Load Plugins
62
+ if (config.plugins && Array.isArray(config.plugins)) {
63
+ config.plugins.forEach(pluginPath => {
64
+ try {
65
+ const plugin = require(path.isAbsolute(pluginPath) ? pluginPath : path.join(process.cwd(), pluginPath));
66
+ if (typeof plugin === 'function') plugin(zerra);
67
+ } catch (e) {
68
+ console.error(`❌ Failed to load plugin: ${pluginPath}`, e);
69
+ }
70
+ });
71
+ }
72
+
45
73
  const apiDir = path.join(process.cwd(), "api");
46
74
 
47
75
  const server = http.createServer(async (req, res) => {
@@ -76,6 +104,10 @@ function startServer(port = 3000) {
76
104
  req.query = Object.fromEntries(parsedUrl.searchParams);
77
105
  req.path = parsedUrl.pathname;
78
106
 
107
+ // Apply Decorators
108
+ Object.entries(resDecorators).forEach(([name, fn]) => { res[name] = fn.bind(res); });
109
+ Object.entries(reqDecorators).forEach(([name, fn]) => { req[name] = fn.bind(req); });
110
+
79
111
  // 3. Enhanced DX: CORS Helper
80
112
  res.cors = (options = { origin: '*', methods: 'GET,POST,PUT,DELETE,OPTIONS' }) => {
81
113
  res.setHeader('Access-Control-Allow-Origin', options.origin);
@@ -147,6 +179,73 @@ function startServer(port = 3000) {
147
179
 
148
180
  req.params = {};
149
181
  const cleanPath = req.path === "/" ? "/index" : req.path;
182
+
183
+ // 10. Enhanced DX: Dev Dashboard
184
+ if (config.features.dashboard && cleanPath === '/__zerra') {
185
+ const getRoutes = (dir, base = '') => {
186
+ let results = [];
187
+ if (!fs.existsSync(dir)) return results;
188
+ const list = fs.readdirSync(dir);
189
+ list.forEach(file => {
190
+ const filePath = path.join(dir, file);
191
+ const stat = fs.statSync(filePath);
192
+ if (stat && stat.isDirectory()) {
193
+ 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}`);
197
+ }
198
+ });
199
+ return results;
200
+ };
201
+
202
+ const routes = getRoutes(apiDir);
203
+ const featureList = Object.entries(config.features)
204
+ .map(([k, v]) => `<li><strong>${k}</strong>: ${v ? '✅' : '❌'}</li>`).join('');
205
+ const routeList = routes.map(r => `<li><a href="${r}">${r}</a></li>`).join('');
206
+
207
+ res.setHeader('Content-Type', 'text/html');
208
+ return res.end(`
209
+ <!DOCTYPE html>
210
+ <html>
211
+ <head>
212
+ <title>Zerra Dashboard</title>
213
+ <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; }
223
+ </style>
224
+ </head>
225
+ <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>
244
+ </body>
245
+ </html>
246
+ `);
247
+ }
248
+
150
249
  let filePath = path.join(apiDir, `${cleanPath}.js`);
151
250
 
152
251
  // 6. Enhanced DX: Dynamic Routing ([id].js)
@@ -261,9 +360,45 @@ function startServer(port = 3000) {
261
360
  }
262
361
  };
263
362
 
264
- await runNext();
363
+ // 11. Enhanced DX: Global Middleware (Plugins)
364
+ let globalIndex = 0;
365
+ const runGlobal = async () => {
366
+ if (globalIndex < globalMiddleware.length) {
367
+ await globalMiddleware[globalIndex++](req, res, runGlobal);
368
+ } else {
369
+ await runNext();
370
+ }
371
+ };
372
+
373
+ await runGlobal();
265
374
 
266
375
  } catch (err) {
376
+ if (config.features.errors) {
377
+ // Check for custom _error.js handler
378
+ const errorHandlerPath = path.join(apiDir, '_error.js');
379
+ if (fs.existsSync(errorHandlerPath)) {
380
+ try {
381
+ delete require.cache[require.resolve(errorHandlerPath)];
382
+ const errorHandler = require(errorHandlerPath);
383
+ if (typeof errorHandler === 'function') {
384
+ return await errorHandler(err, req, res);
385
+ }
386
+ } catch (e) {
387
+ console.error("❌ Error in custom error handler:", e);
388
+ }
389
+ }
390
+
391
+ const statusCode = err.status || 500;
392
+ const message = err.message || "Internal Server Error";
393
+
394
+ return res.status(statusCode).json({
395
+ error: statusCode >= 500 ? "Runtime Error" : "Request Error",
396
+ message: message,
397
+ stack: process.env.NODE_ENV === 'development' || !process.env.NODE_ENV ? err.stack : undefined
398
+ });
399
+ }
400
+
401
+ // Fallback if errors feature is disabled
267
402
  res.status(500).json({ error: "Runtime Error", message: err.message });
268
403
  }
269
404
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zerra-core",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {