@engjts/nexus 0.1.8 → 0.1.9
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/package.json +1 -1
- package/BENCHMARK_REPORT.md +0 -343
- package/documentation/01-getting-started.md +0 -240
- package/documentation/02-context.md +0 -335
- package/documentation/03-routing.md +0 -397
- package/documentation/04-middleware.md +0 -483
- package/documentation/05-validation.md +0 -514
- package/documentation/06-error-handling.md +0 -465
- package/documentation/07-performance.md +0 -364
- package/documentation/08-adapters.md +0 -470
- package/documentation/09-api-reference.md +0 -548
- package/documentation/10-examples.md +0 -582
- package/documentation/11-deployment.md +0 -477
- package/documentation/12-sentry.md +0 -620
- package/documentation/13-sentry-data-storage.md +0 -996
- package/documentation/14-sentry-data-reference.md +0 -457
- package/documentation/15-sentry-summary.md +0 -409
- package/documentation/16-alerts-system.md +0 -745
- package/documentation/17-alert-adapters.md +0 -696
- package/documentation/18-alerts-implementation-summary.md +0 -385
- package/documentation/19-class-based-routing.md +0 -840
- package/documentation/20-websocket-realtime.md +0 -813
- package/documentation/21-cache-system.md +0 -510
- package/documentation/22-job-queue.md +0 -772
- package/documentation/23-sentry-plugin.md +0 -551
- package/documentation/24-testing-utilities.md +0 -1287
- package/documentation/25-api-versioning.md +0 -533
- package/documentation/26-context-store.md +0 -607
- package/documentation/27-dependency-injection.md +0 -329
- package/documentation/28-lifecycle-hooks.md +0 -521
- package/documentation/29-package-structure.md +0 -196
- package/documentation/30-plugin-system.md +0 -414
- package/documentation/31-jwt-authentication.md +0 -597
- package/documentation/32-cli.md +0 -268
- package/documentation/ALERTS-COMPLETE-SUMMARY.md +0 -429
- package/documentation/ALERTS-INDEX.md +0 -330
- package/documentation/ALERTS-QUICK-REFERENCE.md +0 -286
- package/documentation/README.md +0 -178
- package/documentation/index.html +0 -34
- package/modern_framework_paper.md +0 -1870
- package/public/css/style.css +0 -87
- package/public/index.html +0 -34
- package/public/js/app.js +0 -27
- package/src/advanced/cache/InMemoryCacheStore.ts +0 -68
- package/src/advanced/cache/MultiTierCache.ts +0 -194
- package/src/advanced/cache/RedisCacheStore.ts +0 -341
- package/src/advanced/cache/index.ts +0 -5
- package/src/advanced/cache/types.ts +0 -40
- package/src/advanced/graphql/SimpleDataLoader.ts +0 -42
- package/src/advanced/graphql/index.ts +0 -22
- package/src/advanced/graphql/server.ts +0 -252
- package/src/advanced/graphql/types.ts +0 -42
- package/src/advanced/jobs/InMemoryQueueStore.ts +0 -68
- package/src/advanced/jobs/JobQueue.ts +0 -556
- package/src/advanced/jobs/RedisQueueStore.ts +0 -367
- package/src/advanced/jobs/index.ts +0 -5
- package/src/advanced/jobs/types.ts +0 -70
- package/src/advanced/observability/APMManager.ts +0 -163
- package/src/advanced/observability/AlertManager.ts +0 -109
- package/src/advanced/observability/MetricRegistry.ts +0 -151
- package/src/advanced/observability/ObservabilityCenter.ts +0 -304
- package/src/advanced/observability/StructuredLogger.ts +0 -154
- package/src/advanced/observability/TracingManager.ts +0 -117
- package/src/advanced/observability/adapters.ts +0 -304
- package/src/advanced/observability/createObservabilityMiddleware.ts +0 -63
- package/src/advanced/observability/index.ts +0 -11
- package/src/advanced/observability/types.ts +0 -174
- package/src/advanced/playground/extractPathParams.ts +0 -6
- package/src/advanced/playground/generateFieldExample.ts +0 -31
- package/src/advanced/playground/generatePlaygroundHTML.ts +0 -1956
- package/src/advanced/playground/generateSummary.ts +0 -19
- package/src/advanced/playground/getTagFromPath.ts +0 -9
- package/src/advanced/playground/index.ts +0 -8
- package/src/advanced/playground/playground.ts +0 -250
- package/src/advanced/playground/types.ts +0 -49
- package/src/advanced/playground/zodToExample.ts +0 -16
- package/src/advanced/playground/zodToParams.ts +0 -15
- package/src/advanced/postman/buildAuth.ts +0 -31
- package/src/advanced/postman/buildBody.ts +0 -15
- package/src/advanced/postman/buildQueryParams.ts +0 -27
- package/src/advanced/postman/buildRequestItem.ts +0 -36
- package/src/advanced/postman/buildResponses.ts +0 -11
- package/src/advanced/postman/buildUrl.ts +0 -33
- package/src/advanced/postman/capitalize.ts +0 -4
- package/src/advanced/postman/generateCollection.ts +0 -59
- package/src/advanced/postman/generateEnvironment.ts +0 -34
- package/src/advanced/postman/generateExampleFromZod.ts +0 -21
- package/src/advanced/postman/generateFieldExample.ts +0 -45
- package/src/advanced/postman/generateName.ts +0 -20
- package/src/advanced/postman/generateUUID.ts +0 -11
- package/src/advanced/postman/getTagFromPath.ts +0 -10
- package/src/advanced/postman/index.ts +0 -28
- package/src/advanced/postman/postman.ts +0 -156
- package/src/advanced/postman/slugify.ts +0 -7
- package/src/advanced/postman/types.ts +0 -140
- package/src/advanced/realtime/index.ts +0 -18
- package/src/advanced/realtime/websocket.ts +0 -231
- package/src/advanced/sentry/index.ts +0 -1236
- package/src/advanced/sentry/types.ts +0 -355
- package/src/advanced/static/generateDirectoryListing.ts +0 -47
- package/src/advanced/static/generateETag.ts +0 -7
- package/src/advanced/static/getMimeType.ts +0 -9
- package/src/advanced/static/index.ts +0 -32
- package/src/advanced/static/isSafePath.ts +0 -13
- package/src/advanced/static/publicDir.ts +0 -21
- package/src/advanced/static/serveStatic.ts +0 -225
- package/src/advanced/static/spa.ts +0 -24
- package/src/advanced/static/types.ts +0 -159
- package/src/advanced/swagger/SwaggerGenerator.ts +0 -66
- package/src/advanced/swagger/buildOperation.ts +0 -61
- package/src/advanced/swagger/buildParameters.ts +0 -61
- package/src/advanced/swagger/buildRequestBody.ts +0 -21
- package/src/advanced/swagger/buildResponses.ts +0 -54
- package/src/advanced/swagger/capitalize.ts +0 -5
- package/src/advanced/swagger/convertPath.ts +0 -9
- package/src/advanced/swagger/createSwagger.ts +0 -12
- package/src/advanced/swagger/generateOperationId.ts +0 -21
- package/src/advanced/swagger/generateSpec.ts +0 -105
- package/src/advanced/swagger/generateSummary.ts +0 -24
- package/src/advanced/swagger/generateSwaggerUI.ts +0 -70
- package/src/advanced/swagger/generateThemeCss.ts +0 -53
- package/src/advanced/swagger/index.ts +0 -25
- package/src/advanced/swagger/swagger.ts +0 -237
- package/src/advanced/swagger/types.ts +0 -206
- package/src/advanced/swagger/zodFieldToOpenAPI.ts +0 -94
- package/src/advanced/swagger/zodSchemaToOpenAPI.ts +0 -50
- package/src/advanced/swagger/zodToOpenAPI.ts +0 -22
- package/src/advanced/testing/factory.ts +0 -509
- package/src/advanced/testing/harness.ts +0 -612
- package/src/advanced/testing/index.ts +0 -430
- package/src/advanced/testing/load-test.ts +0 -618
- package/src/advanced/testing/mock-server.ts +0 -498
- package/src/advanced/testing/mock.ts +0 -670
- package/src/cli/bin.ts +0 -9
- package/src/cli/cli.ts +0 -158
- package/src/cli/commands/add.ts +0 -178
- package/src/cli/commands/build.ts +0 -73
- package/src/cli/commands/create.ts +0 -166
- package/src/cli/commands/dev.ts +0 -85
- package/src/cli/commands/generate.ts +0 -99
- package/src/cli/commands/help.ts +0 -95
- package/src/cli/commands/init.ts +0 -91
- package/src/cli/commands/version.ts +0 -38
- package/src/cli/index.ts +0 -6
- package/src/cli/templates/generators.ts +0 -359
- package/src/cli/templates/index.ts +0 -680
- package/src/cli/utils/exec.ts +0 -52
- package/src/cli/utils/file-system.ts +0 -78
- package/src/cli/utils/logger.ts +0 -111
- package/src/core/adapter.ts +0 -88
- package/src/core/application.ts +0 -1453
- package/src/core/context-pool.ts +0 -79
- package/src/core/context.ts +0 -856
- package/src/core/index.ts +0 -94
- package/src/core/middleware.ts +0 -272
- package/src/core/performance/buffer-pool.ts +0 -108
- package/src/core/performance/middleware-optimizer.ts +0 -162
- package/src/core/plugin/PluginManager.ts +0 -435
- package/src/core/plugin/builder.ts +0 -358
- package/src/core/plugin/index.ts +0 -50
- package/src/core/plugin/types.ts +0 -214
- package/src/core/router/file-router.ts +0 -623
- package/src/core/router/index.ts +0 -260
- package/src/core/router/radix-tree.ts +0 -242
- package/src/core/serializer.ts +0 -397
- package/src/core/store/index.ts +0 -30
- package/src/core/store/registry.ts +0 -178
- package/src/core/store/request-store.ts +0 -240
- package/src/core/store/types.ts +0 -233
- package/src/core/types.ts +0 -616
- package/src/database/adapter.ts +0 -35
- package/src/database/adapters/index.ts +0 -1
- package/src/database/adapters/mysql.ts +0 -669
- package/src/database/database.ts +0 -70
- package/src/database/dialect.ts +0 -388
- package/src/database/index.ts +0 -12
- package/src/database/migrations.ts +0 -86
- package/src/database/optimizer.ts +0 -125
- package/src/database/query-builder.ts +0 -404
- package/src/database/realtime.ts +0 -53
- package/src/database/schema.ts +0 -71
- package/src/database/transactions.ts +0 -56
- package/src/database/types.ts +0 -87
- package/src/deployment/cluster.ts +0 -471
- package/src/deployment/config.ts +0 -454
- package/src/deployment/docker.ts +0 -599
- package/src/deployment/graceful-shutdown.ts +0 -373
- package/src/deployment/index.ts +0 -56
- package/src/index.ts +0 -281
- package/src/security/adapter.ts +0 -318
- package/src/security/auth/JWTPlugin.ts +0 -234
- package/src/security/auth/JWTProvider.ts +0 -316
- package/src/security/auth/adapter.ts +0 -12
- package/src/security/auth/jwt.ts +0 -234
- package/src/security/auth/middleware.ts +0 -188
- package/src/security/csrf.ts +0 -220
- package/src/security/headers.ts +0 -108
- package/src/security/index.ts +0 -60
- package/src/security/rate-limit/adapter.ts +0 -7
- package/src/security/rate-limit/memory.ts +0 -108
- package/src/security/rate-limit/middleware.ts +0 -181
- package/src/security/sanitization.ts +0 -75
- package/src/security/types.ts +0 -240
- package/src/security/utils.ts +0 -52
- package/tsconfig.json +0 -39
|
@@ -1,521 +0,0 @@
|
|
|
1
|
-
# Lifecycle Hooks
|
|
2
|
-
|
|
3
|
-
Nexus Framework menyediakan sistem **Lifecycle Hooks** untuk intercept dan memodifikasi request/response di berbagai tahap request lifecycle.
|
|
4
|
-
|
|
5
|
-
## Quick Start
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
import { createApp } from 'nexus';
|
|
9
|
-
|
|
10
|
-
const app = createApp();
|
|
11
|
-
|
|
12
|
-
app.hooks({
|
|
13
|
-
onRequest: async (ctx) => {
|
|
14
|
-
ctx.requestId = crypto.randomUUID();
|
|
15
|
-
console.log(`[${ctx.requestId}] ${ctx.method} ${ctx.path}`);
|
|
16
|
-
},
|
|
17
|
-
|
|
18
|
-
afterHandler: async (ctx, result) => {
|
|
19
|
-
return {
|
|
20
|
-
...result,
|
|
21
|
-
meta: { requestId: ctx.requestId, timestamp: Date.now() }
|
|
22
|
-
};
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
onError: async (ctx, error) => {
|
|
26
|
-
console.error(`[${ctx.requestId}] Error:`, error.message);
|
|
27
|
-
// Log to Sentry, etc.
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Hook Lifecycle Order
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
Request masuk
|
|
36
|
-
│
|
|
37
|
-
▼
|
|
38
|
-
┌─────────────────┐
|
|
39
|
-
│ onRequest │ ← Request received, sebelum processing
|
|
40
|
-
└────────┬────────┘
|
|
41
|
-
│
|
|
42
|
-
▼
|
|
43
|
-
┌─────────────────┐
|
|
44
|
-
│ beforeValidation│ ← Sebelum schema validation
|
|
45
|
-
└────────┬────────┘
|
|
46
|
-
│
|
|
47
|
-
▼
|
|
48
|
-
[Validation] ← Schema validation (jika ada)
|
|
49
|
-
│
|
|
50
|
-
▼
|
|
51
|
-
┌─────────────────┐
|
|
52
|
-
│ afterValidation │ ← Setelah validation sukses
|
|
53
|
-
└────────┬────────┘
|
|
54
|
-
│
|
|
55
|
-
▼
|
|
56
|
-
┌─────────────────┐
|
|
57
|
-
│ beforeHandler │ ← Sebelum handler dijalankan
|
|
58
|
-
└────────┬────────┘
|
|
59
|
-
│
|
|
60
|
-
▼
|
|
61
|
-
[Handler] ← Route handler execution
|
|
62
|
-
│
|
|
63
|
-
▼
|
|
64
|
-
┌─────────────────┐
|
|
65
|
-
│ afterHandler │ ← Setelah handler, bisa transform result
|
|
66
|
-
└────────┬────────┘
|
|
67
|
-
│
|
|
68
|
-
▼
|
|
69
|
-
┌─────────────────┐
|
|
70
|
-
│ onResponse │ ← Sebelum response dikirim
|
|
71
|
-
└────────┬────────┘
|
|
72
|
-
│
|
|
73
|
-
▼
|
|
74
|
-
Response sent
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
[Error?]
|
|
78
|
-
│
|
|
79
|
-
▼
|
|
80
|
-
┌─────────────────┐
|
|
81
|
-
│ onError │ ← Ketika error terjadi
|
|
82
|
-
└─────────────────┘
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## API Reference
|
|
86
|
-
|
|
87
|
-
### `app.hooks(hooks)`
|
|
88
|
-
|
|
89
|
-
Register lifecycle hooks untuk request processing.
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
interface LifecycleHooks {
|
|
93
|
-
onRequest?: (ctx: Context) => Promise<void | Response>;
|
|
94
|
-
beforeValidation?: (ctx: Context) => Promise<void | Response>;
|
|
95
|
-
afterValidation?: (ctx: Context) => Promise<void | Response>;
|
|
96
|
-
beforeHandler?: (ctx: Context) => Promise<void | Response>;
|
|
97
|
-
afterHandler?: (ctx: Context, result: any) => Promise<any>;
|
|
98
|
-
onError?: (ctx: Context, error: Error) => Promise<void | Response>;
|
|
99
|
-
onResponse?: (ctx: Context, response: Response) => Promise<void | Response>;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
app.hooks(hooks: LifecycleHooks): Application
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
**Returns:** `Application` (chainable)
|
|
106
|
-
|
|
107
|
-
## Hook Details
|
|
108
|
-
|
|
109
|
-
### 1. `onRequest`
|
|
110
|
-
|
|
111
|
-
Dipanggil pertama kali ketika request masuk, sebelum processing apapun.
|
|
112
|
-
|
|
113
|
-
**Use cases:**
|
|
114
|
-
- Generate request ID
|
|
115
|
-
- Start timing/profiling
|
|
116
|
-
- Early request logging
|
|
117
|
-
- Rate limiting check
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
app.hooks({
|
|
121
|
-
onRequest: async (ctx) => {
|
|
122
|
-
ctx.requestId = crypto.randomUUID();
|
|
123
|
-
ctx.startTime = Date.now();
|
|
124
|
-
console.log(`🚀 [${ctx.requestId}] ${ctx.method} ${ctx.path}`);
|
|
125
|
-
|
|
126
|
-
// Early return untuk block request
|
|
127
|
-
if (isBlacklisted(ctx.headers['x-forwarded-for'])) {
|
|
128
|
-
return ctx.response.status(403).json({ error: 'Forbidden' });
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### 2. `beforeValidation`
|
|
135
|
-
|
|
136
|
-
Dipanggil sebelum schema validation. Berguna untuk transform body sebelum divalidasi.
|
|
137
|
-
|
|
138
|
-
**Use cases:**
|
|
139
|
-
- Unwrap nested body
|
|
140
|
-
- Normalize input data
|
|
141
|
-
- Add default values
|
|
142
|
-
|
|
143
|
-
```typescript
|
|
144
|
-
app.hooks({
|
|
145
|
-
beforeValidation: async (ctx) => {
|
|
146
|
-
// Unwrap jika body dibungkus dalam "data"
|
|
147
|
-
if (ctx.body?.data) {
|
|
148
|
-
ctx.body = ctx.body.data;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Normalize email ke lowercase
|
|
152
|
-
if (ctx.body?.email) {
|
|
153
|
-
ctx.body.email = ctx.body.email.toLowerCase().trim();
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### 3. `afterValidation`
|
|
160
|
-
|
|
161
|
-
Dipanggil setelah validation sukses. Body sudah tervalidasi dan aman digunakan.
|
|
162
|
-
|
|
163
|
-
**Use cases:**
|
|
164
|
-
- Authorization check
|
|
165
|
-
- Load related data
|
|
166
|
-
- Business rule validation
|
|
167
|
-
|
|
168
|
-
```typescript
|
|
169
|
-
app.hooks({
|
|
170
|
-
afterValidation: async (ctx) => {
|
|
171
|
-
// Check authorization untuk admin routes
|
|
172
|
-
if (ctx.path.startsWith('/admin')) {
|
|
173
|
-
const token = ctx.headers.authorization?.replace('Bearer ', '');
|
|
174
|
-
if (!token || !isAdmin(token)) {
|
|
175
|
-
return ctx.response.status(401).json({ error: 'Unauthorized' });
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### 4. `beforeHandler`
|
|
183
|
-
|
|
184
|
-
Dipanggil tepat sebelum route handler dijalankan.
|
|
185
|
-
|
|
186
|
-
**Use cases:**
|
|
187
|
-
- Load user dari token
|
|
188
|
-
- Set up request context
|
|
189
|
-
- Pre-load data
|
|
190
|
-
|
|
191
|
-
```typescript
|
|
192
|
-
app.hooks({
|
|
193
|
-
beforeHandler: async (ctx) => {
|
|
194
|
-
const token = ctx.headers.authorization?.replace('Bearer ', '');
|
|
195
|
-
if (token) {
|
|
196
|
-
ctx.user = await getUserFromToken(token);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Load tenant untuk multi-tenant app
|
|
200
|
-
const tenantId = ctx.headers['x-tenant-id'];
|
|
201
|
-
if (tenantId) {
|
|
202
|
-
ctx.tenant = await loadTenant(tenantId);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
### 5. `afterHandler`
|
|
209
|
-
|
|
210
|
-
Dipanggil setelah handler sukses. **Bisa transform/modify result.**
|
|
211
|
-
|
|
212
|
-
**Use cases:**
|
|
213
|
-
- Add metadata ke response
|
|
214
|
-
- Transform response format
|
|
215
|
-
- Add pagination info
|
|
216
|
-
- Logging response
|
|
217
|
-
|
|
218
|
-
```typescript
|
|
219
|
-
app.hooks({
|
|
220
|
-
afterHandler: async (ctx, result) => {
|
|
221
|
-
const duration = Date.now() - ctx.startTime;
|
|
222
|
-
|
|
223
|
-
// Add metadata ke semua response
|
|
224
|
-
return {
|
|
225
|
-
...result,
|
|
226
|
-
meta: {
|
|
227
|
-
requestId: ctx.requestId,
|
|
228
|
-
duration: `${duration}ms`,
|
|
229
|
-
timestamp: new Date().toISOString(),
|
|
230
|
-
version: 'v1'
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### 6. `onError`
|
|
238
|
-
|
|
239
|
-
Dipanggil ketika error terjadi di manapun dalam request lifecycle.
|
|
240
|
-
|
|
241
|
-
**Use cases:**
|
|
242
|
-
- Error logging ke external service
|
|
243
|
-
- Custom error response
|
|
244
|
-
- Alert/notification
|
|
245
|
-
|
|
246
|
-
```typescript
|
|
247
|
-
app.hooks({
|
|
248
|
-
onError: async (ctx, error) => {
|
|
249
|
-
// Log ke Sentry
|
|
250
|
-
await sentry.captureException(error, {
|
|
251
|
-
extra: {
|
|
252
|
-
requestId: ctx.requestId,
|
|
253
|
-
path: ctx.path,
|
|
254
|
-
method: ctx.method,
|
|
255
|
-
body: ctx.body
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
// Slack notification untuk critical errors
|
|
260
|
-
if (error.message.includes('database')) {
|
|
261
|
-
await slack.notify(`🚨 Database error: ${error.message}`);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Return custom error response
|
|
265
|
-
return ctx.response.status(500).json({
|
|
266
|
-
error: 'Something went wrong',
|
|
267
|
-
requestId: ctx.requestId,
|
|
268
|
-
support: 'Please contact support with this requestId'
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
### 7. `onResponse`
|
|
275
|
-
|
|
276
|
-
Dipanggil sebelum response dikirim ke client. Bisa modify final response.
|
|
277
|
-
|
|
278
|
-
**Use cases:**
|
|
279
|
-
- Final logging
|
|
280
|
-
- Add security headers
|
|
281
|
-
- Response timing
|
|
282
|
-
|
|
283
|
-
```typescript
|
|
284
|
-
app.hooks({
|
|
285
|
-
onResponse: async (ctx, response) => {
|
|
286
|
-
const duration = Date.now() - ctx.startTime;
|
|
287
|
-
|
|
288
|
-
console.log(
|
|
289
|
-
`✨ [${ctx.requestId}] ${ctx.method} ${ctx.path} → ${response.statusCode} (${duration}ms)`
|
|
290
|
-
);
|
|
291
|
-
|
|
292
|
-
// Add security headers
|
|
293
|
-
response.headers['X-Request-ID'] = ctx.requestId;
|
|
294
|
-
response.headers['X-Response-Time'] = `${duration}ms`;
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
## Complete Example
|
|
300
|
-
|
|
301
|
-
```typescript
|
|
302
|
-
import { createApp } from 'nexus';
|
|
303
|
-
|
|
304
|
-
const app = createApp({ debug: true });
|
|
305
|
-
|
|
306
|
-
// Generate unique request ID
|
|
307
|
-
const generateRequestId = () => Math.random().toString(36).substring(2, 10);
|
|
308
|
-
|
|
309
|
-
// Register all hooks
|
|
310
|
-
app.hooks({
|
|
311
|
-
// 1. Request received
|
|
312
|
-
onRequest: async (ctx) => {
|
|
313
|
-
ctx.requestId = generateRequestId();
|
|
314
|
-
ctx.startTime = Date.now();
|
|
315
|
-
console.log(`\n🚀 [${ctx.requestId}] ${ctx.method} ${ctx.path}`);
|
|
316
|
-
},
|
|
317
|
-
|
|
318
|
-
// 2. Before validation
|
|
319
|
-
beforeValidation: async (ctx) => {
|
|
320
|
-
console.log(` 📋 [${ctx.requestId}] Before validation`);
|
|
321
|
-
|
|
322
|
-
// Transform wrapped body
|
|
323
|
-
if (ctx.body?.wrapped) {
|
|
324
|
-
ctx.body = ctx.body.wrapped;
|
|
325
|
-
}
|
|
326
|
-
},
|
|
327
|
-
|
|
328
|
-
// 3. After validation
|
|
329
|
-
afterValidation: async (ctx) => {
|
|
330
|
-
console.log(` ✅ [${ctx.requestId}] Validation passed`);
|
|
331
|
-
|
|
332
|
-
// Authorization check
|
|
333
|
-
if (ctx.path.startsWith('/admin') && !ctx.headers.authorization) {
|
|
334
|
-
return ctx.response.status(401).json({ error: 'Unauthorized' });
|
|
335
|
-
}
|
|
336
|
-
},
|
|
337
|
-
|
|
338
|
-
// 4. Before handler
|
|
339
|
-
beforeHandler: async (ctx) => {
|
|
340
|
-
console.log(` ⚡ [${ctx.requestId}] Before handler`);
|
|
341
|
-
|
|
342
|
-
// Load user from token
|
|
343
|
-
const token = ctx.headers.authorization?.replace('Bearer ', '');
|
|
344
|
-
if (token) {
|
|
345
|
-
ctx.user = { id: 1, name: 'John' }; // await getUserFromToken(token)
|
|
346
|
-
}
|
|
347
|
-
},
|
|
348
|
-
|
|
349
|
-
// 5. After handler - transform result
|
|
350
|
-
afterHandler: async (ctx, result) => {
|
|
351
|
-
const duration = Date.now() - ctx.startTime;
|
|
352
|
-
console.log(` 📦 [${ctx.requestId}] Handler completed (${duration}ms)`);
|
|
353
|
-
|
|
354
|
-
// Add metadata to all responses
|
|
355
|
-
return {
|
|
356
|
-
...result,
|
|
357
|
-
meta: {
|
|
358
|
-
requestId: ctx.requestId,
|
|
359
|
-
duration: `${duration}ms`,
|
|
360
|
-
timestamp: new Date().toISOString()
|
|
361
|
-
}
|
|
362
|
-
};
|
|
363
|
-
},
|
|
364
|
-
|
|
365
|
-
// 6. Error handling
|
|
366
|
-
onError: async (ctx, error) => {
|
|
367
|
-
console.error(` ❌ [${ctx.requestId}] Error:`, error.message);
|
|
368
|
-
|
|
369
|
-
// Log to external service
|
|
370
|
-
// await sentry.captureException(error);
|
|
371
|
-
},
|
|
372
|
-
|
|
373
|
-
// 7. Before response sent
|
|
374
|
-
onResponse: async (ctx, response) => {
|
|
375
|
-
const duration = Date.now() - ctx.startTime;
|
|
376
|
-
console.log(` ✨ [${ctx.requestId}] Response: ${response.statusCode} (${duration}ms)\n`);
|
|
377
|
-
}
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
// Routes
|
|
381
|
-
app.get('/hello', async (ctx) => {
|
|
382
|
-
return { message: 'Hello World!' };
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
app.get('/error', async (ctx) => {
|
|
386
|
-
throw new Error('Something went wrong!');
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
app.listen(3000);
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
**Output di terminal:**
|
|
393
|
-
```
|
|
394
|
-
🚀 [abc123] GET /hello
|
|
395
|
-
📋 [abc123] Before validation
|
|
396
|
-
✅ [abc123] Validation passed
|
|
397
|
-
⚡ [abc123] Before handler
|
|
398
|
-
📦 [abc123] Handler completed (1ms)
|
|
399
|
-
✨ [abc123] Response: 200 (2ms)
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
**Response:**
|
|
403
|
-
```json
|
|
404
|
-
{
|
|
405
|
-
"message": "Hello World!",
|
|
406
|
-
"meta": {
|
|
407
|
-
"requestId": "abc123",
|
|
408
|
-
"duration": "1ms",
|
|
409
|
-
"timestamp": "2025-12-04T10:30:00.000Z"
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
## Early Return
|
|
415
|
-
|
|
416
|
-
Semua hooks (kecuali `afterHandler`) bisa return `Response` untuk short-circuit request:
|
|
417
|
-
|
|
418
|
-
```typescript
|
|
419
|
-
app.hooks({
|
|
420
|
-
onRequest: async (ctx) => {
|
|
421
|
-
// Rate limit check
|
|
422
|
-
if (await isRateLimited(ctx.ip)) {
|
|
423
|
-
return ctx.response.status(429).json({
|
|
424
|
-
error: 'Too many requests'
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
},
|
|
428
|
-
|
|
429
|
-
afterValidation: async (ctx) => {
|
|
430
|
-
// Business rule check
|
|
431
|
-
if (ctx.body.amount > 10000 && !ctx.user.verified) {
|
|
432
|
-
return ctx.response.status(403).json({
|
|
433
|
-
error: 'Verify your account for large transactions'
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
```
|
|
439
|
-
|
|
440
|
-
## Combining with Middleware
|
|
441
|
-
|
|
442
|
-
Hooks dan middleware bekerja bersama:
|
|
443
|
-
|
|
444
|
-
```
|
|
445
|
-
Request → onRequest → [Middleware Chain] → beforeValidation → ... → Handler
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
```typescript
|
|
449
|
-
import { logger, cors } from 'nexus';
|
|
450
|
-
|
|
451
|
-
const app = createApp();
|
|
452
|
-
|
|
453
|
-
// Global middleware (runs after onRequest)
|
|
454
|
-
app.use(logger());
|
|
455
|
-
app.use(cors());
|
|
456
|
-
|
|
457
|
-
// Hooks
|
|
458
|
-
app.hooks({
|
|
459
|
-
onRequest: async (ctx) => {
|
|
460
|
-
// Runs BEFORE middleware
|
|
461
|
-
ctx.requestId = generateId();
|
|
462
|
-
},
|
|
463
|
-
beforeHandler: async (ctx) => {
|
|
464
|
-
// Runs AFTER middleware, before handler
|
|
465
|
-
ctx.user = await loadUser(ctx);
|
|
466
|
-
}
|
|
467
|
-
});
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
## Use Cases
|
|
471
|
-
|
|
472
|
-
### Request Tracing
|
|
473
|
-
```typescript
|
|
474
|
-
app.hooks({
|
|
475
|
-
onRequest: async (ctx) => {
|
|
476
|
-
ctx.traceId = ctx.headers['x-trace-id'] || generateTraceId();
|
|
477
|
-
ctx.spanId = generateSpanId();
|
|
478
|
-
},
|
|
479
|
-
onResponse: async (ctx, response) => {
|
|
480
|
-
response.headers['X-Trace-ID'] = ctx.traceId;
|
|
481
|
-
response.headers['X-Span-ID'] = ctx.spanId;
|
|
482
|
-
}
|
|
483
|
-
});
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
### Performance Monitoring
|
|
487
|
-
```typescript
|
|
488
|
-
app.hooks({
|
|
489
|
-
onRequest: async (ctx) => {
|
|
490
|
-
ctx.metrics = { start: process.hrtime.bigint() };
|
|
491
|
-
},
|
|
492
|
-
onResponse: async (ctx, response) => {
|
|
493
|
-
const duration = Number(process.hrtime.bigint() - ctx.metrics.start) / 1e6;
|
|
494
|
-
await prometheus.histogram('http_request_duration_ms', duration, {
|
|
495
|
-
method: ctx.method,
|
|
496
|
-
path: ctx.path,
|
|
497
|
-
status: response.statusCode
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
});
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
### Multi-tenant
|
|
504
|
-
```typescript
|
|
505
|
-
app.hooks({
|
|
506
|
-
onRequest: async (ctx) => {
|
|
507
|
-
const tenantId = ctx.headers['x-tenant-id'] || ctx.query.tenant;
|
|
508
|
-
if (!tenantId) {
|
|
509
|
-
return ctx.response.status(400).json({ error: 'Tenant ID required' });
|
|
510
|
-
}
|
|
511
|
-
ctx.tenant = await loadTenant(tenantId);
|
|
512
|
-
ctx.db = getTenantDatabase(tenantId);
|
|
513
|
-
}
|
|
514
|
-
});
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
## See Also
|
|
518
|
-
|
|
519
|
-
- [Dependency Injection](./27-dependency-injection.md) - Inject services ke handlers
|
|
520
|
-
- [Middleware](./04-middleware.md) - Middleware system
|
|
521
|
-
- [Error Handling](./06-error-handling.md) - Error handling patterns
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
# Nexus Framework - Package Structure
|
|
2
|
-
|
|
3
|
-
Nexus Framework mendukung tree-shaking dan optional imports melalui subpath exports.
|
|
4
|
-
|
|
5
|
-
## Quick Start
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// Full framework (includes all features)
|
|
9
|
-
import { Application, Router, createApp } from '@engjts/server';
|
|
10
|
-
|
|
11
|
-
// Core only (minimal, no optional deps)
|
|
12
|
-
import { Application, Router, createApp } from '@engjts/server/core';
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Available Subpath Imports
|
|
16
|
-
|
|
17
|
-
### Core (No Optional Dependencies)
|
|
18
|
-
```typescript
|
|
19
|
-
import {
|
|
20
|
-
Application,
|
|
21
|
-
createApp,
|
|
22
|
-
Router,
|
|
23
|
-
MiddlewareExecutor,
|
|
24
|
-
ContextStore,
|
|
25
|
-
RequestStore,
|
|
26
|
-
// Plugin System
|
|
27
|
-
definePlugin,
|
|
28
|
-
createPlugin,
|
|
29
|
-
PluginManager
|
|
30
|
-
} from '@engjts/server/core';
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### Database
|
|
34
|
-
```typescript
|
|
35
|
-
import {
|
|
36
|
-
Database,
|
|
37
|
-
QueryBuilder,
|
|
38
|
-
Schema,
|
|
39
|
-
Migration
|
|
40
|
-
} from '@engjts/server/database';
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### GraphQL (requires `graphql` peer dependency)
|
|
44
|
-
```typescript
|
|
45
|
-
// npm install graphql
|
|
46
|
-
import {
|
|
47
|
-
GraphQLServer,
|
|
48
|
-
SimpleDataLoader
|
|
49
|
-
} from '@engjts/server/graphql';
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### Cache
|
|
53
|
-
```typescript
|
|
54
|
-
import {
|
|
55
|
-
MultiTierCache,
|
|
56
|
-
InMemoryCacheStore,
|
|
57
|
-
RedisCacheStore // requires ioredis
|
|
58
|
-
} from '@engjts/server/cache';
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Jobs (Background Processing)
|
|
62
|
-
```typescript
|
|
63
|
-
import {
|
|
64
|
-
JobQueue,
|
|
65
|
-
InMemoryQueueStore,
|
|
66
|
-
RedisQueueStore // requires ioredis
|
|
67
|
-
} from '@engjts/server/jobs';
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Sentry (Error Tracking & APM)
|
|
71
|
-
```typescript
|
|
72
|
-
import {
|
|
73
|
-
SentryClient,
|
|
74
|
-
initSentry,
|
|
75
|
-
captureException
|
|
76
|
-
} from '@engjts/server/sentry';
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Realtime (WebSocket) (requires `ws` peer dependency)
|
|
80
|
-
```typescript
|
|
81
|
-
// npm install ws
|
|
82
|
-
import {
|
|
83
|
-
WebSocketGateway
|
|
84
|
-
} from '@engjts/server/realtime';
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Testing
|
|
88
|
-
```typescript
|
|
89
|
-
import {
|
|
90
|
-
TestClient,
|
|
91
|
-
Factory,
|
|
92
|
-
MockFn,
|
|
93
|
-
MockDatabase,
|
|
94
|
-
NexusTestRunner
|
|
95
|
-
} from '@engjts/server/testing';
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### Security
|
|
99
|
-
```typescript
|
|
100
|
-
import {
|
|
101
|
-
RateLimiter,
|
|
102
|
-
CSRFProtection,
|
|
103
|
-
JWTAuth,
|
|
104
|
-
SecurityHeaders
|
|
105
|
-
} from '@engjts/server/security';
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### Deployment
|
|
109
|
-
```typescript
|
|
110
|
-
import {
|
|
111
|
-
GracefulShutdownManager,
|
|
112
|
-
ClusterManager,
|
|
113
|
-
ConfigManager,
|
|
114
|
-
DockerGenerator
|
|
115
|
-
} from '@engjts/server/deployment';
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### API Documentation
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
// Swagger
|
|
122
|
-
import { swagger, SwaggerGenerator } from '@engjts/server/swagger';
|
|
123
|
-
|
|
124
|
-
// Postman
|
|
125
|
-
import { postman } from '@engjts/server/postman';
|
|
126
|
-
|
|
127
|
-
// Playground
|
|
128
|
-
import { playground } from '@engjts/server/playground';
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
## Peer Dependencies
|
|
132
|
-
|
|
133
|
-
These dependencies are optional and only needed if you use specific features:
|
|
134
|
-
|
|
135
|
-
| Package | Required For |
|
|
136
|
-
|---------|-------------|
|
|
137
|
-
| `graphql` | GraphQL module |
|
|
138
|
-
| `ws` | WebSocket/Realtime module |
|
|
139
|
-
| `ioredis` | Redis cache & job queue stores |
|
|
140
|
-
|
|
141
|
-
## Installation Examples
|
|
142
|
-
|
|
143
|
-
### Minimal (Core Only)
|
|
144
|
-
```bash
|
|
145
|
-
npm install @engjts/server
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### With GraphQL
|
|
149
|
-
```bash
|
|
150
|
-
npm install @engjts/server graphql
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### With WebSocket
|
|
154
|
-
```bash
|
|
155
|
-
npm install @engjts/server ws
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
### With Redis Support
|
|
159
|
-
```bash
|
|
160
|
-
npm install @engjts/server ioredis
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### Full Installation (All Optional Features)
|
|
164
|
-
```bash
|
|
165
|
-
npm install @engjts/server graphql ws ioredis
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
## Bundle Size Optimization
|
|
169
|
-
|
|
170
|
-
By using subpath imports, you can significantly reduce bundle size:
|
|
171
|
-
|
|
172
|
-
```typescript
|
|
173
|
-
// ❌ Imports everything (larger bundle)
|
|
174
|
-
import { Application, GraphQLServer, WebSocketGateway } from '@engjts/server';
|
|
175
|
-
|
|
176
|
-
// ✅ Tree-shakeable imports (smaller bundle)
|
|
177
|
-
import { Application } from '@engjts/server/core';
|
|
178
|
-
import { GraphQLServer } from '@engjts/server/graphql';
|
|
179
|
-
import { WebSocketGateway } from '@engjts/server/realtime';
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
## Migration from Single Import
|
|
183
|
-
|
|
184
|
-
If you were previously using:
|
|
185
|
-
```typescript
|
|
186
|
-
import { Application, Database, GraphQLServer } from '@engjts/server';
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
You can either:
|
|
190
|
-
1. Keep using it (backwards compatible)
|
|
191
|
-
2. Or switch to subpath imports for better tree-shaking:
|
|
192
|
-
```typescript
|
|
193
|
-
import { Application } from '@engjts/server/core';
|
|
194
|
-
import { Database } from '@engjts/server/database';
|
|
195
|
-
import { GraphQLServer } from '@engjts/server/graphql';
|
|
196
|
-
```
|