vibe-gx 4.1.4 → 4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-gx",
3
- "version": "4.1.4",
3
+ "version": "4.2.0",
4
4
  "description": "A lightweight, high-performance Node.js web framework.",
5
5
  "type": "module",
6
6
  "main": "vibe.js",
@@ -9,6 +9,7 @@ const LOG_LEVELS = {
9
9
  warn: 40,
10
10
  error: 50,
11
11
  fatal: 60,
12
+ silent: 100, // Higher than all levels — suppresses all output (logger: false)
12
13
  };
13
14
 
14
15
  const LEVEL_NAMES = {
@@ -136,45 +137,59 @@ export class Logger {
136
137
  _printPretty(log) {
137
138
  const time = new Date(log.time).toLocaleTimeString();
138
139
  const lvlName = LEVEL_NAMES[log.level] || "INFO";
139
- let prefixC = color.cyan;
140
- if (log.level >= 50) prefixC = color.red;
141
- else if (log.level === 40) prefixC = color.yellow;
142
- else if (log.level <= 20) prefixC = color.dim;
143
-
144
- const prefix = prefixC(`[VIBE ${lvlName} ${time}]`);
145
- let context = "";
146
- if (log.reqId) {
147
- context = `\x1b[90m[${log.reqId}]\x1b[0m `;
148
- }
149
140
 
150
- let content = log.msg || "";
151
- if (log.color && color[log.color]) {
152
- content = color[log.color](content);
153
- }
141
+ const isError = log.level >= 50;
142
+ const isWarn = log.level === 40;
143
+ const isDebug = log.level <= 20;
144
+
145
+ // Build context tag (reqId)
146
+ const context = log.reqId ? `[${log.reqId}] ` : "";
154
147
 
148
+ // Build message content
149
+ let content = log.msg || "";
155
150
  if (log.err && log.err.stack) {
156
- content += "\n" + prefixC(log.err.stack);
151
+ content += "\n" + log.err.stack;
157
152
  }
158
153
 
159
- // Attempt to print remaining metadata if it's not standard
154
+ // Build metadata string (skip standard keys)
160
155
  const skipKeys = [
161
- "level",
162
- "time",
163
- "pid",
164
- "hostname",
165
- "reqId",
166
- "msg",
167
- "err",
168
- "color",
156
+ "level", "time", "pid", "hostname", "reqId", "msg", "err", "color",
169
157
  ];
170
158
  let metaStr = "";
171
159
  for (const key of Object.keys(log)) {
172
160
  if (!skipKeys.includes(key)) {
173
- metaStr += ` \x1b[90m${key}=${JSON.stringify(log[key])}\x1b[0m`;
161
+ metaStr += ` ${key}=${JSON.stringify(log[key])}`;
174
162
  }
175
163
  }
176
164
 
177
- this.stream.write(`${prefix} ${context}${content}${metaStr}\n`);
165
+ const rawPrefix = `[VIBE ${lvlName} ${time}]`;
166
+
167
+ if (isError) {
168
+ // Entire line is red — prefix, context, message, stack, metadata
169
+ const fullLine = `${rawPrefix} ${context}${content}${metaStr}`;
170
+ this.stream.write(color.red(fullLine) + "\n");
171
+ } else if (isWarn) {
172
+ // Yellow prefix, bright content
173
+ const coloredContent = log.color && color[log.color]
174
+ ? color[log.color](content)
175
+ : color.bright(content);
176
+ this.stream.write(
177
+ color.yellow(rawPrefix) + " " + context + coloredContent +
178
+ (metaStr ? color.dim(metaStr) : "") + "\n",
179
+ );
180
+ } else if (isDebug) {
181
+ // Dim entire line for trace/debug
182
+ this.stream.write(color.dim(`${rawPrefix} ${context}${content}${metaStr}`) + "\n");
183
+ } else {
184
+ // Info — green prefix + bright content (matches [VIBE LOG] style)
185
+ const coloredContent = log.color && color[log.color]
186
+ ? color[log.color](content)
187
+ : color.bright(content);
188
+ this.stream.write(
189
+ color.green(rawPrefix) + " " + context + coloredContent +
190
+ (metaStr ? color.dim(metaStr) : "") + "\n",
191
+ );
192
+ }
178
193
  }
179
194
  }
180
195
 
@@ -103,7 +103,7 @@ function parseMultipart(req, res, media, options, resolve, reject) {
103
103
  },
104
104
  });
105
105
  } catch (err) {
106
- console.error("Busboy init failed:", err);
106
+ options.logger?.error(err, "[VIBE] Busboy init failed");
107
107
  return resolve();
108
108
  }
109
109
 
