exisjs 0.7.11 → 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.
- package/README.md +195 -39
- package/dist/cli/commands/dev.d.ts +4 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +136 -32
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/run.d.ts +7 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +99 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/index.js +29 -13
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/manifest.d.ts.map +1 -1
- package/dist/cli/manifest.js +18 -3
- package/dist/cli/manifest.js.map +1 -1
- package/dist/cli/routes.d.ts +6 -0
- package/dist/cli/routes.d.ts.map +1 -1
- package/dist/cli/routes.js +224 -135
- package/dist/cli/routes.js.map +1 -1
- package/dist/config/config.d.ts +2 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +30 -9
- package/dist/config/types.d.ts.map +1 -1
- package/dist/database/lifecycle.d.ts +6 -1
- package/dist/database/lifecycle.d.ts.map +1 -1
- package/dist/database/lifecycle.js +15 -4
- package/dist/database/lifecycle.js.map +1 -1
- package/dist/decorators/params.d.ts +21 -0
- package/dist/decorators/params.d.ts.map +1 -1
- package/dist/decorators/params.js +21 -1
- package/dist/decorators/params.js.map +1 -1
- package/dist/di/container.d.ts +15 -6
- package/dist/di/container.d.ts.map +1 -1
- package/dist/di/container.js +26 -3
- package/dist/di/container.js.map +1 -1
- package/dist/di/index.d.ts +1 -1
- package/dist/di/index.d.ts.map +1 -1
- package/dist/di/index.js.map +1 -1
- package/dist/di/inject.d.ts +2 -1
- package/dist/di/inject.d.ts.map +1 -1
- package/dist/di/inject.js +2 -1
- package/dist/di/inject.js.map +1 -1
- package/dist/docs/BENCHMARKS.md +4 -4
- package/dist/docs/content/README.md +43 -44
- package/dist/docs/content/boundaries.mdx +141 -48
- package/dist/docs/content/cli/commands.mdx +161 -107
- package/dist/docs/content/cli/overview.mdx +61 -46
- package/dist/docs/content/cli/usage.mdx +107 -30
- package/dist/docs/content/config.mdx +145 -147
- package/dist/docs/content/controllers.mdx +193 -464
- package/dist/docs/content/cron.mdx +183 -69
- package/dist/docs/content/decorators.mdx +449 -154
- package/dist/docs/content/errors.mdx +202 -305
- package/dist/docs/content/exisjs-mcp.mdx +59 -49
- package/dist/docs/content/installation.mdx +243 -157
- package/dist/docs/content/introduction.mdx +220 -48
- package/dist/docs/content/middleware.mdx +171 -497
- package/dist/docs/content/modules.mdx +141 -83
- package/dist/docs/content/providers.mdx +216 -180
- package/dist/docs/content/requests.mdx +168 -488
- package/dist/docs/content/responses.mdx +200 -533
- package/dist/docs/content/routing.mdx +239 -558
- package/dist/docs/content/security.mdx +233 -57
- package/dist/docs/content/structure.mdx +151 -240
- package/dist/docs/content/swagger.mdx +192 -91
- package/dist/docs/content/testing.mdx +205 -74
- package/dist/docs/content/validation.mdx +479 -341
- package/dist/docs/reference/README.md +34 -2
- package/dist/docs/reference/config.mdx +277 -73
- package/dist/docs/reference/context.mdx +197 -106
- package/dist/docs/reference/cron.mdx +266 -152
- package/dist/docs/reference/database.mdx +135 -53
- package/dist/docs/reference/decorators.mdx +234 -116
- package/dist/docs/reference/di.mdx +146 -112
- package/dist/docs/reference/error.mdx +155 -178
- package/dist/docs/reference/exis.mdx +141 -70
- package/dist/docs/reference/logger.mdx +110 -126
- package/dist/docs/reference/middleware.mdx +235 -158
- package/dist/docs/reference/plugin.mdx +136 -64
- package/dist/docs/reference/router.mdx +277 -251
- package/dist/docs/reference/sanitize.mdx +91 -87
- package/dist/docs/reference/swagger.mdx +280 -100
- package/dist/docs/reference/testing.mdx +223 -89
- package/dist/docs/reference/validator.mdx +194 -206
- package/dist/lib/inspect-routes.d.ts +2 -0
- package/dist/lib/inspect-routes.d.ts.map +1 -0
- package/dist/lib/inspect-routes.js +148 -0
- package/dist/lib/inspect-routes.js.map +1 -0
- package/dist/lib/start-server.js +13 -8
- package/dist/lib/start-server.js.map +1 -1
- package/dist/module/module.d.ts +6 -3
- package/dist/module/module.d.ts.map +1 -1
- package/dist/module/module.js +17 -3
- package/dist/module/module.js.map +1 -1
- package/dist/router/controller-registrar.d.ts.map +1 -1
- package/dist/router/controller-registrar.js +26 -4
- package/dist/router/controller-registrar.js.map +1 -1
- package/dist/router/scanner/cron-mounter.d.ts.map +1 -1
- package/dist/router/scanner/cron-mounter.js +9 -2
- package/dist/router/scanner/cron-mounter.js.map +1 -1
- package/dist/server/app.d.ts +3 -1
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/app.js +29 -8
- package/dist/server/app.js.map +1 -1
- package/dist/server/define.d.ts.map +1 -1
- package/dist/server/define.js +5 -0
- package/dist/server/define.js.map +1 -1
- package/dist/server/request.d.ts +2 -0
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +11 -0
- package/dist/server/request.js.map +1 -1
- package/dist/swagger/generator.d.ts.map +1 -1
- package/dist/swagger/generator.js +130 -10
- package/dist/swagger/generator.js.map +1 -1
- package/dist/telemetry/index.d.ts +12 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +121 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/testing/client.d.ts +1 -0
- package/dist/testing/client.d.ts.map +1 -1
- package/dist/testing/client.js +4 -1
- package/dist/testing/client.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +20 -4
- package/dist/testing/index.js.map +1 -1
- package/dist/validator/builder.d.ts +26 -1
- package/dist/validator/builder.d.ts.map +1 -1
- package/dist/validator/builder.js +31 -0
- package/dist/validator/builder.js.map +1 -1
- package/dist/validator/engine.d.ts +23 -0
- package/dist/validator/engine.d.ts.map +1 -1
- package/dist/validator/engine.js +228 -31
- package/dist/validator/engine.js.map +1 -1
- package/dist/validator/index.d.ts +8 -4
- package/dist/validator/index.d.ts.map +1 -1
- package/dist/validator/index.js +4 -1
- package/dist/validator/index.js.map +1 -1
- package/dist/validator/tex-types.d.ts +15 -0
- package/dist/validator/tex-types.d.ts.map +1 -1
- package/dist/validator/tex-types.js +102 -0
- package/dist/validator/tex-types.js.map +1 -1
- package/dist/validator/tex.d.ts +1 -1
- package/dist/validator/tex.d.ts.map +1 -1
- package/dist/validator/tex.js +3 -1
- package/dist/validator/tex.js.map +1 -1
- package/dist/validator/types.d.ts +1 -0
- package/dist/validator/types.d.ts.map +1 -1
- package/package.json +47 -14
- package/dist/docs/content/dataloader.mdx +0 -170
- package/dist/docs/reference/dataloader.mdx +0 -91
|
@@ -1,169 +1,160 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
## Services
|
|
12
|
+
---
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
## Defining Injectable Services
|
|
15
15
|
|
|
16
|
-
To
|
|
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
|
-
|
|
22
|
+
id: string
|
|
23
|
+
name: string
|
|
24
|
+
email: string
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
@Injectable(
|
|
27
|
+
@Injectable()
|
|
27
28
|
export class UsersService {
|
|
28
|
-
private
|
|
29
|
+
private users: User[] = []
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
this.users
|
|
31
|
+
async findAll(): Promise<User[]> {
|
|
32
|
+
return this.users
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
async create(user: User): Promise<User> {
|
|
36
|
+
this.users.push(user)
|
|
37
|
+
return user
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
```
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
---
|
|
41
43
|
|
|
42
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
54
|
-
async create(@Body() user: User) {
|
|
55
|
-
this.usersService.create(user)
|
|
56
|
-
return { success: true }
|
|
57
|
-
}
|
|
51
|
+
### Functional Route Context (`inject()`)
|
|
58
52
|
|
|
59
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
|
|
71
|
+
### OOP Constructor and Property Injection
|
|
84
72
|
|
|
85
|
-
|
|
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/
|
|
88
|
-
import {
|
|
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
|
-
@
|
|
91
|
-
export class
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
@Post('/')
|
|
98
|
+
async create(@Body() user: User) {
|
|
99
|
+
return this.usersService.create(user)
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
|
|
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
|
-
|
|
112
|
-
export class UsersService {
|
|
113
|
-
constructor(private db: DatabaseService) {}
|
|
106
|
+
## Provider Tokens and Custom Providers
|
|
114
107
|
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
+
### Registering Custom Providers in Application Startup
|
|
134
120
|
|
|
135
|
-
|
|
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
|
|
129
|
+
// 1. Standard class provider
|
|
144
130
|
app.provide(UsersService, UsersService)
|
|
145
131
|
|
|
146
|
-
// 2. Value
|
|
147
|
-
app.provide(
|
|
148
|
-
|
|
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
|
|
152
|
-
app.provide(
|
|
153
|
-
|
|
154
|
-
|
|
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.
|
|
159
|
-
app.provide(
|
|
160
|
-
|
|
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
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
209
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
219
|
+
private inventoryService: InventoryService
|
|
235
220
|
) {}
|
|
236
221
|
|
|
237
|
-
processOrder(
|
|
238
|
-
return this.
|
|
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
|
-
|
|
233
|
+
@Injectable()
|
|
234
|
+
export class InventoryService {
|
|
235
|
+
constructor(
|
|
236
|
+
@Inject(forwardRef(() => OrderService))
|
|
237
|
+
private orderService: OrderService
|
|
238
|
+
) {}
|
|
245
239
|
|
|
246
|
-
|
|
240
|
+
async reserveStock(orderId: string) {
|
|
241
|
+
// Inventory reservation logic
|
|
242
|
+
return { reserved: true, orderId }
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
247
246
|
|
|
248
|
-
|
|
247
|
+
---
|
|
249
248
|
|
|
250
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
262
|
-
|
|
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
|
|
310
|
+
## Database Providers & Lifecycle Management (`exisjs/database`)
|
|
267
311
|
|
|
268
|
-
|
|
312
|
+
The `exisjs/database` package coordinates database connections, health checks, transactions, and graceful connection draining.
|
|
269
313
|
|
|
270
|
-
### 1.
|
|
314
|
+
### 1. Connection Lifecycle Registration (`registerDatabase`)
|
|
271
315
|
|
|
272
|
-
Registering
|
|
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
|
|
343
|
+
`withTransaction` provides a unified transaction wrapper compatible with **Mongoose**, **Prisma**, **Drizzle**, and generic SQL connection clients:
|
|
301
344
|
|
|
302
|
-
```typescript filename="src/
|
|
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 '
|
|
306
|
-
import { Inventory } from '
|
|
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
|
|
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
|
-
|
|
371
|
+
### 3. Modern Mongoose Interoperability
|
|
330
372
|
|
|
331
|
-
|
|
373
|
+
Use `mongoPoolOptions` and `modernUpdateOptions` to standardize connection pools and query behavior:
|
|
332
374
|
|
|
333
|
-
|
|
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
|
-
|
|
340
|
-
export const poolConfig = mongoPoolOptions({ maxPoolSize: 50 })
|
|
379
|
+
export const poolOptions = mongoPoolOptions({ maxPoolSize: 50 })
|
|
341
380
|
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|