exisjs 0.7.12 → 0.7.13

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.
Files changed (116) hide show
  1. package/README.md +195 -39
  2. package/dist/config/types.d.ts +2 -2
  3. package/dist/config/types.d.ts.map +1 -1
  4. package/dist/decorators/params.d.ts +21 -0
  5. package/dist/decorators/params.d.ts.map +1 -1
  6. package/dist/decorators/params.js +21 -1
  7. package/dist/decorators/params.js.map +1 -1
  8. package/dist/di/container.d.ts +15 -6
  9. package/dist/di/container.d.ts.map +1 -1
  10. package/dist/di/container.js +26 -3
  11. package/dist/di/container.js.map +1 -1
  12. package/dist/di/index.d.ts +1 -1
  13. package/dist/di/index.d.ts.map +1 -1
  14. package/dist/di/index.js.map +1 -1
  15. package/dist/di/inject.d.ts +2 -1
  16. package/dist/di/inject.d.ts.map +1 -1
  17. package/dist/di/inject.js +2 -1
  18. package/dist/di/inject.js.map +1 -1
  19. package/dist/docs/BENCHMARKS.md +4 -4
  20. package/dist/docs/content/README.md +43 -44
  21. package/dist/docs/content/boundaries.mdx +138 -61
  22. package/dist/docs/content/cli/commands.mdx +151 -128
  23. package/dist/docs/content/cli/overview.mdx +61 -47
  24. package/dist/docs/content/cli/usage.mdx +107 -30
  25. package/dist/docs/content/config.mdx +145 -158
  26. package/dist/docs/content/controllers.mdx +193 -464
  27. package/dist/docs/content/cron.mdx +183 -69
  28. package/dist/docs/content/decorators.mdx +449 -154
  29. package/dist/docs/content/errors.mdx +202 -305
  30. package/dist/docs/content/exisjs-mcp.mdx +59 -49
  31. package/dist/docs/content/installation.mdx +243 -157
  32. package/dist/docs/content/introduction.mdx +220 -48
  33. package/dist/docs/content/middleware.mdx +171 -497
  34. package/dist/docs/content/modules.mdx +141 -83
  35. package/dist/docs/content/providers.mdx +216 -180
  36. package/dist/docs/content/requests.mdx +168 -488
  37. package/dist/docs/content/responses.mdx +200 -533
  38. package/dist/docs/content/routing.mdx +235 -598
  39. package/dist/docs/content/security.mdx +233 -57
  40. package/dist/docs/content/structure.mdx +151 -240
  41. package/dist/docs/content/swagger.mdx +192 -91
  42. package/dist/docs/content/testing.mdx +205 -74
  43. package/dist/docs/content/validation.mdx +479 -341
  44. package/dist/docs/reference/README.md +34 -2
  45. package/dist/docs/reference/config.mdx +277 -75
  46. package/dist/docs/reference/context.mdx +197 -106
  47. package/dist/docs/reference/cron.mdx +266 -152
  48. package/dist/docs/reference/database.mdx +135 -53
  49. package/dist/docs/reference/decorators.mdx +234 -116
  50. package/dist/docs/reference/di.mdx +146 -112
  51. package/dist/docs/reference/error.mdx +155 -178
  52. package/dist/docs/reference/exis.mdx +141 -70
  53. package/dist/docs/reference/logger.mdx +110 -126
  54. package/dist/docs/reference/middleware.mdx +235 -158
  55. package/dist/docs/reference/plugin.mdx +136 -64
  56. package/dist/docs/reference/router.mdx +275 -293
  57. package/dist/docs/reference/sanitize.mdx +91 -87
  58. package/dist/docs/reference/swagger.mdx +280 -100
  59. package/dist/docs/reference/testing.mdx +223 -89
  60. package/dist/docs/reference/validator.mdx +194 -206
  61. package/dist/lib/start-server.js +3 -8
  62. package/dist/lib/start-server.js.map +1 -1
  63. package/dist/module/module.d.ts +6 -3
  64. package/dist/module/module.d.ts.map +1 -1
  65. package/dist/module/module.js +17 -3
  66. package/dist/module/module.js.map +1 -1
  67. package/dist/router/controller-registrar.d.ts.map +1 -1
  68. package/dist/router/controller-registrar.js +26 -4
  69. package/dist/router/controller-registrar.js.map +1 -1
  70. package/dist/server/app.d.ts +3 -1
  71. package/dist/server/app.d.ts.map +1 -1
  72. package/dist/server/app.js +24 -8
  73. package/dist/server/app.js.map +1 -1
  74. package/dist/server/request.d.ts +2 -0
  75. package/dist/server/request.d.ts.map +1 -1
  76. package/dist/server/request.js +11 -0
  77. package/dist/server/request.js.map +1 -1
  78. package/dist/swagger/generator.d.ts.map +1 -1
  79. package/dist/swagger/generator.js +130 -10
  80. package/dist/swagger/generator.js.map +1 -1
  81. package/dist/telemetry/index.d.ts +12 -0
  82. package/dist/telemetry/index.d.ts.map +1 -0
  83. package/dist/telemetry/index.js +121 -0
  84. package/dist/telemetry/index.js.map +1 -0
  85. package/dist/testing/client.d.ts +1 -0
  86. package/dist/testing/client.d.ts.map +1 -1
  87. package/dist/testing/client.js +4 -1
  88. package/dist/testing/client.js.map +1 -1
  89. package/dist/testing/index.d.ts.map +1 -1
  90. package/dist/testing/index.js +11 -4
  91. package/dist/testing/index.js.map +1 -1
  92. package/dist/validator/builder.d.ts +26 -1
  93. package/dist/validator/builder.d.ts.map +1 -1
  94. package/dist/validator/builder.js +31 -0
  95. package/dist/validator/builder.js.map +1 -1
  96. package/dist/validator/engine.d.ts +23 -0
  97. package/dist/validator/engine.d.ts.map +1 -1
  98. package/dist/validator/engine.js +228 -31
  99. package/dist/validator/engine.js.map +1 -1
  100. package/dist/validator/index.d.ts +8 -4
  101. package/dist/validator/index.d.ts.map +1 -1
  102. package/dist/validator/index.js +4 -1
  103. package/dist/validator/index.js.map +1 -1
  104. package/dist/validator/tex-types.d.ts +15 -0
  105. package/dist/validator/tex-types.d.ts.map +1 -1
  106. package/dist/validator/tex-types.js +102 -0
  107. package/dist/validator/tex-types.js.map +1 -1
  108. package/dist/validator/tex.d.ts +1 -1
  109. package/dist/validator/tex.d.ts.map +1 -1
  110. package/dist/validator/tex.js +3 -1
  111. package/dist/validator/tex.js.map +1 -1
  112. package/dist/validator/types.d.ts +1 -0
  113. package/dist/validator/types.d.ts.map +1 -1
  114. package/package.json +46 -14
  115. package/dist/docs/content/dataloader.mdx +0 -170
  116. package/dist/docs/reference/dataloader.mdx +0 -91