@@ -152,7 +152,10 @@ function parseMultipart(req, res, media, options, resolve, reject) {
152
152
  media.public &&
153
153
  !dest.startsWith(path.resolve(options.publicFolder || ""))
154
154
  ) {
155
- console.warn("Attempted upload outside public folder, skipping");
155
+ options.logger?.warn(
156
+ { dest, publicFolder: options.publicFolder },
157
+ "[VIBE] Attempted upload outside public folder, skipping",
158
+ );
156
159
  pendingWrites--;
157
160
  checkComplete();
158
161
  return file.resume();
@@ -161,7 +164,7 @@ function parseMultipart(req, res, media, options, resolve, reject) {
161
164
  try {
162
165
  if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
163
166
  } catch (err) {
164
- console.error("Failed to create upload folder:", err);
167
+ options.logger?.error(err, "[VIBE] Failed to create upload folder");
165
168
  pendingWrites--;
166
169
  checkComplete();
167
170
  return file.resume();
@@ -200,14 +203,14 @@ function parseMultipart(req, res, media, options, resolve, reject) {
200
203
  });
201
204
 
202
205
  file.on("error", (err) => {
203
- console.error("File stream error:", err);
206
+ options.logger?.error(err, "[VIBE] File stream error");
204
207
  writeStream.end();
205
208
  pendingWrites--;
206
209
  checkComplete();
207
210
  });
208
211
 
209
212
  writeStream.on("error", (err) => {
210
- console.error("Write stream error:", err);
213
+ options.logger?.error(err, "[VIBE] Write stream error");
211
214
  file.resume();
212
215
  pendingWrites--;
213
216
  checkComplete();
@@ -231,7 +234,7 @@ function parseMultipart(req, res, media, options, resolve, reject) {
231
234
  });
232
235
 
233
236
  bb.on("error", (err) => {
234
- console.error("Busboy error:", err);
237
+ options.logger?.error(err, "[VIBE] Busboy error");
235
238
  req.unpipe(bb);
236
239
  reject(err);
237
240
  });
@@ -266,7 +269,10 @@ function parseJson(req, res, media, options, resolve, reject) {
266
269
  req.on("data", (chunk) => {
267
270
  body += chunk;
268
271
  if (body.length > limit) {
269
- console.warn("JSON payload too large, destroying connection");
272
+ options.logger?.warn(
273
+ { limit, received: body.length },
274
+ "[VIBE] JSON payload too large, destroying connection",
275
+ );
270
276
  req.destroy();
271
277
  }
272
278
  });
@@ -243,7 +243,12 @@ const vibeResponseMethods = {
243
243
  * @param {Error} error
244
244
  */
245
245
  serverError(error) {
246
- console.error(error);
246
+ const logger = this._vibeOptions?.logger;
247
+ if (logger) {
248
+ logger.error(error, "[VIBE] Internal server error");
249
+ } else {
250
+ console.error(error);
251
+ }
247
252
  this.writeHead(500, JSON_CT);
248
253
  this.end(RESPONSES.serverError);
249
254
  },
@@ -319,8 +319,14 @@ async function server(options, port, host, callback) {
319
319
  getNetworkIP(mainHost, port);
320
320
 
321
321
  const strategy = useTrieMatching ? "Trie (O(log n))" : "Linear (O(n))";
322
- console.log(
323
- `[VIBE] Route matching: ${strategy} (${options.routeCount} routes, ${staticRoutes.size} static, threshold: ${options.trieThreshold})`,
322
+ options.logger.info(
323
+ {
324
+ strategy,
325
+ routeCount: options.routeCount,
326
+ staticRoutes: staticRoutes.size,
327
+ trieThreshold: options.trieThreshold,
328
+ },
329
+ "[VIBE] Route matching strategy initialized",
324
330
  );
325
331
 
326
332
  if (callback) callback();
package/vibe.d.ts CHANGED
@@ -237,10 +237,8 @@ export interface VibeRequest extends IncomingMessage {
237
237
  body: Record<string, any>;
238
238
  /** Uploaded files array (if multipart/form-data) */
239
239
  files?: UploadedFile[];
240
- /** Client IP address */
240
+ /** Real client IP — first entry from x-forwarded-for, x-real-ip, or socket address */
241
241
  ip?: string;
242
- /** Detailed client IP info */
243
- fullIp?: string;
244
242
  /** Automatically generated UUID for the request lifecycle */
245
243
  id: string;
246
244
  /** Context-bound logger automatically stamped with the req.id constraint */
@@ -302,9 +300,58 @@ export type Interceptor = (
302
300
  res: VibeResponse,
303
301
  ) => boolean | void | Promise<boolean | void>;
304
302
 
303
+ /**
304
+ * Scoped app interface passed to register() plugin callbacks.
305
+ * A subset of VibeApp — excludes server-level methods that make no sense
306
+ * inside an encapsulated plugin (listen, logRoutes, setPublicFolder, include).
307
+ */
308
+ export interface ScopedVibeApp {
309
+ get: RouteRegistrar;
310
+ post: RouteRegistrar;
311
+ put: RouteRegistrar;
312
+ del: RouteRegistrar;
313
+ patch: RouteRegistrar;
314
+ head: RouteRegistrar;
315
+
316
+ /** Register a global interceptor within this plugin scope */
317
+ plugin: (interceptor: Interceptor) => void;
318
+
319
+ /** Register a nested plugin */
320
+ register: (fn: PluginCallback, opts?: RegisterOptions) => Promise<void>;
321
+
322
+ /** Decorate the app with a custom property */
323
+ decorate: (name: string, value: any) => void;
324
+
325
+ /** Decorate request objects with a custom property */
326
+ decorateRequest: (name: string, value: any) => void;
327
+
328
+ /** Decorate response objects with a custom property */
329
+ decorateReply: (name: string, value: any) => void;
330
+
331
+ /** Override the error handler for this plugin scope */
332
+ setErrorHandler: (
333
+ fn: (error: Error, req: VibeRequest, res: VibeResponse) => void,
334
+ ) => void;
335
+
336
+ /** Structured logger — app.log.info(), .warn(), .error() etc. */
337
+ log: LoggerAPI;
338
+
339
+ /** Alias for log */
340
+ logger: LoggerAPI;
341
+
342
+ /** Legacy colorized string logger */
343
+ logLegacy: (
344
+ value: any,
345
+ typeOrColor?: ColorName | "info" | "error" | "warn" | "req",
346
+ ) => void;
347
+
348
+ /** Any decorators registered via decorate() are available as direct properties */
349
+ [key: string]: any;
350
+ }
351
+
305
352
  /** Plugin callback function (Fastify-style) */
306
353
  export type PluginCallback = (
307
- app: VibeApp,
354
+ app: ScopedVibeApp,
308
355
  opts: RegisterOptions,
309
356
  ) => void | Promise<void>;
310
357
 
@@ -391,11 +438,23 @@ export interface RouterAPI {
391
438
  head: RouteRegistrar;
392
439
 
393
440
  /**
394
- * Log helper supporting native colors and Vibe-stylized log levels
395
- * @param value The message or object to log
396
- * @param typeOrColor Optional color name (e.g. 'green') or level ('info', 'warn', 'error', 'req')
441
+ * Pino/Fastify-compatible structured logger.
442
+ * Available inside both `app.register()` plugins and `app.include()` sub-routers.
443
+ * @example
444
+ * api.log.info("Route registered");
445
+ * api.log.warn({ userId: 1 }, "Slow query");
446
+ */
447
+ log: LoggerAPI;
448
+
449
+ /** Alias for `log` — consistent with `app.logger` */
450
+ logger: LoggerAPI;
451
+
452
+ /**
453
+ * Legacy colorized string logger (plain terminal output).
454
+ * @param value The message to log
455
+ * @param typeOrColor Optional color name (e.g. 'green', 'red')
397
456
  */
398
- log: (
457
+ logLegacy: (
399
458
  value: any,
400
459
  typeOrColor?: ColorName | "info" | "error" | "warn" | "req",
401
460
  ) => void;
@@ -487,6 +546,17 @@ export interface VibeApp extends RouterAPI {
487
546
 
488
547
  /** Alias for `app.log` */
489
548
  logger: LoggerAPI;
549
+
550
+ /**
551
+ * Legacy colorized string logger (plain terminal output).
552
+ * Bypasses the Pino JSON interface — for simple dev-time messages.
553
+ * @param value The message to log
554
+ * @param typeOrColor Optional color name (e.g. 'green', 'red')
555
+ */
556
+ logLegacy: (
557
+ value: any,
558
+ typeOrColor?: ColorName | "info" | "error" | "warn" | "req",
559
+ ) => void;
490
560
  }
491
561
 
492
562
  /**
@@ -759,11 +829,3 @@ export function adapt(
759
829
  mw: (req: any, res: any, next: (err?: any) => void) => void,
760
830
  ): Interceptor;
761
831
 
762
- /**
763
- * Adapt multiple Express middlewares at once.
764
- * @param middlewares - Express middleware functions
765
- * @returns Array of Vibe-compatible interceptors
766
- */
767
- export function adaptAll(
768
- ...middlewares: Array<(req: any, res: any, next: (err?: any) => void) => void>
769
- ): Interceptor[];
package/vibe.js CHANGED
@@ -37,7 +37,6 @@ function pathToRegex(path) {
37
37
  * size: number
38
38
  * }>,
39
39
  * ip?: string,
40
- * fullIp?: string
41
40
  * }} VibeRequest
42
41
  */
43
42
 
@@ -430,7 +429,10 @@ const vibe = (config = {}) => {
430
429
  decorateRequest,
431
430
  decorateReply,
432
431
  register,
433
- log,
432
+ log: appLogger, // Structured logger (api.log.info / warn / error etc.)
433
+ logger: appLogger, // Alias — consistent with root app.logger
434
+ logLegacy: log, // Legacy colorized string logger (api.logLegacy(msg, color))
435
+ setErrorHandler: (fn) => { options.errorHandler = fn; },
434
436
  // Expose decorators
435
437
  ...options.decorators,
436
438
  };
@@ -486,7 +488,9 @@ const vibe = (config = {}) => {
486
488
  del: wrap("DELETE"),
487
489
  patch: wrap("PATCH"),
488
490
  head: wrap("HEAD"),
489
- log,
491
+ log: appLogger, // Structured logger — consistent with app.log
492
+ logger: appLogger, // Alias
493
+ logLegacy: log, // Legacy colorized string logger
490
494
  plugin,
491
495
  };
492
496
  }