@navios/core 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -3
- package/docs/README.md +310 -3
- package/docs/adapters.md +308 -0
- package/docs/application-setup.md +524 -0
- package/docs/attributes.md +689 -0
- package/docs/controllers.md +373 -0
- package/docs/endpoints.md +444 -0
- package/docs/exceptions.md +316 -0
- package/docs/guards.md +550 -0
- package/docs/modules.md +251 -0
- package/docs/quick-start.md +295 -0
- package/docs/services.md +428 -0
- package/docs/testing.md +704 -0
- package/lib/_tsup-dts-rollup.d.mts +313 -280
- package/lib/_tsup-dts-rollup.d.ts +313 -280
- package/lib/index.d.mts +47 -26
- package/lib/index.d.ts +47 -26
- package/lib/index.js +633 -1068
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +632 -1061
- package/lib/index.mjs.map +1 -1
- package/package.json +11 -12
- package/project.json +17 -4
- package/src/__tests__/config.service.spec.mts +11 -9
- package/src/__tests__/controller.spec.mts +1 -2
- package/src/attribute.factory.mts +1 -1
- package/src/config/config.provider.mts +2 -2
- package/src/config/config.service.mts +4 -4
- package/src/decorators/controller.decorator.mts +1 -1
- package/src/decorators/endpoint.decorator.mts +9 -10
- package/src/decorators/header.decorator.mts +1 -1
- package/src/decorators/multipart.decorator.mts +5 -5
- package/src/decorators/stream.decorator.mts +5 -6
- package/src/factories/endpoint-adapter.factory.mts +21 -0
- package/src/factories/http-adapter.factory.mts +20 -0
- package/src/factories/index.mts +6 -0
- package/src/factories/multipart-adapter.factory.mts +21 -0
- package/src/factories/reply.factory.mts +21 -0
- package/src/factories/request.factory.mts +21 -0
- package/src/factories/stream-adapter.factory.mts +20 -0
- package/src/index.mts +1 -1
- package/src/interfaces/abstract-execution-context.inteface.mts +13 -0
- package/src/interfaces/abstract-http-adapter.interface.mts +20 -0
- package/src/interfaces/abstract-http-cors-options.interface.mts +59 -0
- package/src/interfaces/abstract-http-handler-adapter.interface.mts +13 -0
- package/src/interfaces/abstract-http-listen-options.interface.mts +4 -0
- package/src/interfaces/can-activate.mts +4 -2
- package/src/interfaces/http-header.mts +18 -0
- package/src/interfaces/index.mts +6 -0
- package/src/logger/console-logger.service.mts +28 -44
- package/src/logger/index.mts +1 -2
- package/src/logger/logger.service.mts +9 -128
- package/src/logger/logger.tokens.mts +21 -0
- package/src/metadata/handler.metadata.mts +7 -5
- package/src/navios.application.mts +65 -172
- package/src/navios.environment.mts +30 -0
- package/src/navios.factory.mts +53 -12
- package/src/services/guard-runner.service.mts +19 -9
- package/src/services/index.mts +0 -2
- package/src/services/module-loader.service.mts +4 -3
- package/src/tokens/endpoint-adapter.token.mts +8 -0
- package/src/tokens/execution-context.token.mts +2 -2
- package/src/tokens/http-adapter.token.mts +8 -0
- package/src/tokens/index.mts +4 -1
- package/src/tokens/multipart-adapter.token.mts +8 -0
- package/src/tokens/reply.token.mts +1 -5
- package/src/tokens/request.token.mts +1 -7
- package/src/tokens/stream-adapter.token.mts +8 -0
- package/tsconfig.json +6 -1
- package/tsconfig.lib.json +8 -0
- package/tsconfig.spec.json +12 -0
- package/tsup.config.mts +1 -0
- package/docs/recipes/prisma.md +0 -60
- package/examples/simple-test/api/index.mts +0 -64
- package/examples/simple-test/config/config.service.mts +0 -14
- package/examples/simple-test/config/configuration.mts +0 -7
- package/examples/simple-test/index.mts +0 -16
- package/examples/simple-test/src/acl/acl-modern.guard.mts +0 -15
- package/examples/simple-test/src/acl/acl.guard.mts +0 -14
- package/examples/simple-test/src/acl/app.guard.mts +0 -27
- package/examples/simple-test/src/acl/one-more.guard.mts +0 -15
- package/examples/simple-test/src/acl/public.attribute.mts +0 -21
- package/examples/simple-test/src/app.module.mts +0 -9
- package/examples/simple-test/src/user/user.controller.mts +0 -72
- package/examples/simple-test/src/user/user.module.mts +0 -14
- package/examples/simple-test/src/user/user.service.mts +0 -14
- package/src/adapters/endpoint-adapter.service.mts +0 -72
- package/src/adapters/handler-adapter.interface.mts +0 -21
- package/src/adapters/index.mts +0 -4
- package/src/adapters/multipart-adapter.service.mts +0 -131
- package/src/adapters/stream-adapter.service.mts +0 -91
- package/src/logger/logger.factory.mts +0 -36
- package/src/logger/pino-wrapper.mts +0 -64
- package/src/services/controller-adapter.service.mts +0 -124
- package/src/services/execution-context.mts +0 -54
- package/src/tokens/application.token.mts +0 -9
package/docs/services.md
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
# Services and Dependency Injection
|
|
2
|
+
|
|
3
|
+
Navios provides a powerful dependency injection system based on `@navios/di` that allows you to create and manage services throughout your application. Services are typically used to encapsulate business logic, data access, and other shared functionality.
|
|
4
|
+
|
|
5
|
+
## What are Services?
|
|
6
|
+
|
|
7
|
+
Services are classes that contain business logic and can be injected into controllers, other services, or any class within the Navios application. They promote code reusability, testability, and separation of concerns.
|
|
8
|
+
|
|
9
|
+
## Creating Services
|
|
10
|
+
|
|
11
|
+
### Basic Service
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Injectable } from '@navios/di'
|
|
15
|
+
|
|
16
|
+
@Injectable()
|
|
17
|
+
export class UserService {
|
|
18
|
+
async findAll() {
|
|
19
|
+
// Business logic here
|
|
20
|
+
return []
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async findById(id: string) {
|
|
24
|
+
// Business logic here
|
|
25
|
+
return { id, name: 'John Doe' }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async create(userData: CreateUserDto) {
|
|
29
|
+
// Business logic here
|
|
30
|
+
return { id: '1', ...userData }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Service with Dependencies
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { inject, Injectable } from '@navios/di'
|
|
39
|
+
|
|
40
|
+
@Injectable()
|
|
41
|
+
export class UserService {
|
|
42
|
+
private database = inject(DatabaseService)
|
|
43
|
+
private logger = inject(Logger, { context: 'UserService' })
|
|
44
|
+
|
|
45
|
+
async findById(id: string) {
|
|
46
|
+
this.logger.debug(`Finding user by ID: ${id}`)
|
|
47
|
+
return this.database.users.findUnique({ where: { id } })
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async create(userData: CreateUserDto) {
|
|
51
|
+
this.logger.debug('Creating new user')
|
|
52
|
+
return this.database.users.create({ data: userData })
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Dependency Injection
|
|
58
|
+
|
|
59
|
+
### Using `inject()`
|
|
60
|
+
|
|
61
|
+
The `inject()` function is the primary way to inject dependencies:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { inject } from '@navios/di'
|
|
65
|
+
|
|
66
|
+
@Injectable()
|
|
67
|
+
export class UserService {
|
|
68
|
+
private database = inject(DatabaseService)
|
|
69
|
+
private config = inject(ConfigService)
|
|
70
|
+
private logger = inject(Logger, { context: 'UserService' })
|
|
71
|
+
|
|
72
|
+
async findAll() {
|
|
73
|
+
const pageSize = this.config.get('PAGE_SIZE')
|
|
74
|
+
this.logger.debug(`Fetching users with page size: ${pageSize}`)
|
|
75
|
+
return this.database.users.findMany({ take: pageSize })
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Injection with Options
|
|
81
|
+
|
|
82
|
+
You can pass options when injecting dependencies:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
@Injectable()
|
|
86
|
+
export class UserService {
|
|
87
|
+
// Inject with context for logger
|
|
88
|
+
private logger = inject(Logger, { context: 'UserService' })
|
|
89
|
+
|
|
90
|
+
// Inject with custom options
|
|
91
|
+
private cache = inject(CacheService, { ttl: 3600 })
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Service Scopes
|
|
96
|
+
|
|
97
|
+
Services can have different scopes that determine their lifecycle:
|
|
98
|
+
|
|
99
|
+
### Singleton Scope (Default)
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { Injectable, InjectableScope } from '@navios/di'
|
|
103
|
+
|
|
104
|
+
@Injectable({ scope: InjectableScope.Singleton })
|
|
105
|
+
export class ConfigService {
|
|
106
|
+
// Single instance shared across the application
|
|
107
|
+
private config = new Map()
|
|
108
|
+
|
|
109
|
+
get(key: string) {
|
|
110
|
+
return this.config.get(key)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Request Scope
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
@Injectable({ scope: InjectableScope.Request })
|
|
119
|
+
export class RequestContextService {
|
|
120
|
+
// New instance created for each HTTP request
|
|
121
|
+
private requestId = crypto.randomUUID()
|
|
122
|
+
|
|
123
|
+
getRequestId() {
|
|
124
|
+
return this.requestId
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Transient Scope
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
@Injectable({ scope: InjectableScope.Transient })
|
|
133
|
+
export class UtilityService {
|
|
134
|
+
// New instance created every time it's injected
|
|
135
|
+
createId() {
|
|
136
|
+
return crypto.randomUUID()
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Service Tokens
|
|
142
|
+
|
|
143
|
+
Use injection tokens for more flexible dependency injection:
|
|
144
|
+
|
|
145
|
+
### Creating Custom Tokens
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { InjectionToken } from '@navios/di'
|
|
149
|
+
|
|
150
|
+
// Create a token for configuration
|
|
151
|
+
export const CONFIG_TOKEN = InjectionToken.create<{
|
|
152
|
+
apiUrl: string
|
|
153
|
+
apiKey: string
|
|
154
|
+
}>('CONFIG')
|
|
155
|
+
|
|
156
|
+
// Create a token for a database interface
|
|
157
|
+
export interface DatabaseInterface {
|
|
158
|
+
findUser(id: string): Promise<User>
|
|
159
|
+
createUser(data: CreateUserDto): Promise<User>
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const DATABASE_TOKEN =
|
|
163
|
+
InjectionToken.create<DatabaseInterface>('DATABASE')
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Using Tokens in Services
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
@Injectable()
|
|
170
|
+
export class UserService {
|
|
171
|
+
private config = inject(CONFIG_TOKEN)
|
|
172
|
+
private database = inject(DATABASE_TOKEN)
|
|
173
|
+
|
|
174
|
+
async findById(id: string) {
|
|
175
|
+
const apiUrl = this.config.apiUrl
|
|
176
|
+
return this.database.findUser(id)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Common Service Patterns
|
|
182
|
+
|
|
183
|
+
### Repository Pattern
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
export interface UserRepository {
|
|
187
|
+
findById(id: string): Promise<User | null>
|
|
188
|
+
findAll(): Promise<User[]>
|
|
189
|
+
create(user: CreateUserDto): Promise<User>
|
|
190
|
+
update(id: string, user: UpdateUserDto): Promise<User>
|
|
191
|
+
delete(id: string): Promise<void>
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export const USER_REPOSITORY_TOKEN =
|
|
195
|
+
InjectionToken.create<UserRepository>('USER_REPOSITORY')
|
|
196
|
+
|
|
197
|
+
@Injectable()
|
|
198
|
+
export class DatabaseUserRepository implements UserRepository {
|
|
199
|
+
private database = inject(DatabaseService)
|
|
200
|
+
|
|
201
|
+
async findById(id: string) {
|
|
202
|
+
return this.database.users.findUnique({ where: { id } })
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async findAll() {
|
|
206
|
+
return this.database.users.findMany()
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async create(user: CreateUserDto) {
|
|
210
|
+
return this.database.users.create({ data: user })
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async update(id: string, user: UpdateUserDto) {
|
|
214
|
+
return this.database.users.update({ where: { id }, data: user })
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async delete(id: string) {
|
|
218
|
+
await this.database.users.delete({ where: { id } })
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@Injectable()
|
|
223
|
+
export class UserService {
|
|
224
|
+
private userRepository = inject(USER_REPOSITORY_TOKEN)
|
|
225
|
+
|
|
226
|
+
async getUser(id: string) {
|
|
227
|
+
const user = await this.userRepository.findById(id)
|
|
228
|
+
if (!user) {
|
|
229
|
+
throw new NotFoundException('User not found')
|
|
230
|
+
}
|
|
231
|
+
return user
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Factory Pattern
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
export interface EmailProvider {
|
|
240
|
+
send(to: string, subject: string, body: string): Promise<void>
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
@Factory()
|
|
244
|
+
export class EmailProviderFactory {
|
|
245
|
+
private config = inject(ConfigService)
|
|
246
|
+
|
|
247
|
+
create(): EmailProvider {
|
|
248
|
+
const provider = this.config.get('EMAIL_PROVIDER')
|
|
249
|
+
|
|
250
|
+
switch (provider) {
|
|
251
|
+
case 'sendgrid':
|
|
252
|
+
return new SendGridProvider()
|
|
253
|
+
case 'mailgun':
|
|
254
|
+
return new MailgunProvider()
|
|
255
|
+
default:
|
|
256
|
+
return new MockEmailProvider()
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@Injectable()
|
|
262
|
+
export class EmailService {
|
|
263
|
+
private emailProviderFactory = inject(EmailProviderFactory)
|
|
264
|
+
|
|
265
|
+
async sendWelcomeEmail(user: User) {
|
|
266
|
+
const provider = this.emailProviderFactory.create()
|
|
267
|
+
await provider.send(
|
|
268
|
+
user.email,
|
|
269
|
+
'Welcome!',
|
|
270
|
+
`Hello ${user.name}, welcome to our platform!`,
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Event Service
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
export interface DomainEvent {
|
|
280
|
+
type: string
|
|
281
|
+
payload: any
|
|
282
|
+
timestamp: Date
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
@Injectable()
|
|
286
|
+
export class EventService {
|
|
287
|
+
private events: DomainEvent[] = []
|
|
288
|
+
private handlers = new Map<string, Function[]>()
|
|
289
|
+
|
|
290
|
+
publish(event: DomainEvent) {
|
|
291
|
+
this.events.push(event)
|
|
292
|
+
const handlers = this.handlers.get(event.type) || []
|
|
293
|
+
handlers.forEach((handler) => handler(event))
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
subscribe(eventType: string, handler: Function) {
|
|
297
|
+
if (!this.handlers.has(eventType)) {
|
|
298
|
+
this.handlers.set(eventType, [])
|
|
299
|
+
}
|
|
300
|
+
this.handlers.get(eventType)!.push(handler)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
@Injectable()
|
|
305
|
+
export class UserService {
|
|
306
|
+
private eventService = inject(EventService)
|
|
307
|
+
|
|
308
|
+
async createUser(userData: CreateUserDto) {
|
|
309
|
+
const user = await this.database.users.create({ data: userData })
|
|
310
|
+
|
|
311
|
+
// Publish domain event
|
|
312
|
+
this.eventService.publish({
|
|
313
|
+
type: 'USER_CREATED',
|
|
314
|
+
payload: { userId: user.id, email: user.email },
|
|
315
|
+
timestamp: new Date(),
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
return user
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Configuration Service
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
import { Injectable } from '@navios/di'
|
|
327
|
+
|
|
328
|
+
@Injectable()
|
|
329
|
+
export class ConfigService {
|
|
330
|
+
private config = new Map<string, any>()
|
|
331
|
+
|
|
332
|
+
constructor() {
|
|
333
|
+
// Load configuration from environment variables
|
|
334
|
+
this.config.set('DATABASE_URL', process.env.DATABASE_URL)
|
|
335
|
+
this.config.set('JWT_SECRET', process.env.JWT_SECRET)
|
|
336
|
+
this.config.set('PORT', parseInt(process.env.PORT || '3000', 10))
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
get<T = string>(key: string): T {
|
|
340
|
+
return this.config.get(key)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
set(key: string, value: any) {
|
|
344
|
+
this.config.set(key, value)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
has(key: string): boolean {
|
|
348
|
+
return this.config.has(key)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Usage in other services
|
|
353
|
+
@Injectable()
|
|
354
|
+
export class DatabaseService {
|
|
355
|
+
private config = inject(ConfigService)
|
|
356
|
+
|
|
357
|
+
connect() {
|
|
358
|
+
const databaseUrl = this.config.get('DATABASE_URL')
|
|
359
|
+
// Connect to database
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Best Practices
|
|
365
|
+
|
|
366
|
+
### 1. Single Responsibility
|
|
367
|
+
|
|
368
|
+
Each service should have a single, well-defined responsibility:
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
// ✅ Good - Single responsibility
|
|
372
|
+
@Injectable()
|
|
373
|
+
export class UserService {
|
|
374
|
+
async findById(id: string) {
|
|
375
|
+
/* ... */
|
|
376
|
+
}
|
|
377
|
+
async create(user: CreateUserDto) {
|
|
378
|
+
/* ... */
|
|
379
|
+
}
|
|
380
|
+
async update(id: string, user: UpdateUserDto) {
|
|
381
|
+
/* ... */
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ❌ Avoid - Multiple responsibilities
|
|
386
|
+
@Injectable()
|
|
387
|
+
export class UserEmailAuthService {
|
|
388
|
+
// Too many responsibilities mixed together
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### 2. Proper Error Handling
|
|
393
|
+
|
|
394
|
+
Handle errors appropriately in services:
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
@Injectable()
|
|
398
|
+
export class UserService {
|
|
399
|
+
private logger = inject(Logger, { context: 'UserService' })
|
|
400
|
+
|
|
401
|
+
async findById(id: string) {
|
|
402
|
+
try {
|
|
403
|
+
const user = await this.database.users.findUnique({ where: { id } })
|
|
404
|
+
if (!user) {
|
|
405
|
+
throw new NotFoundException(`User with ID ${id} not found`)
|
|
406
|
+
}
|
|
407
|
+
return user
|
|
408
|
+
} catch (error) {
|
|
409
|
+
this.logger.error(`Failed to find user ${id}`, error.stack)
|
|
410
|
+
throw error
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### 3. Use Proper Scopes
|
|
417
|
+
|
|
418
|
+
Choose appropriate scopes for your services:
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
// Singleton for stateless services
|
|
422
|
+
@Injectable({ scope: InjectableScope.Singleton })
|
|
423
|
+
export class UtilityService {}
|
|
424
|
+
|
|
425
|
+
// Request scope for request-specific data
|
|
426
|
+
@Injectable({ scope: InjectableScope.Request })
|
|
427
|
+
export class RequestContextService {}
|
|
428
|
+
```
|