@@ -1,169 +1,160 @@
1
1
  ---
2
- nextUrl: "/docs/gateways"
3
- nextTitle: "Gateways"
2
+ title: "Dependency Injection and Providers"
3
+ description: "Manage dependencies, services, provider lifecycles, injection scopes, and database integration using the ExisJS Inversion of Control container."
4
+ nextUrl: "/docs/modules"
5
+ nextTitle: "Modules"
4
6
  ---
5
7
 
6
- # Providers
8
+ # Dependency Injection and Providers
7
9
 
8
- Providers are a fundamental concept in ExisJS. Many of the basic classes may be treated as a provider: services, repositories, factories, helpers, and so on.
10
+ Providers in ExisJS represent classes and values managed by the Inversion of Control (IoC) Dependency Injection (DI) container. Services, repositories, configuration factories, and database clients can be registered as providers and injected into controllers, routes, and other services.
9
11
 
10
- The main idea of a provider is that it can be injected as a dependency; this means objects can create various relationships with each other, and the function of "wiring up" instances of objects can largely be delegated to the ExisJS runtime system.
11
-
12
- ## Services
12
+ ---
13
13
 
14
- Let's start by creating a simple `UsersService`. This service will be responsible for data storage and retrieval, and is designed to be used by the `UsersController`.
14
+ ## Defining Injectable Services
15
15
 
