@veloxts/core 0.4.0 → 0.4.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/README.md +20 -1229
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,1255 +1,46 @@
|
|
|
1
1
|
# @veloxts/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Pre-Alpha Notice:** This framework is in early development (v0.4.x). APIs are subject to change. Not recommended for production use. Documentation may be incomplete or out of date.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## What is this?
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Core foundation package for the VeloxTS Framework, providing the Fastify wrapper, plugin system, dependency injection container, and base context.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
npm install @veloxts/core
|
|
11
|
-
# or
|
|
12
|
-
pnpm add @veloxts/core
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Quick Start
|
|
16
|
-
|
|
17
|
-
```typescript
|
|
18
|
-
import { veloxApp } from '@veloxts/core';
|
|
19
|
-
|
|
20
|
-
// Create application
|
|
21
|
-
const app = await veloxApp({
|
|
22
|
-
port: 3210,
|
|
23
|
-
host: '0.0.0.0',
|
|
24
|
-
logger: true,
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// Start server
|
|
28
|
-
await app.start();
|
|
29
|
-
console.log(`Server running on ${app.address}`);
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Core API
|
|
33
|
-
|
|
34
|
-
### `veloxApp(config?)`
|
|
35
|
-
|
|
36
|
-
Creates a new VeloxTS application instance with sensible defaults.
|
|
37
|
-
|
|
38
|
-
**Configuration Options:**
|
|
39
|
-
|
|
40
|
-
```typescript
|
|
41
|
-
interface VeloxAppConfig {
|
|
42
|
-
port?: number; // Port to listen on (default: 3000)
|
|
43
|
-
host?: string; // Host to bind to (default: '0.0.0.0')
|
|
44
|
-
logger?: boolean | object; // Enable logging (default: true in dev)
|
|
45
|
-
fastify?: FastifyOptions; // Additional Fastify options
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
**Example:**
|
|
50
|
-
|
|
51
|
-
```typescript
|
|
52
|
-
const app = await veloxApp({
|
|
53
|
-
port: 4000,
|
|
54
|
-
host: '127.0.0.1',
|
|
55
|
-
logger: {
|
|
56
|
-
level: 'info',
|
|
57
|
-
prettyPrint: true,
|
|
58
|
-
},
|
|
59
|
-
fastify: {
|
|
60
|
-
requestTimeout: 30000,
|
|
61
|
-
bodyLimit: 1048576 * 10, // 10MB
|
|
62
|
-
},
|
|
63
|
-
});
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
**Returns:** Promise resolving to `VeloxApp` instance
|
|
67
|
-
|
|
68
|
-
### VeloxApp Instance
|
|
69
|
-
|
|
70
|
-
The application instance provides methods for lifecycle management:
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
interface VeloxApp {
|
|
74
|
-
server: FastifyInstance; // Underlying Fastify server
|
|
75
|
-
config: VeloxAppConfig; // Application configuration
|
|
76
|
-
isRunning: boolean; // Server running state
|
|
77
|
-
address: string | null; // Server address if running
|
|
78
|
-
|
|
79
|
-
// Methods
|
|
80
|
-
start(): Promise<void>; // Start the server
|
|
81
|
-
stop(): Promise<void>; // Stop the server gracefully
|
|
82
|
-
register(plugin, options?): Promise<void>; // Register a plugin
|
|
83
|
-
beforeShutdown(handler): void; // Add shutdown handler
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
**Example:**
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
const app = await veloxApp();
|
|
91
|
-
|
|
92
|
-
// Register plugins
|
|
93
|
-
await app.register(databasePlugin);
|
|
94
|
-
await app.register(routerPlugin);
|
|
95
|
-
|
|
96
|
-
// Add shutdown hook
|
|
97
|
-
app.beforeShutdown(async () => {
|
|
98
|
-
console.log('Cleaning up resources...');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// Start server
|
|
102
|
-
await app.start();
|
|
103
|
-
console.log(`Server running at ${app.address}`);
|
|
104
|
-
|
|
105
|
-
// Graceful shutdown
|
|
106
|
-
process.on('SIGTERM', async () => {
|
|
107
|
-
await app.stop();
|
|
108
|
-
});
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
## Plugin System
|
|
112
|
-
|
|
113
|
-
VeloxTS's plugin system extends functionality while maintaining type safety and encapsulation.
|
|
114
|
-
|
|
115
|
-
### Defining Plugins
|
|
116
|
-
|
|
117
|
-
Use `definePlugin()` to create reusable plugins:
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
import { definePlugin } from '@veloxts/core';
|
|
121
|
-
import { PrismaClient } from '@prisma/client';
|
|
122
|
-
|
|
123
|
-
export const databasePlugin = definePlugin({
|
|
124
|
-
name: '@myapp/database',
|
|
125
|
-
version: '1.0.0',
|
|
126
|
-
async register(server, options) {
|
|
127
|
-
const db = new PrismaClient();
|
|
128
|
-
|
|
129
|
-
// Decorate server with database client
|
|
130
|
-
server.decorate('db', db);
|
|
131
|
-
|
|
132
|
-
// Add lifecycle hook
|
|
133
|
-
server.addHook('onClose', async () => {
|
|
134
|
-
await db.$disconnect();
|
|
135
|
-
});
|
|
136
|
-
},
|
|
137
|
-
});
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Extending Context
|
|
141
|
-
|
|
142
|
-
Use TypeScript declaration merging to extend the base context:
|
|
143
|
-
|
|
144
|
-
```typescript
|
|
145
|
-
import type { PrismaClient } from '@prisma/client';
|
|
146
|
-
|
|
147
|
-
declare module '@veloxts/core' {
|
|
148
|
-
interface BaseContext {
|
|
149
|
-
db: PrismaClient;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
Now `ctx.db` is available in all route handlers with full type safety.
|
|
155
|
-
|
|
156
|
-
### Registering Plugins
|
|
157
|
-
|
|
158
|
-
```typescript
|
|
159
|
-
// Register with app instance
|
|
160
|
-
await app.register(databasePlugin);
|
|
161
|
-
|
|
162
|
-
// Register with options
|
|
163
|
-
await app.register(databasePlugin, {
|
|
164
|
-
connectionString: process.env.DATABASE_URL,
|
|
165
|
-
});
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### Plugin Example: Database Integration
|
|
169
|
-
|
|
170
|
-
Complete example integrating Prisma:
|
|
171
|
-
|
|
172
|
-
```typescript
|
|
173
|
-
import { definePlugin, type BaseContext } from '@veloxts/core';
|
|
174
|
-
import { PrismaClient } from '@prisma/client';
|
|
175
|
-
|
|
176
|
-
// Define plugin
|
|
177
|
-
export const databasePlugin = definePlugin({
|
|
178
|
-
name: '@myapp/database',
|
|
179
|
-
version: '1.0.0',
|
|
180
|
-
async register(server, options) {
|
|
181
|
-
const prisma = new PrismaClient({
|
|
182
|
-
log: options?.log || ['error'],
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// Test connection
|
|
186
|
-
await prisma.$connect();
|
|
187
|
-
|
|
188
|
-
// Decorate Fastify instance
|
|
189
|
-
server.decorate('db', prisma);
|
|
190
|
-
|
|
191
|
-
// Graceful shutdown
|
|
192
|
-
server.addHook('onClose', async () => {
|
|
193
|
-
await prisma.$disconnect();
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
console.log('Database connected');
|
|
197
|
-
},
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
// Extend context
|
|
201
|
-
declare module '@veloxts/core' {
|
|
202
|
-
interface BaseContext {
|
|
203
|
-
db: PrismaClient;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Use in app
|
|
208
|
-
const app = await veloxApp();
|
|
209
|
-
await app.register(databasePlugin);
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
## Context System
|
|
213
|
-
|
|
214
|
-
The context object provides request-scoped state accessible throughout the request lifecycle.
|
|
215
|
-
|
|
216
|
-
### Base Context
|
|
217
|
-
|
|
218
|
-
Every request has a base context:
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
interface BaseContext {
|
|
222
|
-
request: FastifyRequest; // Fastify request object
|
|
223
|
-
reply: FastifyReply; // Fastify reply object
|
|
224
|
-
}
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
### Accessing Context
|
|
228
|
-
|
|
229
|
-
Context is available in route handlers:
|
|
230
|
-
|
|
231
|
-
```typescript
|
|
232
|
-
app.server.get('/users', async (request, reply) => {
|
|
233
|
-
// Access base context
|
|
234
|
-
const { request: req, reply: rep } = request.context;
|
|
235
|
-
|
|
236
|
-
// Access plugin-extended properties
|
|
237
|
-
const users = await request.context.db.user.findMany();
|
|
238
|
-
|
|
239
|
-
return users;
|
|
240
|
-
});
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
### Context in Procedures
|
|
244
|
-
|
|
245
|
-
When using `@veloxts/router`, context is passed to procedure handlers:
|
|
246
|
-
|
|
247
|
-
```typescript
|
|
248
|
-
import { procedure } from '@veloxts/router';
|
|
249
|
-
|
|
250
|
-
const getUsers = procedure()
|
|
251
|
-
.query(async ({ ctx }) => {
|
|
252
|
-
// ctx has type BaseContext with extensions
|
|
253
|
-
return ctx.db.user.findMany();
|
|
254
|
-
});
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
## Error Handling
|
|
258
|
-
|
|
259
|
-
VeloxTS provides structured error classes for consistent API responses.
|
|
260
|
-
|
|
261
|
-
### Error Classes
|
|
262
|
-
|
|
263
|
-
```typescript
|
|
264
|
-
import {
|
|
265
|
-
VeloxError,
|
|
266
|
-
ValidationError,
|
|
267
|
-
NotFoundError,
|
|
268
|
-
UnauthorizedError,
|
|
269
|
-
ForbiddenError,
|
|
270
|
-
} from '@veloxts/core';
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
### `VeloxError`
|
|
274
|
-
|
|
275
|
-
Base error class with status code:
|
|
276
|
-
|
|
277
|
-
```typescript
|
|
278
|
-
throw new VeloxError('Something went wrong', 500);
|
|
279
|
-
|
|
280
|
-
// Response:
|
|
281
|
-
// {
|
|
282
|
-
// "error": "Something went wrong",
|
|
283
|
-
// "statusCode": 500
|
|
284
|
-
// }
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
### `ValidationError`
|
|
288
|
-
|
|
289
|
-
Validation errors with field-level details:
|
|
290
|
-
|
|
291
|
-
```typescript
|
|
292
|
-
throw new ValidationError('Invalid input', {
|
|
293
|
-
email: 'Must be a valid email address',
|
|
294
|
-
age: 'Must be at least 18',
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
// Response:
|
|
298
|
-
// {
|
|
299
|
-
// "error": "Invalid input",
|
|
300
|
-
// "statusCode": 400,
|
|
301
|
-
// "fields": {
|
|
302
|
-
// "email": "Must be a valid email address",
|
|
303
|
-
// "age": "Must be at least 18"
|
|
304
|
-
// }
|
|
305
|
-
// }
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
### `NotFoundError`
|
|
309
|
-
|
|
310
|
-
Resource not found errors:
|
|
311
|
-
|
|
312
|
-
```typescript
|
|
313
|
-
throw new NotFoundError('User', userId);
|
|
314
|
-
|
|
315
|
-
// Response:
|
|
316
|
-
// {
|
|
317
|
-
// "error": "User not found",
|
|
318
|
-
// "statusCode": 404,
|
|
319
|
-
// "resource": "User",
|
|
320
|
-
// "id": "123"
|
|
321
|
-
// }
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
### `UnauthorizedError`
|
|
325
|
-
|
|
326
|
-
Authentication required:
|
|
327
|
-
|
|
328
|
-
```typescript
|
|
329
|
-
throw new UnauthorizedError('Invalid credentials');
|
|
330
|
-
|
|
331
|
-
// Response:
|
|
332
|
-
// {
|
|
333
|
-
// "error": "Invalid credentials",
|
|
334
|
-
// "statusCode": 401
|
|
335
|
-
// }
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
### `ForbiddenError`
|
|
339
|
-
|
|
340
|
-
Insufficient permissions:
|
|
341
|
-
|
|
342
|
-
```typescript
|
|
343
|
-
throw new ForbiddenError('Insufficient permissions');
|
|
344
|
-
|
|
345
|
-
// Response:
|
|
346
|
-
// {
|
|
347
|
-
// "error": "Insufficient permissions",
|
|
348
|
-
// "statusCode": 403
|
|
349
|
-
// }
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
### Error Handler Example
|
|
353
|
-
|
|
354
|
-
```typescript
|
|
355
|
-
import { VeloxError, NotFoundError } from '@veloxts/core';
|
|
356
|
-
|
|
357
|
-
app.server.get('/users/:id', async (request, reply) => {
|
|
358
|
-
try {
|
|
359
|
-
const user = await request.context.db.user.findUnique({
|
|
360
|
-
where: { id: request.params.id },
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
if (!user) {
|
|
364
|
-
throw new NotFoundError('User', request.params.id);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return user;
|
|
368
|
-
} catch (error) {
|
|
369
|
-
if (error instanceof VeloxError) {
|
|
370
|
-
// VeloxError is automatically handled by Fastify
|
|
371
|
-
throw error;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// Handle unexpected errors
|
|
375
|
-
throw new VeloxError('Internal server error', 500);
|
|
376
|
-
}
|
|
377
|
-
});
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
## Configuration
|
|
381
|
-
|
|
382
|
-
### Default Configuration
|
|
383
|
-
|
|
384
|
-
```typescript
|
|
385
|
-
{
|
|
386
|
-
port: 3210,
|
|
387
|
-
host: '0.0.0.0',
|
|
388
|
-
logger: process.env.NODE_ENV !== 'production',
|
|
389
|
-
}
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
### Environment-Based Configuration
|
|
393
|
-
|
|
394
|
-
```typescript
|
|
395
|
-
const app = await veloxApp({
|
|
396
|
-
port: Number(process.env.PORT) || 3210,
|
|
397
|
-
host: process.env.HOST || '0.0.0.0',
|
|
398
|
-
logger: process.env.NODE_ENV !== 'production',
|
|
399
|
-
fastify: {
|
|
400
|
-
trustProxy: process.env.TRUST_PROXY === 'true',
|
|
401
|
-
},
|
|
402
|
-
});
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
### Production Configuration
|
|
406
|
-
|
|
407
|
-
```typescript
|
|
408
|
-
const app = await veloxApp({
|
|
409
|
-
port: 3210,
|
|
410
|
-
host: '0.0.0.0',
|
|
411
|
-
logger: {
|
|
412
|
-
level: 'warn',
|
|
413
|
-
prettyPrint: false,
|
|
414
|
-
},
|
|
415
|
-
fastify: {
|
|
416
|
-
trustProxy: true,
|
|
417
|
-
requestTimeout: 30000,
|
|
418
|
-
bodyLimit: 1048576, // 1MB
|
|
419
|
-
disableRequestLogging: true,
|
|
420
|
-
},
|
|
421
|
-
});
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
## Lifecycle Management
|
|
425
|
-
|
|
426
|
-
### Graceful Shutdown
|
|
427
|
-
|
|
428
|
-
```typescript
|
|
429
|
-
const app = await veloxApp();
|
|
430
|
-
|
|
431
|
-
// Add custom shutdown handlers
|
|
432
|
-
app.beforeShutdown(async () => {
|
|
433
|
-
console.log('Cleaning up resources...');
|
|
434
|
-
await database.disconnect();
|
|
435
|
-
await cache.flush();
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
// Handle SIGTERM (e.g., from Kubernetes, Docker)
|
|
439
|
-
process.on('SIGTERM', async () => {
|
|
440
|
-
console.log('SIGTERM received, shutting down gracefully...');
|
|
441
|
-
await app.stop();
|
|
442
|
-
process.exit(0);
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
// Handle SIGINT (Ctrl+C)
|
|
446
|
-
process.on('SIGINT', async () => {
|
|
447
|
-
console.log('SIGINT received, shutting down gracefully...');
|
|
448
|
-
await app.stop();
|
|
449
|
-
process.exit(0);
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
await app.start();
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
### Startup and Shutdown Hooks
|
|
456
|
-
|
|
457
|
-
Plugins can register lifecycle hooks:
|
|
458
|
-
|
|
459
|
-
```typescript
|
|
460
|
-
export const lifecyclePlugin = definePlugin({
|
|
461
|
-
name: 'lifecycle',
|
|
462
|
-
version: '1.0.0',
|
|
463
|
-
async register(server, options) {
|
|
464
|
-
// Called when server starts listening
|
|
465
|
-
server.addHook('onListen', async () => {
|
|
466
|
-
console.log('Server is listening');
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
// Called when server is closing
|
|
470
|
-
server.addHook('onClose', async () => {
|
|
471
|
-
console.log('Server is closing');
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
// Called for each request
|
|
475
|
-
server.addHook('onRequest', async (request, reply) => {
|
|
476
|
-
console.log(`Request: ${request.method} ${request.url}`);
|
|
477
|
-
});
|
|
478
|
-
},
|
|
479
|
-
});
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
## Advanced Usage
|
|
483
|
-
|
|
484
|
-
### Custom Fastify Instance
|
|
485
|
-
|
|
486
|
-
For advanced scenarios, you can pass a custom Fastify instance:
|
|
487
|
-
|
|
488
|
-
```typescript
|
|
489
|
-
import Fastify from 'fastify';
|
|
490
|
-
|
|
491
|
-
const fastify = Fastify({
|
|
492
|
-
logger: true,
|
|
493
|
-
requestIdHeader: 'x-request-id',
|
|
494
|
-
trustProxy: true,
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
const app = await veloxApp({ fastify });
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
### Accessing Fastify Directly
|
|
501
|
-
|
|
502
|
-
The underlying Fastify instance is available via `app.server`:
|
|
503
|
-
|
|
504
|
-
```typescript
|
|
505
|
-
const app = await veloxApp();
|
|
506
|
-
|
|
507
|
-
// Add Fastify plugins
|
|
508
|
-
await app.server.register(fastifyCors, {
|
|
509
|
-
origin: true,
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
// Add raw routes
|
|
513
|
-
app.server.get('/custom', async (request, reply) => {
|
|
514
|
-
return { message: 'Custom route' };
|
|
515
|
-
});
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
## Practical Examples
|
|
519
|
-
|
|
520
|
-
### Complete Application Setup
|
|
521
|
-
|
|
522
|
-
```typescript
|
|
523
|
-
import { veloxApp } from '@veloxts/core';
|
|
524
|
-
import { createDatabasePlugin } from '@veloxts/orm';
|
|
525
|
-
import { registerRestRoutes } from '@veloxts/router';
|
|
526
|
-
import { PrismaClient } from '@prisma/client';
|
|
527
|
-
import { userProcedures } from './procedures/users';
|
|
528
|
-
|
|
529
|
-
// Initialize
|
|
530
|
-
const prisma = new PrismaClient();
|
|
531
|
-
const app = await veloxApp({
|
|
532
|
-
port: Number(process.env.PORT) || 3210,
|
|
533
|
-
logger: true,
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
// Register database plugin
|
|
537
|
-
await app.register(createDatabasePlugin({ client: prisma }));
|
|
538
|
-
|
|
539
|
-
// Register API routes
|
|
540
|
-
await registerRestRoutes(app.server, {
|
|
541
|
-
prefix: '/api',
|
|
542
|
-
procedures: {
|
|
543
|
-
users: userProcedures,
|
|
544
|
-
},
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
// Graceful shutdown
|
|
548
|
-
const shutdown = async () => {
|
|
549
|
-
await app.stop();
|
|
550
|
-
process.exit(0);
|
|
551
|
-
};
|
|
552
|
-
|
|
553
|
-
process.on('SIGTERM', shutdown);
|
|
554
|
-
process.on('SIGINT', shutdown);
|
|
555
|
-
|
|
556
|
-
// Start server
|
|
557
|
-
await app.start();
|
|
558
|
-
console.log(`Server running at ${app.address}`);
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
## Dependency Injection
|
|
562
|
-
|
|
563
|
-
VeloxTS provides a powerful, type-safe dependency injection container inspired by Angular and NestJS. The DI system enables automatic constructor injection, lifecycle management, and clean separation of concerns.
|
|
564
|
-
|
|
565
|
-
### Overview
|
|
566
|
-
|
|
567
|
-
The DI container manages service creation, dependency resolution, and lifecycle:
|
|
568
|
-
|
|
569
|
-
- **Automatic constructor injection** via TypeScript decorators
|
|
570
|
-
- **Multiple provider types**: classes, factories, values, aliases
|
|
571
|
-
- **Lifecycle scopes**: singleton, transient, request-scoped
|
|
572
|
-
- **Type safety**: Full TypeScript inference without code generation
|
|
573
|
-
- **Circular dependency detection**
|
|
574
|
-
- **Fastify integration** for request-scoped services
|
|
575
|
-
|
|
576
|
-
### Accessing the Container
|
|
577
|
-
|
|
578
|
-
Every VeloxApp instance has a DI container available:
|
|
579
|
-
|
|
580
|
-
```typescript
|
|
581
|
-
const app = await veloxApp();
|
|
582
|
-
|
|
583
|
-
// Access the container
|
|
584
|
-
app.container.register({ /* ... */ });
|
|
585
|
-
const service = app.container.resolve(MyService);
|
|
586
|
-
```
|
|
587
|
-
|
|
588
|
-
### Tokens
|
|
589
|
-
|
|
590
|
-
Tokens are unique identifiers for services. VeloxTS supports three token types:
|
|
591
|
-
|
|
592
|
-
#### Class Tokens
|
|
593
|
-
|
|
594
|
-
Use the class itself as a token:
|
|
9
|
+
## Part of @veloxts/velox
|
|
595
10
|
|
|
596
|
-
|
|
597
|
-
@Injectable()
|
|
598
|
-
class UserService {
|
|
599
|
-
getUsers() { /* ... */ }
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
// Register with class token
|
|
603
|
-
app.container.register({
|
|
604
|
-
provide: UserService,
|
|
605
|
-
useClass: UserService
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
// Resolve with class token
|
|
609
|
-
const userService = app.container.resolve(UserService);
|
|
610
|
-
```
|
|
611
|
-
|
|
612
|
-
#### String Tokens
|
|
613
|
-
|
|
614
|
-
Use string literals for named services:
|
|
615
|
-
|
|
616
|
-
```typescript
|
|
617
|
-
import { token } from '@veloxts/core';
|
|
618
|
-
|
|
619
|
-
interface DatabaseClient {
|
|
620
|
-
query(sql: string): Promise<unknown>;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
const DATABASE = token<DatabaseClient>('DATABASE');
|
|
624
|
-
|
|
625
|
-
app.container.register({
|
|
626
|
-
provide: DATABASE,
|
|
627
|
-
useFactory: () => createDatabaseClient()
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
const db = app.container.resolve(DATABASE);
|
|
631
|
-
```
|
|
632
|
-
|
|
633
|
-
#### Symbol Tokens
|
|
634
|
-
|
|
635
|
-
Use symbols for guaranteed uniqueness:
|
|
636
|
-
|
|
637
|
-
```typescript
|
|
638
|
-
import { token } from '@veloxts/core';
|
|
639
|
-
|
|
640
|
-
interface Logger {
|
|
641
|
-
log(message: string): void;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
const LOGGER = token.symbol<Logger>('Logger');
|
|
645
|
-
|
|
646
|
-
app.container.register({
|
|
647
|
-
provide: LOGGER,
|
|
648
|
-
useClass: ConsoleLogger
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
const logger = app.container.resolve(LOGGER);
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
### Provider Types
|
|
655
|
-
|
|
656
|
-
The container supports four provider types for different use cases. For common patterns, VeloxTS provides succinct provider helpers that simplify registration.
|
|
657
|
-
|
|
658
|
-
#### Succinct Provider Helpers
|
|
659
|
-
|
|
660
|
-
VeloxTS provides convenient helpers for common provider patterns:
|
|
661
|
-
|
|
662
|
-
```typescript
|
|
663
|
-
import { singleton, scoped, transient, value, factory } from '@veloxts/core';
|
|
664
|
-
|
|
665
|
-
// Singleton scope (one instance for entire app)
|
|
666
|
-
app.container.register(singleton(ConfigService));
|
|
667
|
-
|
|
668
|
-
// Request scope (one instance per HTTP request)
|
|
669
|
-
app.container.register(scoped(UserContext));
|
|
670
|
-
|
|
671
|
-
// Transient scope (new instance every time)
|
|
672
|
-
app.container.register(transient(RequestIdGenerator));
|
|
673
|
-
|
|
674
|
-
// Value provider (provide existing value)
|
|
675
|
-
app.container.register(value(CONFIG, { port: 3210 }));
|
|
676
|
-
|
|
677
|
-
// Factory provider (use factory function)
|
|
678
|
-
app.container.register(
|
|
679
|
-
factory(DATABASE, (config: ConfigService) => {
|
|
680
|
-
return new PrismaClient({ datasources: { db: { url: config.databaseUrl } } });
|
|
681
|
-
}, [ConfigService])
|
|
682
|
-
);
|
|
683
|
-
```
|
|
684
|
-
|
|
685
|
-
These helpers are equivalent to the full provider syntax but more concise and readable.
|
|
686
|
-
|
|
687
|
-
#### Class Provider
|
|
688
|
-
|
|
689
|
-
Instantiate a class with automatic dependency injection:
|
|
690
|
-
|
|
691
|
-
```typescript
|
|
692
|
-
@Injectable()
|
|
693
|
-
class UserService {
|
|
694
|
-
constructor(
|
|
695
|
-
private db: DatabaseClient,
|
|
696
|
-
private logger: Logger
|
|
697
|
-
) {}
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// Using succinct helper (recommended)
|
|
701
|
-
app.container.register(singleton(UserService));
|
|
702
|
-
|
|
703
|
-
// Or using full syntax
|
|
704
|
-
app.container.register({
|
|
705
|
-
provide: UserService,
|
|
706
|
-
useClass: UserService,
|
|
707
|
-
scope: Scope.SINGLETON
|
|
708
|
-
});
|
|
709
|
-
```
|
|
710
|
-
|
|
711
|
-
#### Factory Provider
|
|
712
|
-
|
|
713
|
-
Use a factory function to create instances:
|
|
714
|
-
|
|
715
|
-
```typescript
|
|
716
|
-
// Using succinct helper (recommended)
|
|
717
|
-
app.container.register(
|
|
718
|
-
factory(DATABASE, (config: ConfigService) => {
|
|
719
|
-
return createDatabaseClient(config.databaseUrl);
|
|
720
|
-
}, [ConfigService])
|
|
721
|
-
);
|
|
722
|
-
|
|
723
|
-
// Or using full syntax
|
|
724
|
-
app.container.register({
|
|
725
|
-
provide: DATABASE,
|
|
726
|
-
useFactory: (config: ConfigService) => {
|
|
727
|
-
return createDatabaseClient(config.databaseUrl);
|
|
728
|
-
},
|
|
729
|
-
inject: [ConfigService],
|
|
730
|
-
scope: Scope.SINGLETON
|
|
731
|
-
});
|
|
732
|
-
```
|
|
733
|
-
|
|
734
|
-
#### Value Provider
|
|
735
|
-
|
|
736
|
-
Provide an existing value directly:
|
|
737
|
-
|
|
738
|
-
```typescript
|
|
739
|
-
const CONFIG = token<AppConfig>('CONFIG');
|
|
740
|
-
|
|
741
|
-
// Using succinct helper (recommended)
|
|
742
|
-
app.container.register(
|
|
743
|
-
value(CONFIG, {
|
|
744
|
-
port: 3210,
|
|
745
|
-
host: 'localhost',
|
|
746
|
-
debug: true
|
|
747
|
-
})
|
|
748
|
-
);
|
|
11
|
+
This package is part of the VeloxTS Framework. For the complete framework experience, install:
|
|
749
12
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
provide: CONFIG,
|
|
753
|
-
useValue: {
|
|
754
|
-
port: 3210,
|
|
755
|
-
host: 'localhost',
|
|
756
|
-
debug: true
|
|
757
|
-
}
|
|
758
|
-
});
|
|
759
|
-
```
|
|
760
|
-
|
|
761
|
-
#### Existing Provider (Alias)
|
|
762
|
-
|
|
763
|
-
Create an alias to another token:
|
|
764
|
-
|
|
765
|
-
```typescript
|
|
766
|
-
// Register concrete implementation
|
|
767
|
-
app.container.register({
|
|
768
|
-
provide: ConsoleLogger,
|
|
769
|
-
useClass: ConsoleLogger
|
|
770
|
-
});
|
|
771
|
-
|
|
772
|
-
// Create an alias
|
|
773
|
-
app.container.register({
|
|
774
|
-
provide: LOGGER,
|
|
775
|
-
useExisting: ConsoleLogger
|
|
776
|
-
});
|
|
777
|
-
|
|
778
|
-
// Both resolve to the same instance
|
|
779
|
-
const logger1 = app.container.resolve(LOGGER);
|
|
780
|
-
const logger2 = app.container.resolve(ConsoleLogger);
|
|
781
|
-
// logger1 === logger2
|
|
782
|
-
```
|
|
783
|
-
|
|
784
|
-
### Lifecycle Scopes
|
|
785
|
-
|
|
786
|
-
Scopes determine how service instances are created and shared.
|
|
787
|
-
|
|
788
|
-
#### Singleton Scope
|
|
789
|
-
|
|
790
|
-
One instance for the entire application (default):
|
|
791
|
-
|
|
792
|
-
```typescript
|
|
793
|
-
@Injectable({ scope: Scope.SINGLETON })
|
|
794
|
-
class ConfigService {
|
|
795
|
-
// Shared across all requests
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
// Register using succinct helper
|
|
799
|
-
app.container.register(singleton(ConfigService));
|
|
800
|
-
```
|
|
801
|
-
|
|
802
|
-
**Best for:**
|
|
803
|
-
- Configuration services
|
|
804
|
-
- Database connection pools
|
|
805
|
-
- Cache clients
|
|
806
|
-
- Stateless utility services
|
|
807
|
-
|
|
808
|
-
#### Transient Scope
|
|
809
|
-
|
|
810
|
-
New instance on every resolution:
|
|
811
|
-
|
|
812
|
-
```typescript
|
|
813
|
-
@Injectable({ scope: Scope.TRANSIENT })
|
|
814
|
-
class RequestIdGenerator {
|
|
815
|
-
readonly id = crypto.randomUUID();
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
// Register using succinct helper
|
|
819
|
-
app.container.register(transient(RequestIdGenerator));
|
|
13
|
+
```bash
|
|
14
|
+
npm install @veloxts/velox
|
|
820
15
|
```
|
|
821
16
|
|
|
822
|
-
|
|
823
|
-
- Services with mutable state
|
|
824
|
-
- Factories that produce unique objects
|
|
825
|
-
- Services where isolation is critical
|
|
826
|
-
|
|
827
|
-
#### Request Scope
|
|
828
|
-
|
|
829
|
-
One instance per HTTP request:
|
|
830
|
-
|
|
831
|
-
```typescript
|
|
832
|
-
@Injectable({ scope: Scope.REQUEST })
|
|
833
|
-
class UserContext {
|
|
834
|
-
constructor(private request: FastifyRequest) {}
|
|
17
|
+
Visit [@veloxts/velox](https://www.npmjs.com/package/@veloxts/velox) for the complete framework documentation.
|
|
835
18
|
|
|
836
|
-
|
|
837
|
-
return this.request.user?.id;
|
|
838
|
-
}
|
|
839
|
-
}
|
|
19
|
+
## Standalone Installation
|
|
840
20
|
|
|
841
|
-
|
|
842
|
-
|
|
21
|
+
```bash
|
|
22
|
+
npm install @veloxts/core
|
|
843
23
|
```
|
|
844
24
|
|
|
845
|
-
|
|
846
|
-
- User context/session data
|
|
847
|
-
- Request-specific caching
|
|
848
|
-
- Transaction management
|
|
849
|
-
- Audit logging with request context
|
|
850
|
-
|
|
851
|
-
**Note:** Request-scoped services require a Fastify request context. Resolving them outside a request handler throws an error.
|
|
25
|
+
## Documentation
|
|
852
26
|
|
|
853
|
-
|
|
27
|
+
For detailed documentation, usage examples, and API reference, see [GUIDE.md](./GUIDE.md).
|
|
854
28
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
#### Prerequisites
|
|
858
|
-
|
|
859
|
-
Enable decorators in `tsconfig.json`:
|
|
860
|
-
|
|
861
|
-
```json
|
|
862
|
-
{
|
|
863
|
-
"compilerOptions": {
|
|
864
|
-
"experimentalDecorators": true,
|
|
865
|
-
"emitDecoratorMetadata": true
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
```
|
|
869
|
-
|
|
870
|
-
Import `reflect-metadata` at your app's entry point:
|
|
29
|
+
## Quick Example
|
|
871
30
|
|
|
872
31
|
```typescript
|
|
873
|
-
import 'reflect-metadata';
|
|
874
32
|
import { veloxApp } from '@veloxts/core';
|
|
875
|
-
```
|
|
876
|
-
|
|
877
|
-
#### @Injectable()
|
|
878
|
-
|
|
879
|
-
Marks a class as injectable:
|
|
880
|
-
|
|
881
|
-
```typescript
|
|
882
|
-
@Injectable()
|
|
883
|
-
class UserService {
|
|
884
|
-
constructor(private db: DatabaseClient) {}
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
@Injectable({ scope: Scope.REQUEST })
|
|
888
|
-
class RequestLogger {
|
|
889
|
-
constructor(private request: FastifyRequest) {}
|
|
890
|
-
}
|
|
891
|
-
```
|
|
892
|
-
|
|
893
|
-
#### @Inject()
|
|
894
|
-
|
|
895
|
-
Explicitly specifies the injection token:
|
|
896
|
-
|
|
897
|
-
```typescript
|
|
898
|
-
const DATABASE = token<DatabaseClient>('DATABASE');
|
|
899
|
-
|
|
900
|
-
@Injectable()
|
|
901
|
-
class UserService {
|
|
902
|
-
constructor(
|
|
903
|
-
@Inject(DATABASE) private db: DatabaseClient,
|
|
904
|
-
private config: ConfigService // Auto-injected by class token
|
|
905
|
-
) {}
|
|
906
|
-
}
|
|
907
|
-
```
|
|
908
|
-
|
|
909
|
-
Use `@Inject()` when:
|
|
910
|
-
- Injecting by string or symbol token
|
|
911
|
-
- Injecting an interface (TypeScript interfaces are erased at runtime)
|
|
912
|
-
- Automatic type resolution doesn't work
|
|
913
|
-
|
|
914
|
-
#### @Optional()
|
|
915
|
-
|
|
916
|
-
Marks a dependency as optional:
|
|
917
|
-
|
|
918
|
-
```typescript
|
|
919
|
-
@Injectable()
|
|
920
|
-
class NotificationService {
|
|
921
|
-
constructor(
|
|
922
|
-
@Optional() private emailService?: EmailService,
|
|
923
|
-
@Optional() @Inject(SMS_SERVICE) private smsService?: SmsService
|
|
924
|
-
) {}
|
|
925
|
-
|
|
926
|
-
notify(message: string) {
|
|
927
|
-
// Gracefully handle missing services
|
|
928
|
-
this.emailService?.send(message);
|
|
929
|
-
this.smsService?.send(message);
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
```
|
|
933
|
-
|
|
934
|
-
If an optional dependency cannot be resolved, `undefined` is injected instead of throwing an error.
|
|
935
|
-
|
|
936
|
-
### Container API
|
|
937
|
-
|
|
938
|
-
#### Registering Services
|
|
939
|
-
|
|
940
|
-
Register a single provider:
|
|
941
|
-
|
|
942
|
-
```typescript
|
|
943
|
-
// Using succinct helper (recommended)
|
|
944
|
-
app.container.register(scoped(UserService));
|
|
945
|
-
|
|
946
|
-
// Or using full syntax
|
|
947
|
-
app.container.register({
|
|
948
|
-
provide: UserService,
|
|
949
|
-
useClass: UserService,
|
|
950
|
-
scope: Scope.REQUEST
|
|
951
|
-
});
|
|
952
|
-
```
|
|
953
|
-
|
|
954
|
-
Register multiple providers:
|
|
955
|
-
|
|
956
|
-
```typescript
|
|
957
|
-
// Using succinct helpers (recommended)
|
|
958
|
-
app.container.registerMany([
|
|
959
|
-
singleton(UserService),
|
|
960
|
-
singleton(PostService),
|
|
961
|
-
value(CONFIG, appConfig)
|
|
962
|
-
]);
|
|
963
|
-
|
|
964
|
-
// Or using full syntax
|
|
965
|
-
app.container.registerMany([
|
|
966
|
-
{ provide: UserService, useClass: UserService },
|
|
967
|
-
{ provide: PostService, useClass: PostService },
|
|
968
|
-
{ provide: CONFIG, useValue: appConfig }
|
|
969
|
-
]);
|
|
970
|
-
```
|
|
971
|
-
|
|
972
|
-
#### Resolving Services
|
|
973
|
-
|
|
974
|
-
Synchronous resolution:
|
|
975
|
-
|
|
976
|
-
```typescript
|
|
977
|
-
const userService = app.container.resolve(UserService);
|
|
978
|
-
```
|
|
979
|
-
|
|
980
|
-
Asynchronous resolution (for async factories):
|
|
981
|
-
|
|
982
|
-
```typescript
|
|
983
|
-
// Using succinct helper (recommended)
|
|
984
|
-
app.container.register(
|
|
985
|
-
factory(DATABASE, async (config: ConfigService) => {
|
|
986
|
-
const client = createClient(config.dbUrl);
|
|
987
|
-
await client.connect();
|
|
988
|
-
return client;
|
|
989
|
-
}, [ConfigService])
|
|
990
|
-
);
|
|
991
|
-
|
|
992
|
-
const db = await app.container.resolveAsync(DATABASE);
|
|
993
|
-
```
|
|
994
|
-
|
|
995
|
-
Optional resolution (returns `undefined` if not found):
|
|
996
|
-
|
|
997
|
-
```typescript
|
|
998
|
-
const service = app.container.resolveOptional(OptionalService);
|
|
999
|
-
if (service) {
|
|
1000
|
-
service.doSomething();
|
|
1001
|
-
}
|
|
1002
|
-
```
|
|
1003
|
-
|
|
1004
|
-
#### Request Context
|
|
1005
|
-
|
|
1006
|
-
Resolve request-scoped services with context:
|
|
1007
|
-
|
|
1008
|
-
```typescript
|
|
1009
|
-
app.server.get('/users', async (request, reply) => {
|
|
1010
|
-
const ctx = Container.createContext(request);
|
|
1011
|
-
const userContext = app.container.resolve(UserContext, ctx);
|
|
1012
|
-
|
|
1013
|
-
return { userId: userContext.userId };
|
|
1014
|
-
});
|
|
1015
|
-
```
|
|
1016
|
-
|
|
1017
|
-
### Integration with VeloxApp
|
|
1018
|
-
|
|
1019
|
-
The container is automatically attached to the Fastify server for request-scoped services:
|
|
1020
|
-
|
|
1021
|
-
```typescript
|
|
1022
|
-
const app = await veloxApp();
|
|
1023
|
-
|
|
1024
|
-
// Container is already attached to app.server
|
|
1025
|
-
// Request-scoped services work automatically
|
|
1026
|
-
|
|
1027
|
-
@Injectable({ scope: Scope.REQUEST })
|
|
1028
|
-
class RequestLogger {
|
|
1029
|
-
constructor(private request: FastifyRequest) {}
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
// Register using succinct helper
|
|
1033
|
-
app.container.register(scoped(RequestLogger));
|
|
1034
|
-
|
|
1035
|
-
app.server.get('/log', async (request, reply) => {
|
|
1036
|
-
const ctx = Container.createContext(request);
|
|
1037
|
-
const logger = app.container.resolve(RequestLogger, ctx);
|
|
1038
|
-
logger.log('Request received');
|
|
1039
|
-
return { ok: true };
|
|
1040
|
-
});
|
|
1041
|
-
```
|
|
1042
|
-
|
|
1043
|
-
### Practical Examples
|
|
1044
|
-
|
|
1045
|
-
#### Complete Service Layer
|
|
1046
|
-
|
|
1047
|
-
```typescript
|
|
1048
|
-
import 'reflect-metadata';
|
|
1049
|
-
import {
|
|
1050
|
-
veloxApp,
|
|
1051
|
-
Injectable,
|
|
1052
|
-
Inject,
|
|
1053
|
-
Scope,
|
|
1054
|
-
token,
|
|
1055
|
-
singleton,
|
|
1056
|
-
scoped,
|
|
1057
|
-
factory
|
|
1058
|
-
} from '@veloxts/core';
|
|
1059
|
-
import { PrismaClient } from '@prisma/client';
|
|
1060
|
-
|
|
1061
|
-
// Define tokens
|
|
1062
|
-
const DATABASE = token<PrismaClient>('DATABASE');
|
|
1063
|
-
const CONFIG = token<AppConfig>('CONFIG');
|
|
1064
|
-
|
|
1065
|
-
// Configuration service
|
|
1066
|
-
@Injectable()
|
|
1067
|
-
class ConfigService {
|
|
1068
|
-
get databaseUrl(): string {
|
|
1069
|
-
return process.env.DATABASE_URL!;
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
// Database service
|
|
1074
|
-
@Injectable()
|
|
1075
|
-
class DatabaseService {
|
|
1076
|
-
constructor(@Inject(DATABASE) private db: PrismaClient) {}
|
|
1077
|
-
|
|
1078
|
-
async findUser(id: string) {
|
|
1079
|
-
return this.db.user.findUnique({ where: { id } });
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
// Business logic service
|
|
1084
|
-
@Injectable({ scope: Scope.REQUEST })
|
|
1085
|
-
class UserService {
|
|
1086
|
-
constructor(
|
|
1087
|
-
private database: DatabaseService,
|
|
1088
|
-
private config: ConfigService
|
|
1089
|
-
) {}
|
|
1090
|
-
|
|
1091
|
-
async getUser(id: string) {
|
|
1092
|
-
return this.database.findUser(id);
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
// Create app
|
|
1097
|
-
const app = await veloxApp();
|
|
1098
|
-
|
|
1099
|
-
// Register services using succinct helpers
|
|
1100
|
-
app.container.register(
|
|
1101
|
-
factory(DATABASE, (config: ConfigService) => {
|
|
1102
|
-
return new PrismaClient({
|
|
1103
|
-
datasources: { db: { url: config.databaseUrl } }
|
|
1104
|
-
});
|
|
1105
|
-
}, [ConfigService])
|
|
1106
|
-
);
|
|
1107
|
-
|
|
1108
|
-
app.container.register(singleton(ConfigService));
|
|
1109
|
-
app.container.register(singleton(DatabaseService));
|
|
1110
|
-
app.container.register(scoped(UserService));
|
|
1111
|
-
|
|
1112
|
-
// Use in routes
|
|
1113
|
-
app.server.get('/users/:id', async (request, reply) => {
|
|
1114
|
-
const ctx = Container.createContext(request);
|
|
1115
|
-
const userService = app.container.resolve(UserService, ctx);
|
|
1116
|
-
|
|
1117
|
-
const user = await userService.getUser(request.params.id);
|
|
1118
|
-
return user;
|
|
1119
|
-
});
|
|
1120
33
|
|
|
34
|
+
const app = await veloxApp({ port: 3210 });
|
|
1121
35
|
await app.start();
|
|
36
|
+
console.log(`Server running on ${app.address}`);
|
|
1122
37
|
```
|
|
1123
38
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
```typescript
|
|
1127
|
-
import { createContainer, asClass } from '@veloxts/core';
|
|
1128
|
-
|
|
1129
|
-
// Create a child container for testing
|
|
1130
|
-
const testContainer = app.container.createChild();
|
|
1131
|
-
|
|
1132
|
-
// Override services with mocks
|
|
1133
|
-
class MockDatabaseService {
|
|
1134
|
-
async findUser(id: string) {
|
|
1135
|
-
return { id, name: 'Test User' };
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
testContainer.register({
|
|
1140
|
-
provide: DatabaseService,
|
|
1141
|
-
useClass: MockDatabaseService
|
|
1142
|
-
});
|
|
1143
|
-
|
|
1144
|
-
// Test with mocked dependencies
|
|
1145
|
-
const userService = testContainer.resolve(UserService);
|
|
1146
|
-
const user = await userService.getUser('123');
|
|
1147
|
-
// user comes from MockDatabaseService
|
|
1148
|
-
```
|
|
1149
|
-
|
|
1150
|
-
#### Auto-Registration
|
|
1151
|
-
|
|
1152
|
-
Enable auto-registration to automatically register `@Injectable` classes:
|
|
1153
|
-
|
|
1154
|
-
```typescript
|
|
1155
|
-
const app = await createVeloxApp();
|
|
1156
|
-
const autoContainer = createContainer({ autoRegister: true });
|
|
1157
|
-
|
|
1158
|
-
@Injectable()
|
|
1159
|
-
class AutoService {}
|
|
1160
|
-
|
|
1161
|
-
// No need to manually register
|
|
1162
|
-
const service = autoContainer.resolve(AutoService);
|
|
1163
|
-
// Automatically registers and resolves
|
|
1164
|
-
```
|
|
1165
|
-
|
|
1166
|
-
### Advanced Features
|
|
1167
|
-
|
|
1168
|
-
#### Circular Dependency Detection
|
|
1169
|
-
|
|
1170
|
-
The container detects circular dependencies:
|
|
1171
|
-
|
|
1172
|
-
```typescript
|
|
1173
|
-
@Injectable()
|
|
1174
|
-
class ServiceA {
|
|
1175
|
-
constructor(private b: ServiceB) {}
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
@Injectable()
|
|
1179
|
-
class ServiceB {
|
|
1180
|
-
constructor(private a: ServiceA) {}
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
// Throws: Circular dependency detected: ServiceA -> ServiceB -> ServiceA
|
|
1184
|
-
const service = app.container.resolve(ServiceA);
|
|
1185
|
-
```
|
|
1186
|
-
|
|
1187
|
-
#### Container Hierarchy
|
|
1188
|
-
|
|
1189
|
-
Create parent-child container hierarchies:
|
|
1190
|
-
|
|
1191
|
-
```typescript
|
|
1192
|
-
const parentContainer = createContainer();
|
|
1193
|
-
parentContainer.register({
|
|
1194
|
-
provide: ConfigService,
|
|
1195
|
-
useClass: ConfigService
|
|
1196
|
-
});
|
|
1197
|
-
|
|
1198
|
-
const childContainer = parentContainer.createChild();
|
|
1199
|
-
// Child inherits ConfigService from parent
|
|
1200
|
-
// Can override with its own providers
|
|
1201
|
-
|
|
1202
|
-
childContainer.register({
|
|
1203
|
-
provide: UserService,
|
|
1204
|
-
useClass: UserService
|
|
1205
|
-
});
|
|
1206
|
-
```
|
|
1207
|
-
|
|
1208
|
-
#### Debug Information
|
|
1209
|
-
|
|
1210
|
-
Get container statistics:
|
|
1211
|
-
|
|
1212
|
-
```typescript
|
|
1213
|
-
const info = app.container.getDebugInfo();
|
|
1214
|
-
console.log(info);
|
|
1215
|
-
// {
|
|
1216
|
-
// providerCount: 5,
|
|
1217
|
-
// providers: [
|
|
1218
|
-
// 'class(UserService, request)',
|
|
1219
|
-
// 'factory(DATABASE, singleton)',
|
|
1220
|
-
// 'value(CONFIG)',
|
|
1221
|
-
// 'existing(LOGGER => ConsoleLogger)'
|
|
1222
|
-
// ],
|
|
1223
|
-
// hasParent: false,
|
|
1224
|
-
// autoRegister: false
|
|
1225
|
-
// }
|
|
1226
|
-
```
|
|
1227
|
-
|
|
1228
|
-
### Best Practices
|
|
1229
|
-
|
|
1230
|
-
1. **Use tokens for interfaces**: Create string or symbol tokens for interface types since TypeScript interfaces don't exist at runtime.
|
|
1231
|
-
|
|
1232
|
-
2. **Prefer singleton scope**: Use `Scope.SINGLETON` by default for stateless services to improve performance.
|
|
1233
|
-
|
|
1234
|
-
3. **Use request scope for user context**: Store user authentication, request-specific state, and transactions in request-scoped services.
|
|
1235
|
-
|
|
1236
|
-
4. **Avoid circular dependencies**: Refactor shared logic into a third service to break circular dependencies.
|
|
1237
|
-
|
|
1238
|
-
5. **Use `@Inject()` for clarity**: Explicitly specify tokens with `@Inject()` to make dependencies clear and avoid runtime type resolution issues.
|
|
1239
|
-
|
|
1240
|
-
6. **Test with child containers**: Create child containers in tests to override services with mocks without affecting the main container.
|
|
1241
|
-
|
|
1242
|
-
## Related Packages
|
|
1243
|
-
|
|
1244
|
-
- [@veloxts/router](/packages/router) - Procedure-based routing
|
|
1245
|
-
- [@veloxts/validation](/packages/validation) - Schema validation with Zod
|
|
1246
|
-
- [@veloxts/orm](/packages/orm) - Prisma integration
|
|
1247
|
-
- [@veloxts/client](/packages/client) - Type-safe frontend API client
|
|
1248
|
-
- [@veloxts/cli](/packages/cli) - Developer tooling
|
|
1249
|
-
|
|
1250
|
-
## TypeScript Support
|
|
39
|
+
## Learn More
|
|
1251
40
|
|
|
1252
|
-
|
|
41
|
+
- [Full Documentation](./GUIDE.md)
|
|
42
|
+
- [VeloxTS Framework](https://www.npmjs.com/package/@veloxts/velox)
|
|
43
|
+
- [GitHub Repository](https://github.com/veloxts/velox-ts-framework)
|
|
1253
44
|
|
|
1254
45
|
## License
|
|
1255
46
|
|
package/dist/errors.js
CHANGED
|
@@ -80,7 +80,7 @@ export class VeloxError extends Error {
|
|
|
80
80
|
this.statusCode = statusCode;
|
|
81
81
|
this.code = code;
|
|
82
82
|
// Look up catalog entry for enhanced error info
|
|
83
|
-
if (code
|
|
83
|
+
if (code != null && String(code).startsWith('VELOX-')) {
|
|
84
84
|
const entry = ERROR_CATALOG[code];
|
|
85
85
|
if (entry) {
|
|
86
86
|
this.fix = entry.fix?.suggestion;
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,0CAA0C;AAC1C,OAAO,
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|