@veloxts/core 0.7.0 → 0.7.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @veloxts/core
2
2
 
3
+ ## 0.7.2
4
+
5
+ ### Patch Changes
6
+
7
+ - chore(auth,core,create,cli,client,orm,mcp,router,validation,web): simplify code for clarity and maintainability
8
+
9
+ ## 0.7.1
10
+
11
+ ### Patch Changes
12
+
13
+ - security audit, bumps dependency packages
14
+
3
15
  ## 0.7.0
4
16
 
5
17
  ### Minor Changes
package/dist/app.d.ts CHANGED
@@ -79,20 +79,12 @@ export declare class VeloxApp {
79
79
  */
80
80
  get address(): string | null;
81
81
  /**
82
- * Async initialization (called by factory function)
83
- *
84
- * @internal - This method is public for factory access but should not be called directly.
85
- * Use createVeloxApp() instead.
86
- */
87
- initialize(): Promise<void>;
88
- /**
89
- * Sets up request context decorator
90
- *
91
- * Adds `request.context` property to all requests via onRequest hook
82
+ * Async initialization hook for factory function.
83
+ * Empty because Fastify's ready() must be called in start() after plugin registration.
92
84
  *
93
85
  * @internal
94
86
  */
95
- private _setupContext;
87
+ initialize(): Promise<void>;
96
88
  /**
97
89
  * Sets up global error handling
98
90
  *
@@ -101,12 +93,6 @@ export declare class VeloxApp {
101
93
  * @internal
102
94
  */
103
95
  private _setupErrorHandling;
104
- /**
105
- * Sets up graceful shutdown handlers for process signals
106
- *
107
- * @internal
108
- */
109
- private _setupGracefulShutdown;
110
96
  /**
111
97
  * Registers a plugin with the application
112
98
  *
@@ -293,5 +279,6 @@ export declare function veloxApp(config?: VeloxAppConfig): Promise<VeloxApp>;
293
279
  *
294
280
  * const app = await velox({ port: 3030 });
295
281
  * ```
282
+ * @deprecated Use veloxApp() instead.
296
283
  */
297
284
  export declare const velox: typeof veloxApp;
package/dist/app.js CHANGED
@@ -13,6 +13,8 @@ import { registerStatic } from './plugins/static.js';
13
13
  import { printBanner } from './utils/banner.js';
14
14
  import { mergeConfig, validateConfig } from './utils/config.js';
15
15
  import { LifecycleManager } from './utils/lifecycle.js';
16
+ import { createLogger } from './utils/logger.js';
17
+ const log = createLogger('core');
16
18
  /**
17
19
  * Main VeloxTS application instance
18
20
  *
@@ -46,25 +48,18 @@ export class VeloxApp {
46
48
  * @internal
47
49
  */
48
50
  constructor(config) {
49
- // Merge user config with defaults and validate
50
51
  const merged = mergeConfig(config);
51
- // Validate and freeze configuration
52
52
  this._config = validateConfig(merged);
53
- // Create Fastify instance
54
- const fastifyOptions = {
53
+ this._server = fastify({
55
54
  logger: this._config.logger,
56
55
  ...this._config.fastify,
57
- };
58
- this._server = fastify(fastifyOptions);
59
- // Initialize lifecycle manager
56
+ });
60
57
  this._lifecycle = new LifecycleManager();
61
- // Set up context decorator
62
- this._setupContext();
63
- // Set up error handling
58
+ setupContextHook(this._server);
64
59
  this._setupErrorHandling();
65
- // Set up graceful shutdown
66
- this._setupGracefulShutdown();
67
- // Register request logger if enabled via environment
60
+ this._lifecycle.setupSignalHandlers(async () => {
61
+ await this.stop();
62
+ });
68
63
  if (process.env.VELOX_REQUEST_LOGGING === 'true') {
69
64
  this._server.register(requestLogger);
70
65
  }
@@ -108,24 +103,13 @@ export class VeloxApp {
108
103
  return this._address;
109
104
  }
110
105
  /**
111
- * Async initialization (called by factory function)
112
- *
113
- * @internal - This method is public for factory access but should not be called directly.
114
- * Use createVeloxApp() instead.
115
- */
116
- async initialize() {
117
- // Keep empty - ready() must be called in start() after plugins are registered
118
- // This is a Fastify constraint: plugins must be registered before ready()
119
- }
120
- /**
121
- * Sets up request context decorator
122
- *
123
- * Adds `request.context` property to all requests via onRequest hook
106
+ * Async initialization hook for factory function.
107
+ * Empty because Fastify's ready() must be called in start() after plugin registration.
124
108
  *
125
109
  * @internal
126
110
  */
127
- _setupContext() {
128
- setupContextHook(this._server);
111
+ async initialize() {
112
+ // Intentionally empty - ready() is called in start()
129
113
  }
130
114
  /**
131
115
  * Sets up global error handling
@@ -137,8 +121,7 @@ export class VeloxApp {
137
121
  _setupErrorHandling() {
138
122
  this._server.setErrorHandler(async (error, request, reply) => {
139
123
  try {
140
- // Handle ZodError (validation errors) - return 400
141
- if (error instanceof Error && error.name === 'ZodError' && 'issues' in error) {
124
+ if (error.name === 'ZodError' && 'issues' in error) {
142
125
  const zodError = error;
143
126
  return reply.status(400).send({
144
127
  error: 'ValidationError',
@@ -150,9 +133,7 @@ export class VeloxApp {
150
133
  })),
151
134
  });
152
135
  }
153
- // Handle Prisma unique constraint errors - return 409 Conflict
154
- if (error instanceof Error &&
155
- error.name === 'PrismaClientKnownRequestError' &&
136
+ if (error.name === 'PrismaClientKnownRequestError' &&
156
137
  'code' in error &&
157
138
  error.code === 'P2002') {
158
139
  const prismaError = error;
@@ -163,22 +144,21 @@ export class VeloxApp {
163
144
  statusCode: 409,
164
145
  });
165
146
  }
166
- // Only log server errors (5xx), not client errors (4xx)
167
- const statusCode = isVeloxError(error)
168
- ? error.statusCode
169
- : typeof error === 'object' && error !== null && 'statusCode' in error
170
- ? error.statusCode
171
- : 500;
147
+ let statusCode = 500;
148
+ if (isVeloxError(error)) {
149
+ statusCode = error.statusCode;
150
+ }
151
+ else if (typeof error === 'object' && error !== null && 'statusCode' in error) {
152
+ statusCode = error.statusCode;
153
+ }
172
154
  if (statusCode >= 500) {
173
155
  request.log.error(error);
174
156
  }
175
- // Handle VeloxError instances (fast path - most common case)
176
157
  if (isVeloxError(error)) {
177
158
  return reply.status(error.statusCode).send(error.toJSON());
178
159
  }
179
- // Handle other errors
180
- const message = error instanceof Error ? error.message : 'Internal Server Error';
181
- const name = error instanceof Error ? error.name : 'Error';
160
+ const message = error.message ?? 'Internal Server Error';
161
+ const name = error.name ?? 'Error';
182
162
  return reply.status(statusCode).send({
183
163
  error: name,
184
164
  message,
@@ -186,8 +166,7 @@ export class VeloxApp {
186
166
  });
187
167
  }
188
168
  catch (handlerError) {
189
- // Last resort error handling - prevents unhandled rejections
190
- console.error('Critical error in error handler:', handlerError);
169
+ log.error('Critical error in error handler:', handlerError);
191
170
  if (!reply.sent) {
192
171
  return reply.status(500).send({
193
172
  error: 'InternalServerError',
@@ -198,16 +177,6 @@ export class VeloxApp {
198
177
  }
199
178
  });
200
179
  }
201
- /**
202
- * Sets up graceful shutdown handlers for process signals
203
- *
204
- * @internal
205
- */
206
- _setupGracefulShutdown() {
207
- this._lifecycle.setupSignalHandlers(async () => {
208
- await this.stop();
209
- });
210
- }
211
180
  /**
212
181
  * Registers a plugin with the application
213
182
  *
@@ -237,17 +206,13 @@ export class VeloxApp {
237
206
  * ```
238
207
  */
239
208
  async register(plugin, options) {
240
- // Handle VeloxPlugin objects (with name, version, register)
241
209
  if (isVeloxPlugin(plugin)) {
242
- // Validate plugin metadata
243
210
  validatePluginMetadata(plugin);
244
- // Wrap plugin with fastify-plugin for proper encapsulation
245
211
  const wrappedPlugin = fp(plugin.register, {
246
212
  name: plugin.name,
247
213
  dependencies: plugin.dependencies,
248
214
  fastify: '5.x',
249
215
  });
250
- // Register with Fastify
251
216
  try {
252
217
  await this._server.register(wrappedPlugin, options ?? {});
253
218
  }
@@ -256,7 +221,6 @@ export class VeloxApp {
256
221
  }
257
222
  return;
258
223
  }
259
- // Handle FastifyPluginAsync functions (standard Fastify plugins)
260
224
  if (isFastifyPlugin(plugin)) {
261
225
  try {
262
226
  await this._server.register(plugin, options ?? {});
@@ -266,7 +230,6 @@ export class VeloxApp {
266
230
  }
267
231
  return;
268
232
  }
269
- // Invalid plugin type
270
233
  throw new VeloxError('Invalid plugin: must be a VeloxPlugin object or FastifyPluginAsync function', 500, 'INVALID_PLUGIN_TYPE');
271
234
  }
272
235
  /**
@@ -354,16 +317,13 @@ export class VeloxApp {
354
317
  }
355
318
  const startTime = performance.now();
356
319
  try {
357
- // Ensure Fastify is ready before listening (must be after plugin registration)
358
320
  await this._server.ready();
359
- // Start listening
360
321
  const address = await this._server.listen({
361
322
  port: this._config.port,
362
323
  host: this._config.host,
363
324
  });
364
325
  this._isRunning = true;
365
326
  this._address = address;
366
- // Print startup banner unless silent
367
327
  if (!options.silent) {
368
328
  printBanner(this._server, {
369
329
  address,
@@ -397,11 +357,8 @@ export class VeloxApp {
397
357
  throw new VeloxError('Server is not running', 500, 'SERVER_NOT_RUNNING');
398
358
  }
399
359
  try {
400
- // Execute shutdown handlers
401
360
  await this._lifecycle.executeShutdownHandlers();
402
- // Clean up signal handlers to prevent memory leaks in tests
403
361
  this._lifecycle.cleanupSignalHandlers();
404
- // Close server
405
362
  await this._server.close();
406
363
  this._isRunning = false;
407
364
  this._address = null;
@@ -483,5 +440,6 @@ export async function veloxApp(config = {}) {
483
440
  *
484
441
  * const app = await velox({ port: 3030 });
485
442
  * ```
443
+ * @deprecated Use veloxApp() instead.
486
444
  */
487
445
  export const velox = veloxApp;
package/dist/context.js CHANGED
@@ -38,17 +38,13 @@ export function createContext(request, reply) {
38
38
  * ```
39
39
  */
40
40
  export function isContext(value) {
41
- // Early return for non-objects
42
41
  if (typeof value !== 'object' || value === null) {
43
42
  return false;
44
43
  }
45
- // Check properties exist using 'in' operator
46
44
  if (!('request' in value) || !('reply' in value)) {
47
45
  return false;
48
46
  }
49
- // After 'in' checks, safely access properties
50
47
  const ctx = value;
51
- // Verify request and reply are non-null objects
52
48
  return (typeof ctx.request === 'object' &&
53
49
  ctx.request !== null &&
54
50
  typeof ctx.reply === 'object' &&
@@ -65,12 +61,7 @@ export function isContext(value) {
65
61
  * @internal
66
62
  */
67
63
  export function setupContextHook(server) {
68
- // Create context for each request via direct assignment
69
- // TypeScript's declaration merging provides type safety
70
64
  server.addHook('onRequest', async (request, reply) => {
71
- // Direct assignment is ~100-400ns faster than Object.defineProperty
72
- // We use a mutable type assertion here because this is the framework's
73
- // initialization code - the readonly constraint is for user code safety
74
65
  request.context = createContext(request, reply);
75
66
  });
76
67
  }
@@ -33,6 +33,17 @@ export type ErrorCode = keyof typeof ERROR_CATALOG;
33
33
  * Variables for template interpolation
34
34
  */
35
35
  export type InterpolationVars = Record<string, string | number | boolean | undefined>;
36
+ /**
37
+ * JSON representation of a VeloxFailure for API responses
38
+ */
39
+ interface VeloxFailureJson {
40
+ error: string;
41
+ message: string;
42
+ statusCode: number;
43
+ code: string;
44
+ fix?: string;
45
+ docs?: string;
46
+ }
36
47
  /**
37
48
  * Fluent error builder that provides catalog-driven errors
38
49
  * with optional customization.
@@ -111,14 +122,7 @@ export declare class VeloxFailure extends Error {
111
122
  /**
112
123
  * Convert to JSON for API responses
113
124
  */
114
- toJSON(): {
115
- error: string;
116
- message: string;
117
- statusCode: number;
118
- code: string;
119
- fix?: string;
120
- docs?: string;
121
- };
125
+ toJSON(): VeloxFailureJson;
122
126
  /**
123
127
  * Interpolate variables into a template string
124
128
  */
@@ -161,3 +165,4 @@ export declare function fail(code: string, vars?: InterpolationVars): VeloxFailu
161
165
  * @returns true if error is a VeloxFailure
162
166
  */
163
167
  export declare function isVeloxFailure(error: unknown): error is VeloxFailure;
168
+ export {};
@@ -54,7 +54,6 @@ export class VeloxFailure extends Error {
54
54
  this.code = code;
55
55
  this.statusCode = entry.statusCode;
56
56
  this.entry = entry;
57
- // Maintains proper stack trace
58
57
  if (Error.captureStackTrace) {
59
58
  Error.captureStackTrace(this, VeloxFailure);
60
59
  }
@@ -142,11 +141,9 @@ export class VeloxFailure extends Error {
142
141
  statusCode: this.statusCode,
143
142
  code: this.code,
144
143
  };
145
- // Include fix in development
146
144
  if (process.env.NODE_ENV !== 'production' && this.suggestion) {
147
145
  json.fix = this.suggestion;
148
146
  }
149
- // Always include docs URL
150
147
  if (this.docsUrl) {
151
148
  json.docs = this.docsUrl;
152
149
  }
@@ -6,7 +6,9 @@
6
6
  *
7
7
  * @module errors/formatter
8
8
  */
9
+ import { createLogger } from '../utils/logger.js';
9
10
  import { getErrorEntry } from './catalog.js';
11
+ const log = createLogger('core');
10
12
  // ============================================================================
11
13
  // ANSI Color Codes (for terminal output)
12
14
  // ============================================================================
@@ -260,15 +262,12 @@ export function formatErrorForApi(error, catalogCode) {
260
262
  statusCode: error.statusCode ?? entry?.statusCode ?? 500,
261
263
  code: catalogCode ?? error.code,
262
264
  };
263
- // Include field errors for validation
264
265
  if (error.fields) {
265
266
  response.fields = error.fields;
266
267
  }
267
- // Include fix suggestion in development
268
268
  if (process.env.NODE_ENV !== 'production' && entry?.fix) {
269
269
  response.fix = entry.fix.suggestion;
270
270
  }
271
- // Include docs link
272
271
  if (entry?.docsUrl) {
273
272
  response.docs = entry.docsUrl;
274
273
  }
@@ -298,7 +297,7 @@ export function formatErrorOneLine(error, catalogCode) {
298
297
  * @param catalogCode - Optional catalog error code
299
298
  */
300
299
  export function logError(error, catalogCode) {
301
- console.error(formatError(error, catalogCode));
300
+ log.error(formatError(error, catalogCode));
302
301
  }
303
302
  /**
304
303
  * Log a warning with pretty formatting
@@ -314,7 +313,7 @@ export function logWarning(message, suggestion) {
314
313
  lines.push(` ${color('→', 'gray')} ${suggestion}`);
315
314
  }
316
315
  lines.push('');
317
- console.warn(lines.join('\n'));
316
+ log.warn(lines.join('\n'));
318
317
  }
319
318
  /**
320
319
  * Log a deprecation warning
@@ -9,9 +9,6 @@
9
9
  *
10
10
  * @module errors
11
11
  */
12
- // Re-export catalog
13
12
  export { ERROR_CATALOG, ERROR_DOMAINS, getDocsUrl, getErrorEntry, getErrorsByDomain, isKnownErrorCode, } from './catalog.js';
14
- // Re-export fail() - the elegant error creation API
15
13
  export { fail, isVeloxFailure, VeloxFailure, } from './fail.js';
16
- // Re-export formatter
17
14
  export { extractErrorLocation, formatError, formatErrorForApi, formatErrorOneLine, logDeprecation, logError, logWarning, } from './formatter.js';
package/dist/errors.js CHANGED
@@ -3,10 +3,10 @@
3
3
  * Provides base error classes with HTTP status codes and discriminated unions
4
4
  * @module errors
5
5
  */
6
- // Import catalog for use in error classes
7
6
  import { ERROR_CATALOG } from './errors/catalog.js';
8
7
  import { formatError as _formatError } from './errors/formatter.js';
9
- // Re-export the enhanced error catalog, formatter, and fail()
8
+ import { createLogger } from './utils/logger.js';
9
+ const log = createLogger('core');
10
10
  export { ERROR_CATALOG, ERROR_DOMAINS, extractErrorLocation, fail, formatError, formatErrorForApi, formatErrorOneLine, getDocsUrl, getErrorEntry, getErrorsByDomain, isKnownErrorCode, isVeloxFailure, logDeprecation, logError, logWarning, VeloxFailure, } from './errors/index.js';
11
11
  /**
12
12
  * Type guard for validation error responses
@@ -79,7 +79,6 @@ export class VeloxError extends Error {
79
79
  this.name = 'VeloxError';
80
80
  this.statusCode = statusCode;
81
81
  this.code = code;
82
- // Look up catalog entry for enhanced error info
83
82
  if (code != null && String(code).startsWith('VELOX-')) {
84
83
  const entry = ERROR_CATALOG[code];
85
84
  if (entry) {
@@ -87,7 +86,6 @@ export class VeloxError extends Error {
87
86
  this.docsUrl = entry.docsUrl;
88
87
  }
89
88
  }
90
- // Maintains proper stack trace for where error was thrown (V8 only)
91
89
  if (Error.captureStackTrace) {
92
90
  Error.captureStackTrace(this, VeloxError);
93
91
  }
@@ -104,11 +102,9 @@ export class VeloxError extends Error {
104
102
  statusCode: this.statusCode,
105
103
  code: this.code,
106
104
  };
107
- // Include fix suggestion in development only
108
105
  if (process.env.NODE_ENV !== 'production' && this.fix) {
109
106
  response.fix = this.fix;
110
107
  }
111
- // Always include docs URL if available
112
108
  if (this.docsUrl) {
113
109
  response.docs = this.docsUrl;
114
110
  }
@@ -126,7 +122,7 @@ export class VeloxError extends Error {
126
122
  * Log this error with pretty formatting
127
123
  */
128
124
  log() {
129
- console.error(this.format());
125
+ log.error(this.format());
130
126
  }
131
127
  }
132
128
  /**
@@ -296,7 +292,7 @@ export class NotFoundError extends VeloxError {
296
292
  * ```
297
293
  */
298
294
  export function isVeloxError(error) {
299
- return error != null && error instanceof VeloxError;
295
+ return error instanceof VeloxError;
300
296
  }
301
297
  /**
302
298
  * Type guard to check if an error is a ValidationError
package/dist/index.d.ts CHANGED
@@ -32,3 +32,5 @@ export type { AuthContextExtension, CombineContexts, ContextExtension, CoreConte
32
32
  export type { CacheControl, StaticOptions } from './plugins/static.js';
33
33
  export { registerStatic } from './plugins/static.js';
34
34
  export { requestLogger } from './plugins/request-logger.js';
35
+ export type { Logger, LogLevel } from './utils/logger.js';
36
+ export { createLogger } from './utils/logger.js';
package/dist/index.js CHANGED
@@ -15,19 +15,13 @@
15
15
  * @module @veloxts/core
16
16
  */
17
17
  import { createRequire } from 'node:module';
18
- // Read version from package.json dynamically
19
18
  const require = createRequire(import.meta.url);
20
19
  const packageJson = require('../package.json');
21
20
  /** VeloxTS framework version */
22
21
  export const VELOX_VERSION = packageJson.version ?? '0.0.0-unknown';
23
22
  export { VeloxApp, velox, veloxApp } from './app.js';
24
23
  export { createContext, isContext, setupContextHook, setupTestContext } from './context.js';
25
- export { assertNever, ConfigurationError,
26
- // Elegant error creation API
27
- fail, isConfigurationError, isNotFoundError, isNotFoundErrorResponse, isValidationError, isValidationErrorResponse, isVeloxError, isVeloxFailure,
28
- // Developer experience utilities
29
- logDeprecation, logWarning, NotFoundError, ValidationError, VeloxError, VeloxFailure, } from './errors.js';
30
- // Plugin system
24
+ export { assertNever, ConfigurationError, fail, isConfigurationError, isNotFoundError, isNotFoundErrorResponse, isValidationError, isValidationErrorResponse, isVeloxError, isVeloxFailure, logDeprecation, logWarning, NotFoundError, ValidationError, VeloxError, VeloxFailure, } from './errors.js';
31
25
  export { definePlugin, isFastifyPlugin, isVeloxPlugin, validatePluginMetadata } from './plugin.js';
32
26
  export { isValidHost, isValidPort } from './utils/config.js';
33
27
  export { registerStatic } from './plugins/static.js';
@@ -35,3 +29,4 @@ export { registerStatic } from './plugins/static.js';
35
29
  // Request Logging (Development)
36
30
  // ============================================================================
37
31
  export { requestLogger } from './plugins/request-logger.js';
32
+ export { createLogger } from './utils/logger.js';
package/dist/plugin.js CHANGED
@@ -78,7 +78,6 @@ export function validatePluginMetadata(plugin) {
78
78
  if (!plugin.register || typeof plugin.register !== 'function') {
79
79
  throw new VeloxError(`Plugin "${plugin.name}" must have a register function`, 500, 'INVALID_PLUGIN_METADATA');
80
80
  }
81
- // Validate dependencies array if provided
82
81
  if (plugin.dependencies !== undefined) {
83
82
  if (!Array.isArray(plugin.dependencies)) {
84
83
  throw new VeloxError(`Plugin "${plugin.name}" dependencies must be an array`, 500, 'INVALID_PLUGIN_METADATA');
@@ -110,7 +109,6 @@ export function isVeloxPlugin(value) {
110
109
  if (typeof value !== 'object' || value === null) {
111
110
  return false;
112
111
  }
113
- // Use 'in' operator for safe property access without type assertions
114
112
  return ('name' in value &&
115
113
  'version' in value &&
116
114
  'register' in value &&
@@ -93,17 +93,13 @@ function formatDuration(ms) {
93
93
  * @param server - Fastify instance
94
94
  */
95
95
  async function requestLoggerPlugin(server) {
96
- // Skip if request logging not enabled via environment
97
96
  if (process.env.VELOX_REQUEST_LOGGING !== 'true') {
98
97
  return;
99
98
  }
100
- // Track request start time
101
99
  server.addHook('onRequest', async (request) => {
102
100
  request._veloxStartTime = performance.now();
103
101
  });
104
- // Log on response with timing
105
102
  server.addHook('onResponse', async (request, reply) => {
106
- // Wrap in try-catch to ensure logging never breaks request handling
107
103
  try {
108
104
  const startTime = request._veloxStartTime;
109
105
  const duration = startTime ? performance.now() - startTime : 0;
@@ -115,7 +111,7 @@ async function requestLoggerPlugin(server) {
115
111
  console.log(`${COLORS.dim}${timestamp}${COLORS.reset} ${color}${method}${COLORS.reset} ${url} ${color}${status}${COLORS.reset} ${COLORS.dim}${formatDuration(duration)}${COLORS.reset}`);
116
112
  }
117
113
  catch {
118
- // Silently fail - logging should never affect application behavior
114
+ // Logging failures must not affect request handling
119
115
  }
120
116
  });
121
117
  }
@@ -83,9 +83,7 @@ function buildCacheControl(options) {
83
83
  */
84
84
  export async function registerStatic(server, path, options = {}) {
85
85
  const { spa = false, prefix = '/', cache = {}, exclude = [], index = 'index.html' } = options;
86
- // Resolve to absolute path
87
86
  const absoluteRoot = resolve(process.cwd(), path);
88
- // Dynamically import @fastify/static (optional peer dependency)
89
87
  let fastifyStatic;
90
88
  try {
91
89
  const module = await import('@fastify/static');
@@ -94,9 +92,7 @@ export async function registerStatic(server, path, options = {}) {
94
92
  catch {
95
93
  throw new Error('To serve static files, please install @fastify/static:\n\n pnpm add @fastify/static');
96
94
  }
97
- // Build cache control header
98
95
  const cacheControl = buildCacheControl(cache);
99
- // Register static plugin
100
96
  await server.register(fastifyStatic, {
101
97
  root: absoluteRoot,
102
98
  prefix,
@@ -106,10 +102,8 @@ export async function registerStatic(server, path, options = {}) {
106
102
  res.setHeader('Cache-Control', cacheControl);
107
103
  },
108
104
  });
109
- // SPA fallback: serve index.html for non-file routes
110
105
  if (spa) {
111
106
  server.setNotFoundHandler(async (request, reply) => {
112
- // Skip excluded paths (API routes, etc.)
113
107
  for (const excludePath of exclude) {
114
108
  if (request.url.startsWith(excludePath)) {
115
109
  return reply.status(404).send({
@@ -119,7 +113,6 @@ export async function registerStatic(server, path, options = {}) {
119
113
  });
120
114
  }
121
115
  }
122
- // Skip requests for files (have extensions)
123
116
  if (/\.\w+$/.test(request.url)) {
124
117
  return reply.status(404).send({
125
118
  error: 'NotFound',
@@ -127,7 +120,6 @@ export async function registerStatic(server, path, options = {}) {
127
120
  statusCode: 404,
128
121
  });
129
122
  }
130
- // Serve index.html for SPA routes
131
123
  return reply.sendFile(index, absoluteRoot);
132
124
  });
133
125
  }
@@ -17,11 +17,9 @@ export function printBanner(server, options) {
17
17
  const { address, env, startTime } = options;
18
18
  const elapsed = Math.round(performance.now() - startTime);
19
19
  if (env === 'production') {
20
- // Production: single line, minimal, machine-parseable
21
20
  console.log(` VeloxTS v${VELOX_VERSION} | ${env} | ${address}`);
22
21
  return;
23
22
  }
24
- // Development: detailed banner with route information
25
23
  const routes = collectRoutes(server);
26
24
  console.log('');
27
25
  console.log(` ${pc.bold('VeloxTS')} v${VELOX_VERSION}`);
@@ -62,11 +60,7 @@ function formatMethod(method) {
62
60
  */
63
61
  function collectRoutes(server) {
64
62
  const routes = [];
65
- // Fastify exposes routes via printRoutes or we can iterate
66
- // Using the internal routes map after ready()
67
63
  const routesList = server.printRoutes({ commonPrefix: false });
68
- // Parse the route tree output
69
- // Format: "└── /api (GET, HEAD)\n └── /users (GET, POST, HEAD)"
70
64
  const lines = routesList.split('\n');
71
65
  for (const line of lines) {
72
66
  const match = line.match(/([/][^\s(]*)\s*\(([^)]+)\)/);
@@ -38,7 +38,7 @@ export function isValidPort(port) {
38
38
  * @returns true if host is valid (non-empty string), narrowing to ValidHost
39
39
  */
40
40
  export function isValidHost(host) {
41
- return typeof host === 'string' && host.length > 0;
41
+ return host.length > 0;
42
42
  }
43
43
  // ============================================================================
44
44
  // Configuration Functions
@@ -88,10 +88,9 @@ export function validateConfig(config) {
88
88
  if (!isValidHost(config.host)) {
89
89
  throw new Error('Host must be a non-empty string.');
90
90
  }
91
- // Return frozen config with branded types
92
91
  return Object.freeze({
93
- port: config.port, // Already narrowed to ValidPort by isValidPort
94
- host: config.host, // Already narrowed to ValidHost by isValidHost
92
+ port: config.port,
93
+ host: config.host,
95
94
  logger: config.logger,
96
95
  fastify: config.fastify,
97
96
  });
@@ -3,6 +3,8 @@
3
3
  * Handles graceful shutdown and cleanup
4
4
  * @module utils/lifecycle
5
5
  */
6
+ import { createLogger } from './logger.js';
7
+ const log = createLogger('core');
6
8
  /**
7
9
  * Manages graceful shutdown for the VeloxTS application
8
10
  *
@@ -32,12 +34,10 @@ export class LifecycleManager {
32
34
  * ```
33
35
  */
34
36
  addShutdownHandler(handler) {
35
- // Prevent memory leaks from unbounded growth
36
37
  if (this.shutdownHandlers.size >= LifecycleManager.MAX_SHUTDOWN_HANDLERS) {
37
38
  throw new Error(`Maximum number of shutdown handlers (${LifecycleManager.MAX_SHUTDOWN_HANDLERS}) exceeded. ` +
38
39
  'This may indicate a memory leak.');
39
40
  }
40
- // Set automatically prevents duplicates
41
41
  this.shutdownHandlers.add(handler);
42
42
  }
43
43
  /**
@@ -68,7 +68,7 @@ export class LifecycleManager {
68
68
  }
69
69
  catch (error) {
70
70
  // Log error but continue with other handlers
71
- console.error('Error during shutdown handler execution:', error);
71
+ log.error('Error during shutdown handler execution:', error);
72
72
  }
73
73
  }
74
74
  this.isShuttingDown = false;
@@ -85,23 +85,22 @@ export class LifecycleManager {
85
85
  */
86
86
  setupSignalHandlers(onShutdown) {
87
87
  const signals = ['SIGINT', 'SIGTERM'];
88
- signals.forEach((signal) => {
88
+ for (const signal of signals) {
89
89
  const handler = async () => {
90
- console.log(`\nReceived ${signal}, initiating graceful shutdown...`);
90
+ log.info(`\nReceived ${signal}, initiating graceful shutdown...`);
91
91
  try {
92
92
  await onShutdown();
93
- console.log('Graceful shutdown completed');
93
+ log.info('Graceful shutdown completed');
94
94
  process.exit(0);
95
95
  }
96
96
  catch (error) {
97
- console.error('Error during graceful shutdown:', error);
97
+ log.error('Error during graceful shutdown:', error);
98
98
  process.exit(1);
99
99
  }
100
100
  };
101
- // Store handler reference so it can be removed later
102
101
  this.signalHandlers.set(signal, handler);
103
102
  process.once(signal, handler);
104
- });
103
+ }
105
104
  }
106
105
  /**
107
106
  * Removes all signal handlers
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Structured Logger
3
+ *
4
+ * Minimal logging utility that respects VELOX_LOG_LEVEL environment variable.
5
+ * Replaces raw console.* calls in library code with namespaced, level-aware logging.
6
+ */
7
+ /** Supported log levels, ordered by verbosity. */
8
+ export type LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'debug';
9
+ /** Logger instance returned by createLogger. */
10
+ export interface Logger {
11
+ debug: (...args: unknown[]) => void;
12
+ info: (...args: unknown[]) => void;
13
+ warn: (...args: unknown[]) => void;
14
+ error: (...args: unknown[]) => void;
15
+ }
16
+ /**
17
+ * Create a namespaced logger that respects VELOX_LOG_LEVEL.
18
+ *
19
+ * @param namespace - Logger namespace (e.g. 'router', 'auth')
20
+ * @returns Logger with debug/info/warn/error methods
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * import { createLogger } from '@veloxts/core';
25
+ *
26
+ * const log = createLogger('router');
27
+ * log.info('Route registered:', method, path);
28
+ * log.warn('Deprecated route pattern detected');
29
+ * log.error('Failed to register route:', error);
30
+ * ```
31
+ */
32
+ export declare function createLogger(namespace: string): Logger;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Structured Logger
3
+ *
4
+ * Minimal logging utility that respects VELOX_LOG_LEVEL environment variable.
5
+ * Replaces raw console.* calls in library code with namespaced, level-aware logging.
6
+ */
7
+ const LEVEL_PRIORITY = {
8
+ silent: 0,
9
+ error: 1,
10
+ warn: 2,
11
+ info: 3,
12
+ debug: 4,
13
+ };
14
+ function getLogLevel() {
15
+ const env = (process.env.VELOX_LOG_LEVEL ?? 'warn').toLowerCase();
16
+ if (env in LEVEL_PRIORITY)
17
+ return env;
18
+ return 'warn';
19
+ }
20
+ /**
21
+ * Create a namespaced logger that respects VELOX_LOG_LEVEL.
22
+ *
23
+ * @param namespace - Logger namespace (e.g. 'router', 'auth')
24
+ * @returns Logger with debug/info/warn/error methods
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * import { createLogger } from '@veloxts/core';
29
+ *
30
+ * const log = createLogger('router');
31
+ * log.info('Route registered:', method, path);
32
+ * log.warn('Deprecated route pattern detected');
33
+ * log.error('Failed to register route:', error);
34
+ * ```
35
+ */
36
+ export function createLogger(namespace) {
37
+ const prefix = `[@veloxts/${namespace}]`;
38
+ function shouldLog(level) {
39
+ return LEVEL_PRIORITY[level] <= LEVEL_PRIORITY[getLogLevel()];
40
+ }
41
+ return {
42
+ debug: (...args) => {
43
+ if (shouldLog('debug'))
44
+ console.debug(prefix, ...args);
45
+ },
46
+ info: (...args) => {
47
+ if (shouldLog('info'))
48
+ console.info(prefix, ...args);
49
+ },
50
+ warn: (...args) => {
51
+ if (shouldLog('warn'))
52
+ console.warn(prefix, ...args);
53
+ },
54
+ error: (...args) => {
55
+ if (shouldLog('error'))
56
+ console.error(prefix, ...args);
57
+ },
58
+ };
59
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veloxts/core",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "Fastify wrapper and plugin system for VeloxTS framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "devDependencies": {
45
45
  "@fastify/static": "9.0.0",
46
- "@types/node": "25.1.0",
46
+ "@types/node": "25.2.3",
47
47
  "@vitest/coverage-v8": "4.0.18",
48
48
  "typescript": "5.9.3",
49
49
  "vitest": "4.0.18"