16
- To mark a class as a provider that the ExisJS Inversion of Control (IoC) container can manage, we use the `@Injectable()` decorator.
16
+ To make a class discoverable by the IoC container, annotate it with the `@Injectable()` decorator.
17
17
 
18
18
  ```typescript filename="src/http/users/service.ts"
19
19
  import { Injectable } from 'exisjs/decorators'
20
20
 
21
21
  export interface User {
22
- id: string;
23
- email: string;
22
+ id: string
23
+ name: string
24
+ email: string
24
25
  }
25
26
 
26
- @Injectable({ scope: 'singleton' })
27
+ @Injectable()
27
28
  export class UsersService {
28
- private readonly users: User[] = []
29
+ private users: User[] = []
29
30
 
30
- create(user: User) {
31
- this.users.push(user)
31
+ async findAll(): Promise<User[]> {
32
+ return this.users
32
33
  }
33
34
 
34
- findAll(): User[] {
35
- return this.users
35
+ async create(user: User): Promise<User> {
36
+ this.users.push(user)
37
+ return user
36
38
  }
37
39
  }
38
40
  ```
39
41
 
40
- Now that we have a service class to retrieve users, let's use it inside the `UsersController`. ExisJS provides a powerful `inject()` primitive that automatically resolves dependencies from the current context.
42
+ ---
41
43
 
42
- ```typescript filename="src/http/users/route.ts"
43
- import { Controller, Get, Post, Body } from 'exisjs/decorators'
44
- import { inject } from 'exisjs/di'
45
- import { UsersService, User } from './service'
44
+ ## Injecting Dependencies
46
45
 
47
- @Controller('users')
48
- export default class UsersController {
49
-
50
- // Clean, ergonomic dependency injection
51
- private usersService = inject(UsersService)
46
+ ExisJS supports three injection patterns:
47
+ 1. **Contextual Injection (`inject()`)**: Used in functional route handlers, middleware, and boundaries.
48
+ 2. **Constructor Injection**: Automatically resolves types from constructor parameters in OOP classes.
49
+ 3. **Property Field Injection (`@Inject()`)**: Injects dependencies directly into class properties.
52
50
 
53
- @Post()
54
- async create(@Body() user: User) {
55
- this.usersService.create(user)
56
- return { success: true }
57
- }
51
+ ### Functional Route Context (`inject()`)
58
52
 
59
- @Get()
60
- async findAll() {
61
- return this.usersService.findAll()
62
- }
63
- }
64
- ```
65
-
66
- <Info title="Note on Dependency Injection">
67
- ExisJS provides two elegant ways to inject dependencies:
68
- 1. **Property-Level / Functional Injection**: Use `inject(UsersService)` for concise, boilerplate-free injection.
69
- 2. **Constructor Injection**: In OOP controllers and services, ExisJS inspects TypeScript's emitted `design:paramtypes` and `INJECTABLE_REGISTRY` to automatically inject service instances into constructor parameters (`constructor(private usersService: UsersService)`) without requiring explicit `@Inject()` decorators on every parameter!
70
- </Info>
53
+ In the functional paradigm, call `inject(Token)` inside route handlers, middleware, or boundaries. The container resolves the dependency from the active execution context:
71
54
 
72
- ### Extended: Manual Resolution
73
- If you are operating completely outside of an ExisJS controller or route (for example, in a background worker, CRON job, or a raw script like a database seeder), you can manually resolve dependencies as an escape hatch via the app container:
74
-
75
- ```typescript filename="scripts/seed.ts"
76
- import { getActiveApp } from 'exisjs/app'
55
+ ```typescript filename="src/http/users/route.ts"
56
+ import { controller, route } from 'exisjs/router'
57
+ import { inject } from 'exisjs/di'
77
58
  import { UsersService } from './service'
78
59
 
