@veloxts/core 0.7.1 → 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 +6 -0
- package/dist/app.d.ts +4 -17
- package/dist/app.js +22 -66
- package/dist/context.js +0 -9
- package/dist/errors/fail.d.ts +13 -8
- package/dist/errors/fail.js +0 -3
- package/dist/errors/formatter.js +0 -3
- package/dist/errors/index.js +0 -3
- package/dist/errors.js +1 -7
- package/dist/index.js +1 -7
- package/dist/plugin.js +0 -2
- package/dist/plugins/request-logger.js +1 -5
- package/dist/plugins/static.js +0 -8
- package/dist/utils/banner.js +0 -6
- package/dist/utils/config.js +3 -4
- package/dist/utils/lifecycle.js +2 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
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
|
|
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
|
-
|
|
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
|
@@ -48,25 +48,18 @@ export class VeloxApp {
|
|
|
48
48
|
* @internal
|
|
49
49
|
*/
|
|
50
50
|
constructor(config) {
|
|
51
|
-
// Merge user config with defaults and validate
|
|
52
51
|
const merged = mergeConfig(config);
|
|
53
|
-
// Validate and freeze configuration
|
|
54
52
|
this._config = validateConfig(merged);
|
|
55
|
-
|
|
56
|
-
const fastifyOptions = {
|
|
53
|
+
this._server = fastify({
|
|
57
54
|
logger: this._config.logger,
|
|
58
55
|
...this._config.fastify,
|
|
59
|
-
};
|
|
60
|
-
this._server = fastify(fastifyOptions);
|
|
61
|
-
// Initialize lifecycle manager
|
|
56
|
+
});
|
|
62
57
|
this._lifecycle = new LifecycleManager();
|
|
63
|
-
|
|
64
|
-
this._setupContext();
|
|
65
|
-
// Set up error handling
|
|
58
|
+
setupContextHook(this._server);
|
|
66
59
|
this._setupErrorHandling();
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
this._lifecycle.setupSignalHandlers(async () => {
|
|
61
|
+
await this.stop();
|
|
62
|
+
});
|
|
70
63
|
if (process.env.VELOX_REQUEST_LOGGING === 'true') {
|
|
71
64
|
this._server.register(requestLogger);
|
|
72
65
|
}
|
|
@@ -110,24 +103,13 @@ export class VeloxApp {
|
|
|
110
103
|
return this._address;
|
|
111
104
|
}
|
|
112
105
|
/**
|
|
113
|
-
* Async initialization
|
|
114
|
-
*
|
|
115
|
-
* @internal - This method is public for factory access but should not be called directly.
|
|
116
|
-
* Use createVeloxApp() instead.
|
|
117
|
-
*/
|
|
118
|
-
async initialize() {
|
|
119
|
-
// Keep empty - ready() must be called in start() after plugins are registered
|
|
120
|
-
// This is a Fastify constraint: plugins must be registered before ready()
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Sets up request context decorator
|
|
124
|
-
*
|
|
125
|
-
* 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.
|
|
126
108
|
*
|
|
127
109
|
* @internal
|
|
128
110
|
*/
|
|
129
|
-
|
|
130
|
-
|
|
111
|
+
async initialize() {
|
|
112
|
+
// Intentionally empty - ready() is called in start()
|
|
131
113
|
}
|
|
132
114
|
/**
|
|
133
115
|
* Sets up global error handling
|
|
@@ -139,8 +121,7 @@ export class VeloxApp {
|
|
|
139
121
|
_setupErrorHandling() {
|
|
140
122
|
this._server.setErrorHandler(async (error, request, reply) => {
|
|
141
123
|
try {
|
|
142
|
-
|
|
143
|
-
if (error instanceof Error && error.name === 'ZodError' && 'issues' in error) {
|
|
124
|
+
if (error.name === 'ZodError' && 'issues' in error) {
|
|
144
125
|
const zodError = error;
|
|
145
126
|
return reply.status(400).send({
|
|
146
127
|
error: 'ValidationError',
|
|
@@ -152,9 +133,7 @@ export class VeloxApp {
|
|
|
152
133
|
})),
|
|
153
134
|
});
|
|
154
135
|
}
|
|
155
|
-
|
|
156
|
-
if (error instanceof Error &&
|
|
157
|
-
error.name === 'PrismaClientKnownRequestError' &&
|
|
136
|
+
if (error.name === 'PrismaClientKnownRequestError' &&
|
|
158
137
|
'code' in error &&
|
|
159
138
|
error.code === 'P2002') {
|
|
160
139
|
const prismaError = error;
|
|
@@ -165,22 +144,21 @@ export class VeloxApp {
|
|
|
165
144
|
statusCode: 409,
|
|
166
145
|
});
|
|
167
146
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
+
}
|
|
174
154
|
if (statusCode >= 500) {
|
|
175
155
|
request.log.error(error);
|
|
176
156
|
}
|
|
177
|
-
// Handle VeloxError instances (fast path - most common case)
|
|
178
157
|
if (isVeloxError(error)) {
|
|
179
158
|
return reply.status(error.statusCode).send(error.toJSON());
|
|
180
159
|
}
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
const name = error instanceof Error ? error.name : 'Error';
|
|
160
|
+
const message = error.message ?? 'Internal Server Error';
|
|
161
|
+
const name = error.name ?? 'Error';
|
|
184
162
|
return reply.status(statusCode).send({
|
|
185
163
|
error: name,
|
|
186
164
|
message,
|
|
@@ -188,7 +166,6 @@ export class VeloxApp {
|
|
|
188
166
|
});
|
|
189
167
|
}
|
|
190
168
|
catch (handlerError) {
|
|
191
|
-
// Last resort error handling - prevents unhandled rejections
|
|
192
169
|
log.error('Critical error in error handler:', handlerError);
|
|
193
170
|
if (!reply.sent) {
|
|
194
171
|
return reply.status(500).send({
|
|
@@ -200,16 +177,6 @@ export class VeloxApp {
|
|
|
200
177
|
}
|
|
201
178
|
});
|
|
202
179
|
}
|
|
203
|
-
/**
|
|
204
|
-
* Sets up graceful shutdown handlers for process signals
|
|
205
|
-
*
|
|
206
|
-
* @internal
|
|
207
|
-
*/
|
|
208
|
-
_setupGracefulShutdown() {
|
|
209
|
-
this._lifecycle.setupSignalHandlers(async () => {
|
|
210
|
-
await this.stop();
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
180
|
/**
|
|
214
181
|
* Registers a plugin with the application
|
|
215
182
|
*
|
|
@@ -239,17 +206,13 @@ export class VeloxApp {
|
|
|
239
206
|
* ```
|
|
240
207
|
*/
|
|
241
208
|
async register(plugin, options) {
|
|
242
|
-
// Handle VeloxPlugin objects (with name, version, register)
|
|
243
209
|
if (isVeloxPlugin(plugin)) {
|
|
244
|
-
// Validate plugin metadata
|
|
245
210
|
validatePluginMetadata(plugin);
|
|
246
|
-
// Wrap plugin with fastify-plugin for proper encapsulation
|
|
247
211
|
const wrappedPlugin = fp(plugin.register, {
|
|
248
212
|
name: plugin.name,
|
|
249
213
|
dependencies: plugin.dependencies,
|
|
250
214
|
fastify: '5.x',
|
|
251
215
|
});
|
|
252
|
-
// Register with Fastify
|
|
253
216
|
try {
|
|
254
217
|
await this._server.register(wrappedPlugin, options ?? {});
|
|
255
218
|
}
|
|
@@ -258,7 +221,6 @@ export class VeloxApp {
|
|
|
258
221
|
}
|
|
259
222
|
return;
|
|
260
223
|
}
|
|
261
|
-
// Handle FastifyPluginAsync functions (standard Fastify plugins)
|
|
262
224
|
if (isFastifyPlugin(plugin)) {
|
|
263
225
|
try {
|
|
264
226
|
await this._server.register(plugin, options ?? {});
|
|
@@ -268,7 +230,6 @@ export class VeloxApp {
|
|
|
268
230
|
}
|
|
269
231
|
return;
|
|
270
232
|
}
|
|
271
|
-
// Invalid plugin type
|
|
272
233
|
throw new VeloxError('Invalid plugin: must be a VeloxPlugin object or FastifyPluginAsync function', 500, 'INVALID_PLUGIN_TYPE');
|
|
273
234
|
}
|
|
274
235
|
/**
|
|
@@ -356,16 +317,13 @@ export class VeloxApp {
|
|
|
356
317
|
}
|
|
357
318
|
const startTime = performance.now();
|
|
358
319
|
try {
|
|
359
|
-
// Ensure Fastify is ready before listening (must be after plugin registration)
|
|
360
320
|
await this._server.ready();
|
|
361
|
-
// Start listening
|
|
362
321
|
const address = await this._server.listen({
|
|
363
322
|
port: this._config.port,
|
|
364
323
|
host: this._config.host,
|
|
365
324
|
});
|
|
366
325
|
this._isRunning = true;
|
|
367
326
|
this._address = address;
|
|
368
|
-
// Print startup banner unless silent
|
|
369
327
|
if (!options.silent) {
|
|
370
328
|
printBanner(this._server, {
|
|
371
329
|
address,
|
|
@@ -399,11 +357,8 @@ export class VeloxApp {
|
|
|
399
357
|
throw new VeloxError('Server is not running', 500, 'SERVER_NOT_RUNNING');
|
|
400
358
|
}
|
|
401
359
|
try {
|
|
402
|
-
// Execute shutdown handlers
|
|
403
360
|
await this._lifecycle.executeShutdownHandlers();
|
|
404
|
-
// Clean up signal handlers to prevent memory leaks in tests
|
|
405
361
|
this._lifecycle.cleanupSignalHandlers();
|
|
406
|
-
// Close server
|
|
407
362
|
await this._server.close();
|
|
408
363
|
this._isRunning = false;
|
|
409
364
|
this._address = null;
|
|
@@ -485,5 +440,6 @@ export async function veloxApp(config = {}) {
|
|
|
485
440
|
*
|
|
486
441
|
* const app = await velox({ port: 3030 });
|
|
487
442
|
* ```
|
|
443
|
+
* @deprecated Use veloxApp() instead.
|
|
488
444
|
*/
|
|
489
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
|
}
|
package/dist/errors/fail.d.ts
CHANGED
|
@@ -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 {};
|
package/dist/errors/fail.js
CHANGED
|
@@ -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
|
}
|
package/dist/errors/formatter.js
CHANGED
|
@@ -262,15 +262,12 @@ export function formatErrorForApi(error, catalogCode) {
|
|
|
262
262
|
statusCode: error.statusCode ?? entry?.statusCode ?? 500,
|
|
263
263
|
code: catalogCode ?? error.code,
|
|
264
264
|
};
|
|
265
|
-
// Include field errors for validation
|
|
266
265
|
if (error.fields) {
|
|
267
266
|
response.fields = error.fields;
|
|
268
267
|
}
|
|
269
|
-
// Include fix suggestion in development
|
|
270
268
|
if (process.env.NODE_ENV !== 'production' && entry?.fix) {
|
|
271
269
|
response.fix = entry.fix.suggestion;
|
|
272
270
|
}
|
|
273
|
-
// Include docs link
|
|
274
271
|
if (entry?.docsUrl) {
|
|
275
272
|
response.docs = entry.docsUrl;
|
|
276
273
|
}
|
package/dist/errors/index.js
CHANGED
|
@@ -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,12 +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
8
|
import { createLogger } from './utils/logger.js';
|
|
10
9
|
const log = createLogger('core');
|
|
11
|
-
// Re-export the enhanced error catalog, formatter, and fail()
|
|
12
10
|
export { ERROR_CATALOG, ERROR_DOMAINS, extractErrorLocation, fail, formatError, formatErrorForApi, formatErrorOneLine, getDocsUrl, getErrorEntry, getErrorsByDomain, isKnownErrorCode, isVeloxFailure, logDeprecation, logError, logWarning, VeloxFailure, } from './errors/index.js';
|
|
13
11
|
/**
|
|
14
12
|
* Type guard for validation error responses
|
|
@@ -81,7 +79,6 @@ export class VeloxError extends Error {
|
|
|
81
79
|
this.name = 'VeloxError';
|
|
82
80
|
this.statusCode = statusCode;
|
|
83
81
|
this.code = code;
|
|
84
|
-
// Look up catalog entry for enhanced error info
|
|
85
82
|
if (code != null && String(code).startsWith('VELOX-')) {
|
|
86
83
|
const entry = ERROR_CATALOG[code];
|
|
87
84
|
if (entry) {
|
|
@@ -89,7 +86,6 @@ export class VeloxError extends Error {
|
|
|
89
86
|
this.docsUrl = entry.docsUrl;
|
|
90
87
|
}
|
|
91
88
|
}
|
|
92
|
-
// Maintains proper stack trace for where error was thrown (V8 only)
|
|
93
89
|
if (Error.captureStackTrace) {
|
|
94
90
|
Error.captureStackTrace(this, VeloxError);
|
|
95
91
|
}
|
|
@@ -106,11 +102,9 @@ export class VeloxError extends Error {
|
|
|
106
102
|
statusCode: this.statusCode,
|
|
107
103
|
code: this.code,
|
|
108
104
|
};
|
|
109
|
-
// Include fix suggestion in development only
|
|
110
105
|
if (process.env.NODE_ENV !== 'production' && this.fix) {
|
|
111
106
|
response.fix = this.fix;
|
|
112
107
|
}
|
|
113
|
-
// Always include docs URL if available
|
|
114
108
|
if (this.docsUrl) {
|
|
115
109
|
response.docs = this.docsUrl;
|
|
116
110
|
}
|
|
@@ -298,7 +292,7 @@ export class NotFoundError extends VeloxError {
|
|
|
298
292
|
* ```
|
|
299
293
|
*/
|
|
300
294
|
export function isVeloxError(error) {
|
|
301
|
-
return error
|
|
295
|
+
return error instanceof VeloxError;
|
|
302
296
|
}
|
|
303
297
|
/**
|
|
304
298
|
* Type guard to check if an error is a ValidationError
|
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';
|
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
|
-
//
|
|
114
|
+
// Logging failures must not affect request handling
|
|
119
115
|
}
|
|
120
116
|
});
|
|
121
117
|
}
|
package/dist/plugins/static.js
CHANGED
|
@@ -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
|
}
|
package/dist/utils/banner.js
CHANGED
|
@@ -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*\(([^)]+)\)/);
|
package/dist/utils/config.js
CHANGED
|
@@ -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
|
|
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,
|
|
94
|
-
host: config.host,
|
|
92
|
+
port: config.port,
|
|
93
|
+
host: config.host,
|
|
95
94
|
logger: config.logger,
|
|
96
95
|
fastify: config.fastify,
|
|
97
96
|
});
|
package/dist/utils/lifecycle.js
CHANGED
|
@@ -34,12 +34,10 @@ export class LifecycleManager {
|
|
|
34
34
|
* ```
|
|
35
35
|
*/
|
|
36
36
|
addShutdownHandler(handler) {
|
|
37
|
-
// Prevent memory leaks from unbounded growth
|
|
38
37
|
if (this.shutdownHandlers.size >= LifecycleManager.MAX_SHUTDOWN_HANDLERS) {
|
|
39
38
|
throw new Error(`Maximum number of shutdown handlers (${LifecycleManager.MAX_SHUTDOWN_HANDLERS}) exceeded. ` +
|
|
40
39
|
'This may indicate a memory leak.');
|
|
41
40
|
}
|
|
42
|
-
// Set automatically prevents duplicates
|
|
43
41
|
this.shutdownHandlers.add(handler);
|
|
44
42
|
}
|
|
45
43
|
/**
|
|
@@ -87,7 +85,7 @@ export class LifecycleManager {
|
|
|
87
85
|
*/
|
|
88
86
|
setupSignalHandlers(onShutdown) {
|
|
89
87
|
const signals = ['SIGINT', 'SIGTERM'];
|
|
90
|
-
|
|
88
|
+
for (const signal of signals) {
|
|
91
89
|
const handler = async () => {
|
|
92
90
|
log.info(`\nReceived ${signal}, initiating graceful shutdown...`);
|
|
93
91
|
try {
|
|
@@ -100,10 +98,9 @@ export class LifecycleManager {
|
|
|
100
98
|
process.exit(1);
|
|
101
99
|
}
|
|
102
100
|
};
|
|
103
|
-
// Store handler reference so it can be removed later
|
|
104
101
|
this.signalHandlers.set(signal, handler);
|
|
105
102
|
process.once(signal, handler);
|
|
106
|
-
}
|
|
103
|
+
}
|
|
107
104
|
}
|
|
108
105
|
/**
|
|
109
106
|
* Removes all signal handlers
|