@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.
Files changed (202) hide show
  1. package/README.md +670 -0
  2. package/dist/OnRoadExpress.d.ts +59 -0
  3. package/dist/OnRoadExpress.d.ts.map +1 -0
  4. package/dist/OnRoadExpress.js +320 -0
  5. package/dist/OnRoadExpress.js.map +1 -0
  6. package/dist/container/Container.d.ts +53 -0
  7. package/dist/container/Container.d.ts.map +1 -0
  8. package/dist/container/Container.js +175 -0
  9. package/dist/container/Container.js.map +1 -0
  10. package/dist/container/index.d.ts +3 -0
  11. package/dist/container/index.d.ts.map +1 -0
  12. package/dist/container/index.js +2 -0
  13. package/dist/container/index.js.map +1 -0
  14. package/dist/context/RequestContext.d.ts +10 -0
  15. package/dist/context/RequestContext.d.ts.map +1 -0
  16. package/dist/context/RequestContext.js +11 -0
  17. package/dist/context/RequestContext.js.map +1 -0
  18. package/dist/core/AbstractController.d.ts +20 -0
  19. package/dist/core/AbstractController.d.ts.map +1 -0
  20. package/dist/core/AbstractController.js +17 -0
  21. package/dist/core/AbstractController.js.map +1 -0
  22. package/dist/core/AbstractRepository.d.ts +8 -0
  23. package/dist/core/AbstractRepository.d.ts.map +1 -0
  24. package/dist/core/AbstractRepository.js +3 -0
  25. package/dist/core/AbstractRepository.js.map +1 -0
  26. package/dist/core/AbstractService.d.ts +12 -0
  27. package/dist/core/AbstractService.d.ts.map +1 -0
  28. package/dist/core/AbstractService.js +14 -0
  29. package/dist/core/AbstractService.js.map +1 -0
  30. package/dist/core/MongooseRepository.d.ts +19 -0
  31. package/dist/core/MongooseRepository.d.ts.map +1 -0
  32. package/dist/core/MongooseRepository.js +48 -0
  33. package/dist/core/MongooseRepository.js.map +1 -0
  34. package/dist/core/RequestEventBus.d.ts +18 -0
  35. package/dist/core/RequestEventBus.d.ts.map +1 -0
  36. package/dist/core/RequestEventBus.js +43 -0
  37. package/dist/core/RequestEventBus.js.map +1 -0
  38. package/dist/core/Sentinel.d.ts +19 -0
  39. package/dist/core/Sentinel.d.ts.map +1 -0
  40. package/dist/core/Sentinel.js +56 -0
  41. package/dist/core/Sentinel.js.map +1 -0
  42. package/dist/core/SequelizeRepository.d.ts +19 -0
  43. package/dist/core/SequelizeRepository.d.ts.map +1 -0
  44. package/dist/core/SequelizeRepository.js +48 -0
  45. package/dist/core/SequelizeRepository.js.map +1 -0
  46. package/dist/core/index.d.ts +11 -0
  47. package/dist/core/index.d.ts.map +1 -0
  48. package/dist/core/index.js +8 -0
  49. package/dist/core/index.js.map +1 -0
  50. package/dist/database/ConnectionManager.d.ts +26 -0
  51. package/dist/database/ConnectionManager.d.ts.map +1 -0
  52. package/dist/database/ConnectionManager.js +17 -0
  53. package/dist/database/ConnectionManager.js.map +1 -0
  54. package/dist/database/MongoConnectionManager.d.ts +8 -0
  55. package/dist/database/MongoConnectionManager.d.ts.map +1 -0
  56. package/dist/database/MongoConnectionManager.js +47 -0
  57. package/dist/database/MongoConnectionManager.js.map +1 -0
  58. package/dist/database/SequelizeConnectionManager.d.ts +13 -0
  59. package/dist/database/SequelizeConnectionManager.d.ts.map +1 -0
  60. package/dist/database/SequelizeConnectionManager.js +58 -0
  61. package/dist/database/SequelizeConnectionManager.js.map +1 -0
  62. package/dist/database/index.d.ts +6 -0
  63. package/dist/database/index.d.ts.map +1 -0
  64. package/dist/database/index.js +4 -0
  65. package/dist/database/index.js.map +1 -0
  66. package/dist/entity/EntityRegistry.d.ts +9 -0
  67. package/dist/entity/EntityRegistry.d.ts.map +1 -0
  68. package/dist/entity/EntityRegistry.js +114 -0
  69. package/dist/entity/EntityRegistry.js.map +1 -0
  70. package/dist/entity/decorators.d.ts +62 -0
  71. package/dist/entity/decorators.d.ts.map +1 -0
  72. package/dist/entity/decorators.js +103 -0
  73. package/dist/entity/decorators.js.map +1 -0
  74. package/dist/entity/index.d.ts +4 -0
  75. package/dist/entity/index.d.ts.map +1 -0
  76. package/dist/entity/index.js +3 -0
  77. package/dist/entity/index.js.map +1 -0
  78. package/dist/filters/FilterChain.d.ts +34 -0
  79. package/dist/filters/FilterChain.d.ts.map +1 -0
  80. package/dist/filters/FilterChain.js +42 -0
  81. package/dist/filters/FilterChain.js.map +1 -0
  82. package/dist/filters/builtins/CorsFilter.d.ts +22 -0
  83. package/dist/filters/builtins/CorsFilter.d.ts.map +1 -0
  84. package/dist/filters/builtins/CorsFilter.js +41 -0
  85. package/dist/filters/builtins/CorsFilter.js.map +1 -0
  86. package/dist/filters/builtins/JwtFilter.d.ts +27 -0
  87. package/dist/filters/builtins/JwtFilter.d.ts.map +1 -0
  88. package/dist/filters/builtins/JwtFilter.js +35 -0
  89. package/dist/filters/builtins/JwtFilter.js.map +1 -0
  90. package/dist/filters/builtins/RequestContextFilter.d.ts +12 -0
  91. package/dist/filters/builtins/RequestContextFilter.d.ts.map +1 -0
  92. package/dist/filters/builtins/RequestContextFilter.js +32 -0
  93. package/dist/filters/builtins/RequestContextFilter.js.map +1 -0
  94. package/dist/filters/builtins/RoleFilter.d.ts +27 -0
  95. package/dist/filters/builtins/RoleFilter.d.ts.map +1 -0
  96. package/dist/filters/builtins/RoleFilter.js +36 -0
  97. package/dist/filters/builtins/RoleFilter.js.map +1 -0
  98. package/dist/filters/builtins/TenantFilter.d.ts +26 -0
  99. package/dist/filters/builtins/TenantFilter.d.ts.map +1 -0
  100. package/dist/filters/builtins/TenantFilter.js +29 -0
  101. package/dist/filters/builtins/TenantFilter.js.map +1 -0
  102. package/dist/filters/builtins/index.d.ts +11 -0
  103. package/dist/filters/builtins/index.d.ts.map +1 -0
  104. package/dist/filters/builtins/index.js +6 -0
  105. package/dist/filters/builtins/index.js.map +1 -0
  106. package/dist/filters/index.d.ts +5 -0
  107. package/dist/filters/index.d.ts.map +1 -0
  108. package/dist/filters/index.js +4 -0
  109. package/dist/filters/index.js.map +1 -0
  110. package/dist/index.d.ts +27 -0
  111. package/dist/index.d.ts.map +1 -0
  112. package/dist/index.js +31 -0
  113. package/dist/index.js.map +1 -0
  114. package/dist/logging/OnRoadLogger.d.ts +8 -0
  115. package/dist/logging/OnRoadLogger.d.ts.map +1 -0
  116. package/dist/logging/OnRoadLogger.js +2 -0
  117. package/dist/logging/OnRoadLogger.js.map +1 -0
  118. package/dist/logging/PinoLogger.d.ts +16 -0
  119. package/dist/logging/PinoLogger.d.ts.map +1 -0
  120. package/dist/logging/PinoLogger.js +33 -0
  121. package/dist/logging/PinoLogger.js.map +1 -0
  122. package/dist/logging/index.d.ts +4 -0
  123. package/dist/logging/index.d.ts.map +1 -0
  124. package/dist/logging/index.js +2 -0
  125. package/dist/logging/index.js.map +1 -0
  126. package/dist/messaging/MatchingObject.d.ts +9 -0
  127. package/dist/messaging/MatchingObject.d.ts.map +1 -0
  128. package/dist/messaging/MatchingObject.js +21 -0
  129. package/dist/messaging/MatchingObject.js.map +1 -0
  130. package/dist/messaging/index.d.ts +2 -0
  131. package/dist/messaging/index.d.ts.map +1 -0
  132. package/dist/messaging/index.js +2 -0
  133. package/dist/messaging/index.js.map +1 -0
  134. package/dist/plugins/OnRoadPlugin.d.ts +6 -0
  135. package/dist/plugins/OnRoadPlugin.d.ts.map +1 -0
  136. package/dist/plugins/OnRoadPlugin.js +2 -0
  137. package/dist/plugins/OnRoadPlugin.js.map +1 -0
  138. package/dist/plugins/index.d.ts +2 -0
  139. package/dist/plugins/index.d.ts.map +1 -0
  140. package/dist/plugins/index.js +2 -0
  141. package/dist/plugins/index.js.map +1 -0
  142. package/dist/providers/MessagingProvider.d.ts +17 -0
  143. package/dist/providers/MessagingProvider.d.ts.map +1 -0
  144. package/dist/providers/MessagingProvider.js +3 -0
  145. package/dist/providers/MessagingProvider.js.map +1 -0
  146. package/dist/providers/RealtimeProvider.d.ts +13 -0
  147. package/dist/providers/RealtimeProvider.d.ts.map +1 -0
  148. package/dist/providers/RealtimeProvider.js +3 -0
  149. package/dist/providers/RealtimeProvider.js.map +1 -0
  150. package/dist/providers/SocketProvider.d.ts +7 -0
  151. package/dist/providers/SocketProvider.d.ts.map +1 -0
  152. package/dist/providers/SocketProvider.js +3 -0
  153. package/dist/providers/SocketProvider.js.map +1 -0
  154. package/dist/providers/TaskSchedulerProvider.d.ts +19 -0
  155. package/dist/providers/TaskSchedulerProvider.d.ts.map +1 -0
  156. package/dist/providers/TaskSchedulerProvider.js +3 -0
  157. package/dist/providers/TaskSchedulerProvider.js.map +1 -0
  158. package/dist/providers/index.d.ts +8 -0
  159. package/dist/providers/index.d.ts.map +1 -0
  160. package/dist/providers/index.js +5 -0
  161. package/dist/providers/index.js.map +1 -0
  162. package/dist/security/decorators.d.ts +5 -0
  163. package/dist/security/decorators.d.ts.map +1 -0
  164. package/dist/security/decorators.js +26 -0
  165. package/dist/security/decorators.js.map +1 -0
  166. package/dist/security/index.d.ts +2 -0
  167. package/dist/security/index.d.ts.map +1 -0
  168. package/dist/security/index.js +2 -0
  169. package/dist/security/index.js.map +1 -0
  170. package/dist/storage/StorageProvider.d.ts +7 -0
  171. package/dist/storage/StorageProvider.d.ts.map +1 -0
  172. package/dist/storage/StorageProvider.js +3 -0
  173. package/dist/storage/StorageProvider.js.map +1 -0
  174. package/dist/storage/index.d.ts +2 -0
  175. package/dist/storage/index.d.ts.map +1 -0
  176. package/dist/storage/index.js +2 -0
  177. package/dist/storage/index.js.map +1 -0
  178. package/dist/transport/HttpTransport.d.ts +8 -0
  179. package/dist/transport/HttpTransport.d.ts.map +1 -0
  180. package/dist/transport/HttpTransport.js +26 -0
  181. package/dist/transport/HttpTransport.js.map +1 -0
  182. package/dist/transport/InterServiceTransport.d.ts +15 -0
  183. package/dist/transport/InterServiceTransport.d.ts.map +1 -0
  184. package/dist/transport/InterServiceTransport.js +20 -0
  185. package/dist/transport/InterServiceTransport.js.map +1 -0
  186. package/dist/transport/MessagingTransport.d.ts +9 -0
  187. package/dist/transport/MessagingTransport.d.ts.map +1 -0
  188. package/dist/transport/MessagingTransport.js +21 -0
  189. package/dist/transport/MessagingTransport.js.map +1 -0
  190. package/dist/transport/TransportFactory.d.ts +13 -0
  191. package/dist/transport/TransportFactory.d.ts.map +1 -0
  192. package/dist/transport/TransportFactory.js +35 -0
  193. package/dist/transport/TransportFactory.js.map +1 -0
  194. package/dist/transport/index.d.ts +6 -0
  195. package/dist/transport/index.d.ts.map +1 -0
  196. package/dist/transport/index.js +5 -0
  197. package/dist/transport/index.js.map +1 -0
  198. package/dist/types/express-augment.d.ts +27 -0
  199. package/dist/types/express-augment.d.ts.map +1 -0
  200. package/dist/types/express-augment.js +4 -0
  201. package/dist/types/express-augment.js.map +1 -0
  202. 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