79
- const app = getActiveApp()
80
- const usersService = app.container.resolve(UsersService)
60
+ export default controller({
61
+ list: route.get('/', {
62
+ async handle({ res }) {
63
+ const usersService = inject(UsersService)
64
+ const users = await usersService.findAll()
65
+ res.json({ users })
66
+ },
67
+ }),
68
+ })
81
69
  ```
82
70
 
83
- ## Service Lifecycle Hooks
71
+ ### OOP Constructor and Property Injection
84
72
 
85
- ExisJS `@Injectable()` services can implement lifecycle interfaces. When the application starts up or shuts down gracefully, ExisJS automatically triggers these hooks on all managed singletons:
73
+ In class-based controllers and services, dependencies declared in the constructor are automatically resolved using TypeScript's emitted metadata (`design:paramtypes`):
86
74
 
87
- ```typescript filename="src/http/database/service.ts"
88
- import { Injectable, type OnInit, type OnDestroy } from 'exisjs/decorators'
75
+ ```typescript filename="src/http/users/route.ts"
76
+ import { Controller, Get, Post, Body } from 'exisjs/decorators'
77
+ import { Inject, Optional } from 'exisjs/di'
78
+ import { UsersService, User } from './service'
79
+ import { MetricsService } from '../../services/metrics.service'
89
80
 
90
- @Injectable()
91
- export class DatabaseService implements OnInit, OnDestroy {
92
- async onInit(): Promise<void> {
93
- // Invoked automatically during app startup (app.create / app.listen)
94
- await this.connectPool()
81
+ @Controller('/users')
82
+ export default class UsersController {
83
+ // Property injection with an optional token
84
+ @Inject('METRICS_SERVICE')
85
+ @Optional()
86
+ private metrics?: MetricsService
87
+
88
+ // Automatic constructor injection
89
+ constructor(private usersService: UsersService) {}
90
+
91
+ @Get('/')
92
+ async list() {
93
+ this.metrics?.track('get_users')
94
+ return this.usersService.findAll()
95
95
  }
96
96
 
97
- async onDestroy(): Promise<void> {
98
- // Invoked automatically during graceful termination (app.close / SIGINT / SIGTERM)
99
- await this.drainPool()
97
+ @Post('/')
98
+ async create(@Body() user: User) {
99
+ return this.usersService.create(user)
100
100
  }
101
101
  }
102
102
  ```
103
103
 
104
- ## Service Method Enhancements
105
-
106
- You can supercharge service methods with off-heap caching, automated transaction wrapping, and SLA performance profiling:
107
-
108
- ```typescript filename="src/http/users/service.ts"
109
- import { Injectable, Cacheable, CacheEvict, Transactional, Profile } from 'exisjs/decorators'
104
+ ---
110
105
 
111
- @Injectable()
112
- export class UsersService {
113
- constructor(private db: DatabaseService) {}
106
+ ## Provider Tokens and Custom Providers
114
107
 
115
- // Caches return values off-heap in Rust LRU cache
116
- @Cacheable({ ttl: 300, key: (id: string) => `user:${id}` })
117
- @Profile({ thresholdMs: 100, warnSlow: true })
118
- async findById(id: string) {
119
- return this.db.query('SELECT * FROM users WHERE id = $1', [id])
120
- }
108
+ By default, passing a class constructor to the container creates an instance of that class. You can also register custom providers using string or symbol tokens, explicit values, factory functions with injected dependencies, or token aliases via `app.provide()` or module provider arrays.
121
109
 
122
- // Runs in an atomic transaction; automatically rolls back on error
123
- @Transactional()
124
- @CacheEvict({ key: (id: string) => `user:${id}` })
125
- async updateUser(id: string, data: any) {
126
- return this.db.update('users', id, data)
127
- }
128
- }
129
- ```
110
+ ### Provider Types
130
111
 
131
- ## Custom Providers
112
+ | Provider Form | Syntax | Description |
113
+ | :--- | :--- | :--- |
114
+ | **Class Provider** | `UsersService` or `{ provide: 'IUsersService', useClass: UsersService }` | Instantiates and manages a class constructor. |
115
+ | **Value Provider** | `{ provide: 'CONFIG', useValue: value }` | Injects a static value, configuration object, or third-party client. |
116
+ | **Factory Provider** | `{ provide: 'DB', useFactory: (cfg) => connect(cfg), inject: ['CONFIG'] }` | Evaluates a factory function with automatically resolved dependencies. |
117
+ | **Existing / Alias Provider** | `{ provide: 'DB_ALIAS', useExisting: 'DB' }` | Maps an alias token directly to another registered provider token. |
132
118
 
