@onroad/core 4.0.0-alpha.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 +670 -0
- package/dist/OnRoadExpress.d.ts +59 -0
- package/dist/OnRoadExpress.d.ts.map +1 -0
- package/dist/OnRoadExpress.js +320 -0
- package/dist/OnRoadExpress.js.map +1 -0
- package/dist/container/Container.d.ts +53 -0
- package/dist/container/Container.d.ts.map +1 -0
- package/dist/container/Container.js +175 -0
- package/dist/container/Container.js.map +1 -0
- package/dist/container/index.d.ts +3 -0
- package/dist/container/index.d.ts.map +1 -0
- package/dist/container/index.js +2 -0
- package/dist/container/index.js.map +1 -0
- package/dist/context/RequestContext.d.ts +10 -0
- package/dist/context/RequestContext.d.ts.map +1 -0
- package/dist/context/RequestContext.js +11 -0
- package/dist/context/RequestContext.js.map +1 -0
- package/dist/core/AbstractController.d.ts +20 -0
- package/dist/core/AbstractController.d.ts.map +1 -0
- package/dist/core/AbstractController.js +17 -0
- package/dist/core/AbstractController.js.map +1 -0
- package/dist/core/AbstractRepository.d.ts +8 -0
- package/dist/core/AbstractRepository.d.ts.map +1 -0
- package/dist/core/AbstractRepository.js +3 -0
- package/dist/core/AbstractRepository.js.map +1 -0
- package/dist/core/AbstractService.d.ts +12 -0
- package/dist/core/AbstractService.d.ts.map +1 -0
- package/dist/core/AbstractService.js +14 -0
- package/dist/core/AbstractService.js.map +1 -0
- package/dist/core/MongooseRepository.d.ts +19 -0
- package/dist/core/MongooseRepository.d.ts.map +1 -0
- package/dist/core/MongooseRepository.js +48 -0
- package/dist/core/MongooseRepository.js.map +1 -0
- package/dist/core/RequestEventBus.d.ts +18 -0
- package/dist/core/RequestEventBus.d.ts.map +1 -0
- package/dist/core/RequestEventBus.js +43 -0
- package/dist/core/RequestEventBus.js.map +1 -0
- package/dist/core/Sentinel.d.ts +19 -0
- package/dist/core/Sentinel.d.ts.map +1 -0
- package/dist/core/Sentinel.js +56 -0
- package/dist/core/Sentinel.js.map +1 -0
- package/dist/core/SequelizeRepository.d.ts +19 -0
- package/dist/core/SequelizeRepository.d.ts.map +1 -0
- package/dist/core/SequelizeRepository.js +48 -0
- package/dist/core/SequelizeRepository.js.map +1 -0
- package/dist/core/index.d.ts +11 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +8 -0
- package/dist/core/index.js.map +1 -0
- package/dist/database/ConnectionManager.d.ts +26 -0
- package/dist/database/ConnectionManager.d.ts.map +1 -0
- package/dist/database/ConnectionManager.js +17 -0
- package/dist/database/ConnectionManager.js.map +1 -0
- package/dist/database/MongoConnectionManager.d.ts +8 -0
- package/dist/database/MongoConnectionManager.d.ts.map +1 -0
- package/dist/database/MongoConnectionManager.js +47 -0
- package/dist/database/MongoConnectionManager.js.map +1 -0
- package/dist/database/SequelizeConnectionManager.d.ts +13 -0
- package/dist/database/SequelizeConnectionManager.d.ts.map +1 -0
- package/dist/database/SequelizeConnectionManager.js +58 -0
- package/dist/database/SequelizeConnectionManager.js.map +1 -0
- package/dist/database/index.d.ts +6 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +4 -0
- package/dist/database/index.js.map +1 -0
- package/dist/entity/EntityRegistry.d.ts +9 -0
- package/dist/entity/EntityRegistry.d.ts.map +1 -0
- package/dist/entity/EntityRegistry.js +114 -0
- package/dist/entity/EntityRegistry.js.map +1 -0
- package/dist/entity/decorators.d.ts +62 -0
- package/dist/entity/decorators.d.ts.map +1 -0
- package/dist/entity/decorators.js +103 -0
- package/dist/entity/decorators.js.map +1 -0
- package/dist/entity/index.d.ts +4 -0
- package/dist/entity/index.d.ts.map +1 -0
- package/dist/entity/index.js +3 -0
- package/dist/entity/index.js.map +1 -0
- package/dist/filters/FilterChain.d.ts +34 -0
- package/dist/filters/FilterChain.d.ts.map +1 -0
- package/dist/filters/FilterChain.js +42 -0
- package/dist/filters/FilterChain.js.map +1 -0
- package/dist/filters/builtins/CorsFilter.d.ts +22 -0
- package/dist/filters/builtins/CorsFilter.d.ts.map +1 -0
- package/dist/filters/builtins/CorsFilter.js +41 -0
- package/dist/filters/builtins/CorsFilter.js.map +1 -0
- package/dist/filters/builtins/JwtFilter.d.ts +27 -0
- package/dist/filters/builtins/JwtFilter.d.ts.map +1 -0
- package/dist/filters/builtins/JwtFilter.js +35 -0
- package/dist/filters/builtins/JwtFilter.js.map +1 -0
- package/dist/filters/builtins/RequestContextFilter.d.ts +12 -0
- package/dist/filters/builtins/RequestContextFilter.d.ts.map +1 -0
- package/dist/filters/builtins/RequestContextFilter.js +32 -0
- package/dist/filters/builtins/RequestContextFilter.js.map +1 -0
- package/dist/filters/builtins/RoleFilter.d.ts +27 -0
- package/dist/filters/builtins/RoleFilter.d.ts.map +1 -0
- package/dist/filters/builtins/RoleFilter.js +36 -0
- package/dist/filters/builtins/RoleFilter.js.map +1 -0
- package/dist/filters/builtins/TenantFilter.d.ts +26 -0
- package/dist/filters/builtins/TenantFilter.d.ts.map +1 -0
- package/dist/filters/builtins/TenantFilter.js +29 -0
- package/dist/filters/builtins/TenantFilter.js.map +1 -0
- package/dist/filters/builtins/index.d.ts +11 -0
- package/dist/filters/builtins/index.d.ts.map +1 -0
- package/dist/filters/builtins/index.js +6 -0
- package/dist/filters/builtins/index.js.map +1 -0
- package/dist/filters/index.d.ts +5 -0
- package/dist/filters/index.d.ts.map +1 -0
- package/dist/filters/index.js +4 -0
- package/dist/filters/index.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/OnRoadLogger.d.ts +8 -0
- package/dist/logging/OnRoadLogger.d.ts.map +1 -0
- package/dist/logging/OnRoadLogger.js +2 -0
- package/dist/logging/OnRoadLogger.js.map +1 -0
- package/dist/logging/PinoLogger.d.ts +16 -0
- package/dist/logging/PinoLogger.d.ts.map +1 -0
- package/dist/logging/PinoLogger.js +33 -0
- package/dist/logging/PinoLogger.js.map +1 -0
- package/dist/logging/index.d.ts +4 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +2 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/messaging/MatchingObject.d.ts +9 -0
- package/dist/messaging/MatchingObject.d.ts.map +1 -0
- package/dist/messaging/MatchingObject.js +21 -0
- package/dist/messaging/MatchingObject.js.map +1 -0
- package/dist/messaging/index.d.ts +2 -0
- package/dist/messaging/index.d.ts.map +1 -0
- package/dist/messaging/index.js +2 -0
- package/dist/messaging/index.js.map +1 -0
- package/dist/plugins/OnRoadPlugin.d.ts +6 -0
- package/dist/plugins/OnRoadPlugin.d.ts.map +1 -0
- package/dist/plugins/OnRoadPlugin.js +2 -0
- package/dist/plugins/OnRoadPlugin.js.map +1 -0
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +2 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/providers/MessagingProvider.d.ts +17 -0
- package/dist/providers/MessagingProvider.d.ts.map +1 -0
- package/dist/providers/MessagingProvider.js +3 -0
- package/dist/providers/MessagingProvider.js.map +1 -0
- package/dist/providers/RealtimeProvider.d.ts +13 -0
- package/dist/providers/RealtimeProvider.d.ts.map +1 -0
- package/dist/providers/RealtimeProvider.js +3 -0
- package/dist/providers/RealtimeProvider.js.map +1 -0
- package/dist/providers/SocketProvider.d.ts +7 -0
- package/dist/providers/SocketProvider.d.ts.map +1 -0
- package/dist/providers/SocketProvider.js +3 -0
- package/dist/providers/SocketProvider.js.map +1 -0
- package/dist/providers/TaskSchedulerProvider.d.ts +19 -0
- package/dist/providers/TaskSchedulerProvider.d.ts.map +1 -0
- package/dist/providers/TaskSchedulerProvider.js +3 -0
- package/dist/providers/TaskSchedulerProvider.js.map +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +5 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/security/decorators.d.ts +5 -0
- package/dist/security/decorators.d.ts.map +1 -0
- package/dist/security/decorators.js +26 -0
- package/dist/security/decorators.js.map +1 -0
- package/dist/security/index.d.ts +2 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +2 -0
- package/dist/security/index.js.map +1 -0
- package/dist/storage/StorageProvider.d.ts +7 -0
- package/dist/storage/StorageProvider.d.ts.map +1 -0
- package/dist/storage/StorageProvider.js +3 -0
- package/dist/storage/StorageProvider.js.map +1 -0
- package/dist/storage/index.d.ts +2 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/transport/HttpTransport.d.ts +8 -0
- package/dist/transport/HttpTransport.d.ts.map +1 -0
- package/dist/transport/HttpTransport.js +26 -0
- package/dist/transport/HttpTransport.js.map +1 -0
- package/dist/transport/InterServiceTransport.d.ts +15 -0
- package/dist/transport/InterServiceTransport.d.ts.map +1 -0
- package/dist/transport/InterServiceTransport.js +20 -0
- package/dist/transport/InterServiceTransport.js.map +1 -0
- package/dist/transport/MessagingTransport.d.ts +9 -0
- package/dist/transport/MessagingTransport.d.ts.map +1 -0
- package/dist/transport/MessagingTransport.js +21 -0
- package/dist/transport/MessagingTransport.js.map +1 -0
- package/dist/transport/TransportFactory.d.ts +13 -0
- package/dist/transport/TransportFactory.d.ts.map +1 -0
- package/dist/transport/TransportFactory.js +35 -0
- package/dist/transport/TransportFactory.js.map +1 -0
- package/dist/transport/index.d.ts +6 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +5 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/types/express-augment.d.ts +27 -0
- package/dist/types/express-augment.d.ts.map +1 -0
- package/dist/types/express-augment.js +4 -0
- package/dist/types/express-augment.js.map +1 -0
- package/package.json +134 -0
package/README.md
ADDED
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
# TypeRoad
|
|
2
|
+
|
|
3
|
+
> TypeScript backend framework for multi-tenant Express APIs — DI Container, Filter Chain, EventBus, Provider Pattern.
|
|
4
|
+
|
|
5
|
+
**v4.0.0-alpha.0** — Full TypeScript rewrite of the onRoad framework.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
- [Quick Start](#quick-start)
|
|
13
|
+
- [Architecture Overview](#architecture-overview)
|
|
14
|
+
- [DI Container & Decorators](#di-container--decorators)
|
|
15
|
+
- [Controllers, Services & Repositories](#controllers-services--repositories)
|
|
16
|
+
- [Entity System](#entity-system)
|
|
17
|
+
- [Filter Chain](#filter-chain)
|
|
18
|
+
- [Security (Roles & Public)](#security-roles--public)
|
|
19
|
+
- [Sentinel & EventBus](#sentinel--eventbus)
|
|
20
|
+
- [Providers](#providers)
|
|
21
|
+
- [Inter-Service Transport](#inter-service-transport)
|
|
22
|
+
- [Request Context (AsyncLocalStorage)](#request-context-asynclocalstorage)
|
|
23
|
+
- [Logging](#logging)
|
|
24
|
+
- [Plugins](#plugins)
|
|
25
|
+
- [Storage](#storage)
|
|
26
|
+
- [Health Endpoint](#health-endpoint)
|
|
27
|
+
- [Graceful Shutdown](#graceful-shutdown)
|
|
28
|
+
- [Testing](#testing)
|
|
29
|
+
- [Subpath Exports](#subpath-exports)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @onroad/core
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Peer dependencies** (install as needed):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install express reflect-metadata pino uuid
|
|
43
|
+
# Optional — only if using HttpTransport:
|
|
44
|
+
npm install axios
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
> TypeRoad is ESM-only (`"type": "module"`). Your `tsconfig.json` must use `"module": "NodeNext"` or `"Node16"` and enable `"experimentalDecorators": true`.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import "reflect-metadata"
|
|
55
|
+
import { OnRoadExpress, Controller, Service, Repository } from "@onroad/core"
|
|
56
|
+
import { SequelizeConnectionManager } from "@onroad/core/database"
|
|
57
|
+
import { CorsFilter, JwtFilter, TenantFilter, RoleFilter } from "@onroad/core/filters"
|
|
58
|
+
|
|
59
|
+
// 1. Define your layers
|
|
60
|
+
@Repository()
|
|
61
|
+
class OrdemRepository { /* ... */ }
|
|
62
|
+
|
|
63
|
+
@Service()
|
|
64
|
+
class OrdemService { /* ... */ }
|
|
65
|
+
|
|
66
|
+
@Controller("/ordem")
|
|
67
|
+
class OrdemController {
|
|
68
|
+
/* ... */
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 2. Create the app
|
|
72
|
+
const app = new OnRoadExpress({
|
|
73
|
+
connections: [
|
|
74
|
+
new SequelizeConnectionManager({ dialect: "postgres", host: "localhost", database: "mydb" }),
|
|
75
|
+
],
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// 3. Register filters
|
|
79
|
+
app.useFilters([
|
|
80
|
+
new CorsFilter({ origins: ["http://localhost:3000"] }),
|
|
81
|
+
new JwtFilter({ secret: process.env.JWT_SECRET! }),
|
|
82
|
+
new TenantFilter(),
|
|
83
|
+
new RoleFilter(),
|
|
84
|
+
])
|
|
85
|
+
|
|
86
|
+
// 4. Register modules
|
|
87
|
+
app.register([OrdemController, OrdemService, OrdemRepository])
|
|
88
|
+
|
|
89
|
+
// 5. Build & start
|
|
90
|
+
await app.buildServer({ port: 3001 })
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Architecture Overview
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
┌──────────────────────────────────────────────────┐
|
|
99
|
+
│ OnRoadExpress │
|
|
100
|
+
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
|
|
101
|
+
│ │ Container │ │ FilterChain│ │ EventBus │ │
|
|
102
|
+
│ │ (DI + IoC) │ │ (Middleware)│ │ (Sentinel) │ │
|
|
103
|
+
│ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │
|
|
104
|
+
│ │ │ │ │
|
|
105
|
+
│ ┌─────▼───────────────▼───────────────▼──────┐ │
|
|
106
|
+
│ │ Request Handler (route) │ │
|
|
107
|
+
│ │ requestContext.run({ tenant, appToken }) │ │
|
|
108
|
+
│ │ ┌──────────┐ ┌─────────┐ ┌────────────┐ │ │
|
|
109
|
+
│ │ │Controller│→│ Service │→│ Repository │ │ │
|
|
110
|
+
│ │ └──────────┘ └─────────┘ └────────────┘ │ │
|
|
111
|
+
│ └─────────────────────────────────────────────┘ │
|
|
112
|
+
│ │
|
|
113
|
+
│ ┌──────────────────────────────────────────┐ │
|
|
114
|
+
│ │ Providers │ │
|
|
115
|
+
│ │ Messaging │ Realtime │ TaskSched │ Socket│ │
|
|
116
|
+
│ └──────────────────────────────────────────┘ │
|
|
117
|
+
│ │
|
|
118
|
+
│ ┌──────────────────────────────────────────┐ │
|
|
119
|
+
│ │ TransportFactory │ │
|
|
120
|
+
│ │ HttpTransport │ MessagingTransport │ │
|
|
121
|
+
│ └──────────────────────────────────────────┘ │
|
|
122
|
+
└──────────────────────────────────────────────────┘
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## DI Container & Decorators
|
|
128
|
+
|
|
129
|
+
TypeRoad uses a decorator-based DI container with automatic class scanning.
|
|
130
|
+
|
|
131
|
+
### Decorators
|
|
132
|
+
|
|
133
|
+
| Decorator | Scope | Purpose |
|
|
134
|
+
|-----------|-------|---------|
|
|
135
|
+
| `@Injectable()` | Transient (default) | Generic injectable class |
|
|
136
|
+
| `@Controller("/path")` | Request | Express route handler |
|
|
137
|
+
| `@Service()` | Request | Business logic layer |
|
|
138
|
+
| `@Repository()` | Request | Data access layer |
|
|
139
|
+
|
|
140
|
+
### Scopes
|
|
141
|
+
|
|
142
|
+
| Scope | Behavior |
|
|
143
|
+
|-------|----------|
|
|
144
|
+
| `SINGLETON` | One instance for the entire application |
|
|
145
|
+
| `REQUEST` | One instance per request scope |
|
|
146
|
+
| `TRANSIENT` | New instance on every resolve |
|
|
147
|
+
|
|
148
|
+
### Usage
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { Injectable, Scope } from "@onroad/core"
|
|
152
|
+
|
|
153
|
+
@Injectable({ scope: Scope.SINGLETON })
|
|
154
|
+
class CacheManager {
|
|
155
|
+
private store = new Map<string, unknown>()
|
|
156
|
+
get(key: string) { return this.store.get(key) }
|
|
157
|
+
set(key: string, value: unknown) { this.store.set(key, value) }
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Register all classes via `app.register([...])` — the container auto-scans metadata.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Controllers, Services & Repositories
|
|
166
|
+
|
|
167
|
+
### AbstractController
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import { Controller, AbstractController, type RouteConfig } from "@onroad/core"
|
|
171
|
+
|
|
172
|
+
@Controller("/ordem")
|
|
173
|
+
class OrdemController extends AbstractController<OrdemService> {
|
|
174
|
+
constructor() {
|
|
175
|
+
super({
|
|
176
|
+
service: OrdemService,
|
|
177
|
+
routes: {
|
|
178
|
+
findAll: { method: "get" },
|
|
179
|
+
create: { method: "post" },
|
|
180
|
+
update: { method: "put", path: "/:id" },
|
|
181
|
+
remove: { method: "delete", path: "/:id" },
|
|
182
|
+
},
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async findAll(req: Request, res: Response) {
|
|
187
|
+
const data = await this.service.findAll(req.query)
|
|
188
|
+
res.json({ content: data })
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async create(req: Request, res: Response) {
|
|
192
|
+
const sentinel = new Sentinel(req, res)
|
|
193
|
+
const result = await this.service.create(req.body, sentinel)
|
|
194
|
+
await sentinel.finishRequest(result)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### AbstractService
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import { Service, AbstractService } from "@onroad/core"
|
|
203
|
+
|
|
204
|
+
@Service()
|
|
205
|
+
class OrdemService extends AbstractService<OrdemRepository> {
|
|
206
|
+
constructor() {
|
|
207
|
+
super({ repository: OrdemRepository })
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async findAll(query: unknown) {
|
|
211
|
+
return this.repository.findAll(query)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### SequelizeRepository / MongooseRepository
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { Repository, SequelizeRepository } from "@onroad/core"
|
|
220
|
+
|
|
221
|
+
@Repository()
|
|
222
|
+
class OrdemRepository extends SequelizeRepository {
|
|
223
|
+
constructor() {
|
|
224
|
+
super({ modelName: "Ordem" })
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Entity System
|
|
232
|
+
|
|
233
|
+
Decorate model classes with metadata for auto-registration:
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
import { Entity, Column, DataType, HasMany } from "@onroad/core/entity"
|
|
237
|
+
|
|
238
|
+
@Entity({ tableName: "ordens", timestamps: true })
|
|
239
|
+
class Ordem {
|
|
240
|
+
@Column({ type: DataType.UUID, primaryKey: true })
|
|
241
|
+
id!: string
|
|
242
|
+
|
|
243
|
+
@Column({ type: DataType.STRING, allowNull: false })
|
|
244
|
+
descricao!: string
|
|
245
|
+
|
|
246
|
+
@Column({ type: DataType.ENUM("aberta", "fechada") })
|
|
247
|
+
status!: string
|
|
248
|
+
|
|
249
|
+
@HasMany(() => Tarefa, { foreignKey: "ordemId" })
|
|
250
|
+
tarefas!: Tarefa[]
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The `EntityRegistry` collects all decorated entities for model initialization.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Filter Chain
|
|
259
|
+
|
|
260
|
+
Filters are ordered middleware executed before route handlers. TypeRoad includes 5 built-in filters:
|
|
261
|
+
|
|
262
|
+
| Filter | Order | Purpose |
|
|
263
|
+
|--------|-------|---------|
|
|
264
|
+
| `CorsFilter` | 10 | CORS headers |
|
|
265
|
+
| `JwtFilter` | 20 | JWT token validation |
|
|
266
|
+
| `TenantFilter` | 30 | Multi-tenant resolution |
|
|
267
|
+
| `RoleFilter` | 40 | Role-based access |
|
|
268
|
+
| `RequestContextFilter` | 50 | Attach logger/context to request |
|
|
269
|
+
|
|
270
|
+
### Registration (3 forms)
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
// 1. Bare instance
|
|
274
|
+
app.useFilters([new CorsFilter({ origins: ["*"] })])
|
|
275
|
+
|
|
276
|
+
// 2. Class reference (uses @Filter decorator config)
|
|
277
|
+
app.useFilters([CorsFilter])
|
|
278
|
+
|
|
279
|
+
// 3. Object form (manual config overrides decorator)
|
|
280
|
+
app.useFilters([{
|
|
281
|
+
filter: new JwtFilter({ secret: "..." }),
|
|
282
|
+
order: 15,
|
|
283
|
+
exclude: ["/health", "/public"],
|
|
284
|
+
}])
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Custom Filter
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
import { OnRoadFilter, Filter, FilterChain } from "@onroad/core/filters"
|
|
291
|
+
|
|
292
|
+
@Filter({ order: 25 })
|
|
293
|
+
class AuditFilter extends OnRoadFilter {
|
|
294
|
+
async execute(req: Request, res: Response, chain: FilterChain) {
|
|
295
|
+
console.log(`[${req.method}] ${req.path}`)
|
|
296
|
+
await chain.next(req, res) // pass to next filter
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Auto-discovered when passed to app.register()
|
|
301
|
+
app.register([AuditFilter])
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Filters support `exclude` (skip specific paths) and `condition` (dynamic enable/disable).
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Security (Roles & Public)
|
|
309
|
+
|
|
310
|
+
### @Roles
|
|
311
|
+
|
|
312
|
+
Restrict access at class or method level:
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
import { Roles, Public } from "@onroad/core/security"
|
|
316
|
+
|
|
317
|
+
@Roles("admin", "manager")
|
|
318
|
+
@Controller("/config")
|
|
319
|
+
class ConfigController extends AbstractController<ConfigService> {
|
|
320
|
+
// Inherits class-level roles: admin, manager
|
|
321
|
+
|
|
322
|
+
@Roles("admin") // Method-level overrides class-level
|
|
323
|
+
async deleteAll(req: Request, res: Response) { /* ... */ }
|
|
324
|
+
|
|
325
|
+
@Public() // No role check — open access
|
|
326
|
+
async getVersion(req: Request, res: Response) { /* ... */ }
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
- Method-level `@Roles` **overrides** class-level (not additive).
|
|
331
|
+
- `@Public()` sets roles to `[]` (empty array = no restriction).
|
|
332
|
+
- `getRoles(instance, methodName)` returns `undefined` if no decorator is present (no enforcement).
|
|
333
|
+
|
|
334
|
+
OnRoadExpress enforces roles automatically in `buildServer()` by reading `req.decoded.roles` or `req.user.roles`.
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Sentinel & EventBus
|
|
339
|
+
|
|
340
|
+
The `Sentinel` manages the request lifecycle — transactions, errors, and events.
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
async create(req: Request, res: Response) {
|
|
344
|
+
const sentinel = new Sentinel(req, res)
|
|
345
|
+
|
|
346
|
+
// Start database transaction
|
|
347
|
+
sentinel.transaction = await sequelize.transaction()
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
const result = await this.service.create(req.body, sentinel)
|
|
351
|
+
|
|
352
|
+
// Queue side-effects
|
|
353
|
+
sentinel.appendMo({ type: "ordemCriada", data: result })
|
|
354
|
+
sentinel.appendNotification({ userId: "abc", title: "Nova OS", body: "..." })
|
|
355
|
+
|
|
356
|
+
// Commit + fire afterCommit events + send response
|
|
357
|
+
await sentinel.finishRequest(result)
|
|
358
|
+
} catch (err) {
|
|
359
|
+
sentinel.appendError(err as Error)
|
|
360
|
+
await sentinel.finishRequest(null) // triggers rollback + 500
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### EventBus Handlers
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
sentinel.eventBus.on("afterCommit", async (payload) => {
|
|
369
|
+
// Publish matching objects to RTDB
|
|
370
|
+
// Send notifications
|
|
371
|
+
// Propagate to other APIs
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
sentinel.eventBus.on("matchingObject", (mo) => {
|
|
375
|
+
// Process individual matching object
|
|
376
|
+
})
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Providers
|
|
382
|
+
|
|
383
|
+
TypeRoad defines 4 abstract provider interfaces. Each has an `isReady()` method and lifecycle hooks.
|
|
384
|
+
|
|
385
|
+
| Provider | Purpose | Example Implementation |
|
|
386
|
+
|----------|---------|----------------------|
|
|
387
|
+
| `MessagingProvider` | Inter-API async messaging | RabbitMQ (CloudAMQP) |
|
|
388
|
+
| `RealtimeProvider` | Push updates to frontends | Firebase RTDB |
|
|
389
|
+
| `TaskSchedulerProvider` | Delayed/scheduled tasks | Google Cloud Tasks |
|
|
390
|
+
| `SocketProvider` | WebSocket communication | Socket.io |
|
|
391
|
+
|
|
392
|
+
### Registration
|
|
393
|
+
|
|
394
|
+
```ts
|
|
395
|
+
const app = new OnRoadExpress()
|
|
396
|
+
|
|
397
|
+
app.setMessagingProvider(new RabbitMQMessagingProvider(process.env.AMQP_URL!))
|
|
398
|
+
app.setRealtimeProvider(new FirebaseRealtimeProvider(firebaseApp))
|
|
399
|
+
app.setTaskSchedulerProvider(new CloudTasksProvider(config))
|
|
400
|
+
app.setSocketProvider(new SocketIOProvider(httpServer))
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
All providers fail gracefully during `buildServer()` — if one fails to initialize, the app continues without it.
|
|
404
|
+
|
|
405
|
+
### Implementing a Provider
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
import { MessagingProvider, type MessagingMessage, type MessageWorker } from "@onroad/core"
|
|
409
|
+
|
|
410
|
+
class RabbitMQMessagingProvider extends MessagingProvider {
|
|
411
|
+
isReady(): boolean { return this.connected }
|
|
412
|
+
|
|
413
|
+
async initialize(config: Record<string, unknown>): Promise<void> {
|
|
414
|
+
// Connect to RabbitMQ...
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async publish(message: MessagingMessage): Promise<void> {
|
|
418
|
+
// Publish to exchange...
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async subscribe(route: string, worker: MessageWorker): Promise<void> {
|
|
422
|
+
// Bind queue and consume...
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async shutdown(): Promise<void> {
|
|
426
|
+
// Close connection...
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## Inter-Service Transport
|
|
434
|
+
|
|
435
|
+
Transport classes enable API-to-API communication with zero boilerplate.
|
|
436
|
+
|
|
437
|
+
### HttpTransport
|
|
438
|
+
|
|
439
|
+
```ts
|
|
440
|
+
// Register API endpoints
|
|
441
|
+
app.registerApiClients({
|
|
442
|
+
Manutencao: "http://api-manutencao:3001",
|
|
443
|
+
Processo: "http://api-processo:3002",
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
// Inside a service — just call it
|
|
447
|
+
const transport = app.transportFactory.createHttp("Manutencao")
|
|
448
|
+
const result = await transport.send("/ordem/sync", { data: payload })
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### MessagingTransport
|
|
452
|
+
|
|
453
|
+
```ts
|
|
454
|
+
const transport = app.transportFactory.createMessaging()
|
|
455
|
+
await transport.send("ordem.created", { ordemId: "123" })
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
No `Sentinel` or tenant needs to be passed — **the transport reads them automatically** from the active `RequestContext` via `AsyncLocalStorage`.
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
## Request Context (AsyncLocalStorage)
|
|
463
|
+
|
|
464
|
+
Every route handler is wrapped in `requestContext.run()` by `OnRoadExpress`, providing request-scoped data without parameter threading:
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
import { getRequestContext } from "@onroad/core"
|
|
468
|
+
|
|
469
|
+
// Anywhere in the call stack during a request:
|
|
470
|
+
const ctx = getRequestContext()
|
|
471
|
+
console.log(ctx.tenant) // current tenant
|
|
472
|
+
console.log(ctx.appToken) // x-app-token header
|
|
473
|
+
console.log(ctx.logger) // request-scoped logger (if configured)
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
This is what enables `InterServiceTransport`, `HttpTransport`, and `MessagingTransport` to automatically include `tenant` and `x-app-token` headers without receiving them as constructor arguments.
|
|
477
|
+
|
|
478
|
+
If called outside a request handler, `getRequestContext()` throws a descriptive error.
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## Logging
|
|
483
|
+
|
|
484
|
+
TypeRoad uses `pino` via the `PinoLogger` class implementing the `OnRoadLogger` interface.
|
|
485
|
+
|
|
486
|
+
```ts
|
|
487
|
+
import { PinoLogger } from "@onroad/core/logging"
|
|
488
|
+
|
|
489
|
+
const app = new OnRoadExpress({
|
|
490
|
+
logger: new PinoLogger({ level: "debug" }),
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
// Anywhere:
|
|
494
|
+
app.logger.info("Server started", { port: 3001 })
|
|
495
|
+
app.logger.warn("Slow query", { duration: 2500 })
|
|
496
|
+
app.logger.error("Connection failed", { host: "db" })
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### OnRoadLogger Interface
|
|
500
|
+
|
|
501
|
+
```ts
|
|
502
|
+
interface OnRoadLogger {
|
|
503
|
+
info(message: string, meta?: Record<string, unknown>): void
|
|
504
|
+
warn(message: string, meta?: Record<string, unknown>): void
|
|
505
|
+
error(message: string, meta?: Record<string, unknown>): void
|
|
506
|
+
debug(message: string, meta?: Record<string, unknown>): void
|
|
507
|
+
child(bindings: Record<string, unknown>): OnRoadLogger
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
Swap `PinoLogger` for any custom implementation of `OnRoadLogger`.
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## Plugins
|
|
516
|
+
|
|
517
|
+
Plugins extend OnRoadExpress capabilities. A plugin receives the full `app` reference during installation.
|
|
518
|
+
|
|
519
|
+
```ts
|
|
520
|
+
import type { OnRoadPlugin } from "@onroad/core"
|
|
521
|
+
|
|
522
|
+
class MetricsPlugin implements OnRoadPlugin {
|
|
523
|
+
name = "metrics"
|
|
524
|
+
|
|
525
|
+
async install(app: { app: Express; eventBus: RequestEventBus }): Promise<void> {
|
|
526
|
+
app.eventBus.on("afterCommit", (payload) => {
|
|
527
|
+
// Track metrics...
|
|
528
|
+
})
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
await app.use(new MetricsPlugin())
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### @onroad/plugin-notification
|
|
536
|
+
|
|
537
|
+
The notification plugin is a separate package: [`@onroad/plugin-notification`](https://github.com/user/onroad-notificationPlugin).
|
|
538
|
+
|
|
539
|
+
```ts
|
|
540
|
+
import { NotificationPlugin } from "@onroad/plugin-notification"
|
|
541
|
+
|
|
542
|
+
await app.use(new NotificationPlugin({
|
|
543
|
+
apiUrl: process.env.NOTIFICATION_API_URL!,
|
|
544
|
+
timeout: 5000,
|
|
545
|
+
}))
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
It listens to `afterCommit` events and sends notification payloads to the notification API using native `fetch` with `Promise.allSettled` for resilience.
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## Storage
|
|
553
|
+
|
|
554
|
+
Abstract `StorageProvider` for file/attachment operations (e.g., GCS, S3):
|
|
555
|
+
|
|
556
|
+
```ts
|
|
557
|
+
import { StorageProvider } from "@onroad/core/storage"
|
|
558
|
+
|
|
559
|
+
class GCSStorageProvider extends StorageProvider {
|
|
560
|
+
async upload(buffer: Buffer, opts: UploadOptions): Promise<string> { /* ... */ }
|
|
561
|
+
async getSignedUrl(key: string): Promise<string> { /* ... */ }
|
|
562
|
+
async delete(key: string): Promise<void> { /* ... */ }
|
|
563
|
+
}
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
---
|
|
567
|
+
|
|
568
|
+
## Health Endpoint
|
|
569
|
+
|
|
570
|
+
`GET /health` is automatically registered by `buildServer()`:
|
|
571
|
+
|
|
572
|
+
```json
|
|
573
|
+
{
|
|
574
|
+
"status": "ok",
|
|
575
|
+
"timestamp": "2025-01-15T10:30:00.000Z",
|
|
576
|
+
"providers": {
|
|
577
|
+
"messaging": true,
|
|
578
|
+
"realtime": true,
|
|
579
|
+
"taskScheduler": null,
|
|
580
|
+
"socket": null
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
- `true` = provider registered and ready
|
|
586
|
+
- `false` = provider registered but not ready
|
|
587
|
+
- `null` = provider not registered
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
## Graceful Shutdown
|
|
592
|
+
|
|
593
|
+
```ts
|
|
594
|
+
await app.shutdown()
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
`shutdown()` disconnects all providers sequentially (messaging, realtime, taskScheduler, socket), closes the HTTP server, and logs each step. Each provider failure is isolated — one failing provider does not block the others.
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## Testing
|
|
602
|
+
|
|
603
|
+
TypeRoad uses [Vitest](https://vitest.dev/) with **223 tests** across 9 test files:
|
|
604
|
+
|
|
605
|
+
```bash
|
|
606
|
+
npm test # Run all tests
|
|
607
|
+
npm run test:watch # Watch mode
|
|
608
|
+
npm run test:coverage # Coverage report
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
| Test File | Tests | Covers |
|
|
612
|
+
|-----------|-------|--------|
|
|
613
|
+
| `container.test.ts` | 16 | DI Container, decorators, scopes |
|
|
614
|
+
| `entity.test.ts` | 14 | Entity, Column, associations |
|
|
615
|
+
| `database.test.ts` | 13 | ConnectionManagers |
|
|
616
|
+
| `filters.test.ts` | 33 | FilterChain, all 5 built-in filters |
|
|
617
|
+
| `security.test.ts` | 25 | @Roles, @Public, enforcement |
|
|
618
|
+
| `sentinel.test.ts` | 36 | Sentinel, EventBus, transactions |
|
|
619
|
+
| `logging.test.ts` | 20 | PinoLogger, OnRoadLogger |
|
|
620
|
+
| `providers.test.ts` | 41 | All 4 providers, graceful-fail, shutdown |
|
|
621
|
+
| `transport.test.ts` | 25 | HttpTransport, MessagingTransport, TransportFactory, RequestContext |
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
## Project Structure
|
|
626
|
+
|
|
627
|
+
```
|
|
628
|
+
src/
|
|
629
|
+
├── index.ts # Public API exports
|
|
630
|
+
├── OnRoadExpress.ts # Main orchestrator
|
|
631
|
+
├── container/ # DI Container + decorators
|
|
632
|
+
├── context/ # RequestContext (AsyncLocalStorage)
|
|
633
|
+
├── core/ # AbstractController/Service/Repository, Sentinel, EventBus
|
|
634
|
+
├── entity/ # @Entity, @Column, @Field, associations
|
|
635
|
+
├── filters/ # FilterChain, @Filter, built-in filters (Cors, JWT, Tenant, Role, RequestContext)
|
|
636
|
+
├── security/ # @Roles, @Public
|
|
637
|
+
├── database/ # ConnectionManager abstract + Sequelize/Mongo implementations
|
|
638
|
+
├── transport/ # InterServiceTransport, HttpTransport, MessagingTransport, TransportFactory
|
|
639
|
+
├── messaging/ # MatchingObject
|
|
640
|
+
├── providers/ # MessagingProvider, RealtimeProvider, TaskSchedulerProvider, SocketProvider
|
|
641
|
+
├── logging/ # OnRoadLogger interface + PinoLogger
|
|
642
|
+
├── plugins/ # OnRoadPlugin interface
|
|
643
|
+
├── storage/ # StorageProvider abstract
|
|
644
|
+
└── types/ # Express Request augmentation
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
---
|
|
648
|
+
|
|
649
|
+
## Subpath Exports
|
|
650
|
+
|
|
651
|
+
Import only what you need:
|
|
652
|
+
|
|
653
|
+
```ts
|
|
654
|
+
import { OnRoadExpress } from "@onroad/core" // Main
|
|
655
|
+
import { AbstractController, Sentinel } from "@onroad/core/core" // Core classes
|
|
656
|
+
import { Entity, Column, DataType } from "@onroad/core/entity" // Entity decorators
|
|
657
|
+
import { FilterChain, CorsFilter } from "@onroad/core/filters" // Filters
|
|
658
|
+
import { Roles, Public } from "@onroad/core/security" // Security
|
|
659
|
+
import { SequelizeConnectionManager } from "@onroad/core/database" // Database
|
|
660
|
+
import { HttpTransport, TransportFactory } from "@onroad/core/transport" // Transport
|
|
661
|
+
import { MatchingObject } from "@onroad/core/messaging" // Messaging
|
|
662
|
+
import { PinoLogger } from "@onroad/core/logging" // Logging
|
|
663
|
+
import { StorageProvider } from "@onroad/core/storage" // Storage
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
## License
|
|
669
|
+
|
|
670
|
+
UNLICENSED — HashCodeTI-Brasil
|