133
- ExisJS's DI container is flexible. By default, when you register a class, ExisJS instantiates it for you. However, you often need more control. You can register standard classes, raw values, factories, or aliased classes natively using `app.provide()`.
119
+ ### Registering Custom Providers in Application Startup
134
120
 
135
- ### Functional Paradigm
121
+ #### Functional Paradigm
136
122
 
137
123
  ```typescript filename="src/http/server.ts"
138
- import exis from 'exisjs'
124
+ import { exis } from 'exisjs'
139
125
  import { UsersService } from './users/service'
140
126
 
141
127
  export default exis({
142
128
  async onStart(app) {
143
- // 1. Standard Class Provider
129
+ // 1. Standard class provider
144
130
  app.provide(UsersService, UsersService)
145
131
 
146
- // 2. Value Provider
147
- app.provide('DATABASE_URL', {
148
- useValue: 'postgres://localhost:5432/mydb'
132
+ // 2. Value provider with single-object syntax
133
+ app.provide({
134
+ provide: 'DATABASE_URL',
135
+ useValue: process.env.DATABASE_URL || 'postgres://localhost:5432/mydb',
149
136
  })
150
137
 
151
- // 3. Factory Provider
152
- app.provide('LoggerService', {
153
- useFactory: () => {
154
- return { log: (msg: string) => console.log(`[Logger]: ${msg}`) }
155
- }
138
+ // 3. Factory provider with dependency injection
139
+ app.provide({
140
+ provide: 'DATABASE_CLIENT',
141
+ useFactory: (dbUrl: string) => ({
142
+ url: dbUrl,
143
+ query: (sql: string) => `Executed ${sql}`,
144
+ }),
145
+ inject: ['DATABASE_URL'],
156
146
  })
157
147
 
158
- // 4. Aliased Class Provider
159
- app.provide('AuthService', {
160
- useClass: UsersService
148
+ // 4. Existing alias provider
149
+ app.provide({
150
+ provide: 'DB',
151
+ useExisting: 'DATABASE_CLIENT',
161
152
  })
162
- }
153
+ },
163
154
  })
164
155
  ```
165
156
 
166
- ### Class-Based (OOP) Paradigm
157
+ #### Class-Based (OOP) Paradigm
167
158
 
168
159
  ```typescript filename="src/http/server.ts"
169
160
  import { Server } from 'exisjs/decorators'
@@ -171,56 +162,50 @@ import { UsersService } from './users/service'
171
162
 
172
163
  @Server({
173
164
  providers: [
174
- // 1. Standard Class Provider
175
165
  UsersService,
176
- // 2. Value Provider
177
- ['DATABASE_URL', { useValue: 'postgres://localhost:5432/mydb' }],
178
- // 3. Factory Provider
179
- ['LoggerService', { useFactory: () => ({ log: (msg: string) => console.log(`[Logger]: ${msg}`) }) }],
180
- // 4. Aliased Class Provider
181
- ['AuthService', { useClass: UsersService }]
182
- ]
166
+ { provide: 'DATABASE_URL', useValue: 'postgres://localhost:5432/mydb' },
167
+ {
168
+ provide: 'DATABASE_CLIENT',
169
+ useFactory: (dbUrl: string) => ({ url: dbUrl }),
170
+ inject: ['DATABASE_URL'],
171
+ },
172
+ { provide: 'DB', useExisting: 'DATABASE_CLIENT' },
173
+ { provide: 'IUsersService', useClass: UsersService },
174
+ ],
183
175
  })
184
176
  export default class RootServer {}
185
177
  ```
186
178
 
187
- ### Constructor & Property Injection (`@Inject`, `@Optional`, and `forwardRef`)
188
-
189
- In addition to property-level `inject()`, ExisJS provides full OOP constructor and property field injection with `@Inject(token)`, `@Optional()`, and `forwardRef()`:
179
+ ---
190
180
 
191
- ```typescript filename="src/http/users/route.ts"
192
- import { Controller, Get, Param } from 'exisjs/decorators'
193
- import { Inject, Optional, forwardRef } from 'exisjs/di'
194
- import { UsersService } from './service'
195
- import { ConfigService } from '../config/service'
181
+ ## Injection Scopes
196
182
 
197
- @Controller('/users')
198
- export default class UsersController {
199
- // 1. Property-level decorator injection
200
- @Inject(ConfigService)
201
- private config!: ConfigService
183
+ ExisJS supports three provider lifetime scopes configured via `@Injectable({ scope })` or the `scope` option in custom provider definitions:
202
184
 
203
- // 2. Optional token injection
204
- @Inject('METRICS')
205
- @Optional()
206
- private metrics?: any
185
+ | Scope | Identifier | Lifetime Behavior |
186
+ | :--- | :--- | :--- |
187
+ | **Singleton (Default)** | `'singleton'` | A single instance is cached globally and shared across all incoming requests. |
188
+ | **Request** | `'request'` | A new instance is instantiated for each incoming HTTP request and destroyed when the request completes. |
189
+ | **Transient** | `'transient'` | A new instance is created every time the dependency is injected or resolved. |
207
190
 
208
- // 3. Constructor injection with forwardRef for circular dependencies
209
- constructor(
210
- private usersService: UsersService,
211
- @Inject(forwardRef(() => UsersService)) private circularRef: UsersService
212
- ) {}
191
+ ```typescript filename="src/services/context.service.ts"
192
+ import { Injectable } from 'exisjs/decorators'
213
193
 
214
- @Get('/:id')
215
- async getUser(@Param('id') id: string) {
216
- return this.usersService.findAll()
217
- }
194
+ @Injectable({ scope: 'request' })
195
+ export class RequestContextService {
196
+ public readonly requestId = crypto.randomUUID()
197
+ public readonly timestamp = new Date()
218
198
  }
219
199
  ```
220
200
 
221
- ### Forward References (`forwardRef`) & Circular Dependencies
201
+ > [!WARNING]
202
+ > **Request Scope Lifecycle**: Request-scoped providers can only be resolved within an active HTTP request context. Attempting to resolve a request-scoped provider during boot scripts, background cron tasks, or standalone worker processes will throw a container error.
203
+
204
+ ---
205
+
206
+ ## Circular Dependencies (`forwardRef`)
222
207
 
223
- When two services mutually depend on each other (`ServiceA` requires `ServiceB` and `ServiceB` requires `ServiceA`), circular references normally lead to `ReferenceError` during module evaluation. ExisJS solves this using `forwardRef(() => Service)` alongside lazy ES6 proxy resolution:
208
+ When two providers depend on each other, direct TypeScript constructor references cause `ReferenceError` during module loading. Wrap the injection token in `forwardRef()` to resolve circular dependencies using lazy proxy instances:
224
209
 
225
210
  ```typescript filename="src/services/order.service.ts"
226
211
  import { Injectable } from 'exisjs/decorators'
@@ -231,45 +216,104 @@ import { InventoryService } from './inventory.service'
231
216
  export class OrderService {
232
217
  constructor(
233
218
  @Inject(forwardRef(() => InventoryService))
234
- private inventory: InventoryService
219
+ private inventoryService: InventoryService
235
220
  ) {}
236
221
 
237
- processOrder(id: string) {
238
- return this.inventory.reserveStock(id)
222
+ async processOrder(orderId: string) {
223
+ return this.inventoryService.reserveStock(orderId)
239
224
  }
240
225
  }
241
226
  ```
242
227
 
228
+ ```typescript filename="src/services/inventory.service.ts"
229
+ import { Injectable } from 'exisjs/decorators'
230
+ import { Inject, forwardRef } from 'exisjs/di'
231
+ import { OrderService } from './order.service'
243
232
 
244
- ## Injection Scopes
233
+ @Injectable()
234
+ export class InventoryService {
235
+ constructor(
236
+ @Inject(forwardRef(() => OrderService))
237
+ private orderService: OrderService
238
+ ) {}
245
239
 
246
- For people coming from different programming language backgrounds, it might be unexpected to learn that in ExisJS, almost everything is shared across incoming requests. We have a connection pool to the database, singleton services with global state, etc. This is because Node.js doesn't follow the request/response Multi-Threaded Stateless Model.
240
+ async reserveStock(orderId: string) {
241
+ // Inventory reservation logic
242
+ return { reserved: true, orderId }
243
+ }
244
+ }
245
+ ```
247
246
 
248
- However, there are use-cases when request-based or transient lifetime is desired. ExisJS supports three provider scopes in the `@Injectable()` decorator and `app.provide()`:
247
+ ---
249
248
 
250
- - **Singleton (Default)**: A single instance of the provider is shared across the entire application.
251
- - **Request**: A new instance of the provider is created exclusively for each incoming request, and garbage collected afterward.
252
- - **Transient**: A brand-new instance is created every single time the dependency is requested/injected.
249
+ ## Service Lifecycle Hooks
253
250
 
254
- ```typescript
255
- @Injectable({ scope: 'transient' })
256
- export class OperationContext {
257
- public readonly id = crypto.randomUUID()
251
+ Providers can implement `OnInit` and `OnDestroy` lifecycle interfaces to handle asynchronous resource initialization and graceful cleanup:
252
+
253
+ ```typescript filename="src/services/database.service.ts"
254
+ import { Injectable, type OnInit, type OnDestroy } from 'exisjs/decorators'
255
+
256
+ @Injectable()
257
+ export class DatabaseService implements OnInit, OnDestroy {
258
+ async onInit(): Promise<void> {
259
+ // Triggered automatically during application startup
260
+ await this.connectConnectionPool()
261
+ }
262
+
263
+ async onDestroy(): Promise<void> {
264
+ // Triggered automatically during graceful shutdown (SIGINT / SIGTERM)
265
+ await this.closeConnectionPool()
266
+ }
267
+
268
+ private async connectConnectionPool() {
269
+ // Connection logic
270
+ }
271
+
272
+ private async closeConnectionPool() {
273
+ // Teardown logic
274
+ }
258
275
  }
259
276
  ```
260
277
 
261
- > [!WARNING]
262
- > **Request Scope Constraints**: A provider marked as `scope: 'request'` **must** be resolved within the lifecycle of an active HTTP request. If you try to `inject()` or resolve a request-scoped provider during application startup, inside a background queue worker, or from a cron job, the container will throw a runtime error.
278
+ ---
279
+
280
+ ## Method Decorator Enhancements
281
+
282
+ ExisJS provides method decorators to add caching, transactions, and execution profiling to service methods:
283
+
284
+ ```typescript filename="src/services/roles.service.ts"
285
+ import { Injectable, Cacheable, CacheEvict, Transactional, Profile } from 'exisjs/decorators'
286
+ import { DatabaseService } from './database.service'
287
+
288
+ @Injectable()
289
+ export class RolesService {
290
+ constructor(private db: DatabaseService) {}
291
+
292
+ // Caches method results using an LRU cache
293
+ @Cacheable({ ttl: 300, key: (id: string) => `role:${id}` })
294
+ @Profile({ thresholdMs: 50, warnSlow: true })
295
+ async findById(id: string) {
296
+ return this.db.query('SELECT * FROM roles WHERE id = $1', [id])
297
+ }
298
+
299
+ // Wraps operations in an atomic database transaction and evicts cache keys
300
+ @Transactional()
301
+ @CacheEvict({ key: (id: string) => `role:${id}` })
302
+ async updateRole(id: string, name: string) {
303
+ return this.db.query('UPDATE roles SET name = $1 WHERE id = $2', [name, id])
304
+ }
305
+ }
306
+ ```
263
307
 
264
308
  ---
265
309
 
266
- ## Database Providers & Lifecycle Interop (`exisjs/database`)
310
+ ## Database Providers & Lifecycle Management (`exisjs/database`)
267
311
 
268
- ExisJS provides first-party lifecycle management and transaction coordination for databases (PostgreSQL, SQLite, MySQL, MongoDB, Prisma, Drizzle, Mongoose).
312
+ The `exisjs/database` package coordinates database connections, health checks, transactions, and graceful connection draining.
269
313
 
270
- ### 1. Registering Database Lifecycle (`registerDatabase`)
314
+ ### 1. Connection Lifecycle Registration (`registerDatabase`)
271
315
 
272
- Registering your database with `registerDatabase` ensures connections are automatically connected on boot and safely closed with zero leaked sockets when the server receives `SIGTERM` / `SIGINT`:
316
+ Registering databases with `registerDatabase` ensures connections are initialized during server boot and drained gracefully on application shutdown without lingering open sockets:
273
317
 
274
318
  ```typescript filename="src/http/server.ts"
275
319
  import { exis } from 'exisjs'
@@ -278,7 +322,6 @@ import mongoose from 'mongoose'
278
322
 
279
323
  export default exis({
280
324
  async onStart(app) {
281
- // Automatically handles startup and graceful shutdown
282
325
  registerDatabase(app, {
283
326
  name: 'mongodb',
284
327
  connect: async () => {
@@ -291,25 +334,24 @@ export default exis({
291
334
  return mongoose.connection.readyState === 1
292
335
  },
293
336
  })
294
- }
337
+ },
295
338
  })
296
339
  ```
297
340
 
298
341
  ### 2. Universal Transaction Runner (`withTransaction`)
299
342
 
300
- `withTransaction` provides a duck-typed transaction runner that automatically coordinates transaction lifecycles, commits, aborts, and session cleanups across different ORMs:
343
+ `withTransaction` provides a unified transaction wrapper compatible with **Mongoose**, **Prisma**, **Drizzle**, and generic SQL connection clients:
301
344
 
302
- ```typescript filename="src/http/orders/service.ts"
345
+ ```typescript filename="src/services/order.service.ts"
303
346
  import { withTransaction } from 'exisjs/database'
304
347
  import mongoose from 'mongoose'
305
- import { Order } from '@/models/Order'
306
- import { Inventory } from '@/models/Inventory'
348
+ import { Order } from '../models/order.model'
349
+ import { Inventory } from '../models/inventory.model'
307
350
 
308
351
  export async function placeOrder(customerId: string, items: { sku: string; qty: number }[]) {
309
352
  return withTransaction(mongoose, async (session) => {
310
- // Automatically attaches session to queries and commits/aborts on error
311
353
  const order = await Order.create([{ customerId, items }], { session })
312
-
354
+
313
355
  for (const item of items) {
314
356
  const updated = await Inventory.findOneAndUpdate(
315
357
  { sku: item.sku, stock: { $gte: item.qty } },
@@ -317,7 +359,7 @@ export async function placeOrder(customerId: string, items: { sku: string; qty:
317
359
  { session, returnDocument: 'after' }
318
360
  )
319
361
  if (!updated) {
320
- throw new Error(`Insufficient stock for SKU: ${item.sku}`)
362
+ throw new Error(`Insufficient inventory for SKU: ${item.sku}`)
321
363
  }
322
364
  }
323
365
 
@@ -326,27 +368,21 @@ export async function placeOrder(customerId: string, items: { sku: string; qty:
326
368
  }
327
369
  ```
328
370
 
329
- Works identically with Prisma (`prisma.$transaction`) and Drizzle (`db.transaction`).
371
+ ### 3. Modern Mongoose Interoperability
330
372
 
331
- ### 3. Modern Mongoose 8/9 Interop & Connection Pooling
373
+ Use `mongoPoolOptions` and `modernUpdateOptions` to standardize connection pools and query behavior:
332
374
 
333
- ExisJS exports helpers to prevent deprecation warnings in modern Mongoose:
334
-
335
- ```typescript filename="src/models/user.ts"
375
+ ```typescript filename="src/models/user.model.ts"
336
376
  import { modernUpdateOptions, mongoPoolOptions } from 'exisjs/database'
337
377
  import mongoose from 'mongoose'
338
378
 
339
- // Cloud-hardened pool options (maxPoolSize, serverSelectionTimeoutMS, etc.)
340
- export const poolConfig = mongoPoolOptions({ maxPoolSize: 50 })
379
+ export const poolOptions = mongoPoolOptions({ maxPoolSize: 50 })
341
380
 
342
- // Automatically maps legacy { new: true } to { returnDocument: 'after' }
343
- export async function updateUsername(id: string, name: string) {
344
- return User.findByIdAndUpdate(
381
+ export async function updateUser(id: string, name: string) {
382
+ return mongoose.model('User').findByIdAndUpdate(
345
383
  id,
346
384
  { $set: { name } },
347
385
  modernUpdateOptions({ returnDocument: 'after' })
348
386
  )
349
387
  }
350
388
  ```
351
-
352
-