@nextrush/core 3.0.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/LICENSE +21 -0
- package/README.md +626 -0
- package/dist/index.d.ts +388 -0
- package/dist/index.js +534 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tanzim (NextRush)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
# @nextrush/core
|
|
2
|
+
|
|
3
|
+
> The minimal core of NextRush: Application, middleware composition, and plugin system.
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
Backend frameworks often bundle everything together. You pay for features you don't use:
|
|
8
|
+
|
|
9
|
+
- Routing logic when you only need middleware composition
|
|
10
|
+
- Body parsing when you're building a proxy
|
|
11
|
+
- Complex plugin systems when you need simple extensibility
|
|
12
|
+
|
|
13
|
+
This creates bloat. Cold starts suffer. Memory usage grows. Debugging becomes harder.
|
|
14
|
+
|
|
15
|
+
## How NextRush Approaches This
|
|
16
|
+
|
|
17
|
+
`@nextrush/core` provides **only the essentials**:
|
|
18
|
+
|
|
19
|
+
- **Application**: Middleware registration and plugin management
|
|
20
|
+
- **Middleware Composition**: Koa-style `compose()` for async middleware chains
|
|
21
|
+
- **Plugin System**: Simple, typed plugin interface
|
|
22
|
+
- **Error Handling**: Configurable error handlers with production/development modes
|
|
23
|
+
|
|
24
|
+
Everything else (routing, body parsing, authentication) lives in separate packages. You install what you use.
|
|
25
|
+
|
|
26
|
+
## Mental Model
|
|
27
|
+
|
|
28
|
+
Think of the core as a **middleware pipeline manager**:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
Request → [Middleware 1] → [Middleware 2] → [Handler] → [Middleware 2] → [Middleware 1] → Response
|
|
32
|
+
↓ ↓ ↓ ↑ ↑
|
|
33
|
+
Before Before Execute After After
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Each middleware can:
|
|
37
|
+
|
|
38
|
+
1. Do something before calling `ctx.next()` or `next()`
|
|
39
|
+
2. Call `await ctx.next()` to pass control downstream
|
|
40
|
+
3. Do something after `ctx.next()` returns
|
|
41
|
+
|
|
42
|
+
This is the "onion model" - requests flow inward, responses flow outward.
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pnpm add @nextrush/core
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { createApp } from '@nextrush/core';
|
|
54
|
+
import { listen } from '@nextrush/adapter-node';
|
|
55
|
+
|
|
56
|
+
const app = createApp();
|
|
57
|
+
|
|
58
|
+
// Logging middleware
|
|
59
|
+
app.use(async (ctx, next) => {
|
|
60
|
+
console.log(`→ ${ctx.method} ${ctx.path}`);
|
|
61
|
+
await next();
|
|
62
|
+
console.log(`← ${ctx.status}`);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Handler
|
|
66
|
+
app.use(async (ctx) => {
|
|
67
|
+
ctx.json({ message: 'Hello World' });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
listen(app, { port: 3000 });
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Application
|
|
74
|
+
|
|
75
|
+
### Creating an Application
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { createApp, Application } from '@nextrush/core';
|
|
79
|
+
|
|
80
|
+
// Factory function (recommended)
|
|
81
|
+
const app = createApp();
|
|
82
|
+
|
|
83
|
+
// With options
|
|
84
|
+
const app = createApp({
|
|
85
|
+
env: 'production', // 'development' | 'production' | 'test'
|
|
86
|
+
proxy: true, // Trust proxy headers (X-Forwarded-*)
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Application Options
|
|
91
|
+
|
|
92
|
+
| Option | Type | Default | Description |
|
|
93
|
+
| -------- | ----------------------------------------- | --------------- | ------------------------------------------------------- |
|
|
94
|
+
| `env` | `'development' \| 'production' \| 'test'` | `'development'` | Environment mode |
|
|
95
|
+
| `proxy` | `boolean` | `false` | Trust proxy headers |
|
|
96
|
+
| `logger` | `Logger` | No-op (silent) | Pluggable logger. Pass `console` for quick dev logging. |
|
|
97
|
+
|
|
98
|
+
### Application Properties
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
app.isProduction; // boolean - true if env === 'production'
|
|
102
|
+
app.isRunning; // boolean - true after app.start() called
|
|
103
|
+
app.middlewareCount; // number - count of registered middleware
|
|
104
|
+
app.options; // ApplicationOptions - readonly config
|
|
105
|
+
app.logger; // Logger - readonly configured logger instance
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Middleware
|
|
109
|
+
|
|
110
|
+
### Registration
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// Single middleware
|
|
114
|
+
app.use(async (ctx, next) => {
|
|
115
|
+
await next();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Multiple middleware
|
|
119
|
+
app.use(middleware1, middleware2, middleware3);
|
|
120
|
+
|
|
121
|
+
// Method chaining
|
|
122
|
+
app.use(cors()).use(helmet()).use(json());
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Two Syntax Styles
|
|
126
|
+
|
|
127
|
+
NextRush supports both modern and traditional Koa-style middleware:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// Modern syntax (ctx.next)
|
|
131
|
+
app.use(async (ctx) => {
|
|
132
|
+
console.log('Before');
|
|
133
|
+
await ctx.next();
|
|
134
|
+
console.log('After');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Traditional Koa syntax (next parameter)
|
|
138
|
+
app.use(async (ctx, next) => {
|
|
139
|
+
console.log('Before');
|
|
140
|
+
await next();
|
|
141
|
+
console.log('After');
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Both styles work identically. Use whichever you prefer.
|
|
146
|
+
|
|
147
|
+
### Middleware Order
|
|
148
|
+
|
|
149
|
+
Middleware executes in registration order (onion model):
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
app.use(async (ctx, next) => {
|
|
153
|
+
console.log('1: Start');
|
|
154
|
+
await next();
|
|
155
|
+
console.log('1: End');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
app.use(async (ctx, next) => {
|
|
159
|
+
console.log('2: Start');
|
|
160
|
+
await next();
|
|
161
|
+
console.log('2: End');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
app.use(async (ctx) => {
|
|
165
|
+
console.log('3: Handler');
|
|
166
|
+
ctx.json({ ok: true });
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Output:
|
|
170
|
+
// 1: Start
|
|
171
|
+
// 2: Start
|
|
172
|
+
// 3: Handler
|
|
173
|
+
// 2: End
|
|
174
|
+
// 1: End
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Conditional Middleware
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
app.use(async (ctx, next) => {
|
|
181
|
+
// Skip middleware for health checks
|
|
182
|
+
if (ctx.path === '/health') {
|
|
183
|
+
return next();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Apply logic to other routes
|
|
187
|
+
const start = Date.now();
|
|
188
|
+
await next();
|
|
189
|
+
console.log(`${ctx.path} took ${Date.now() - start}ms`);
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Early Termination
|
|
194
|
+
|
|
195
|
+
Skip remaining middleware by not calling `next()`:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
app.use(async (ctx, next) => {
|
|
199
|
+
if (!ctx.get('Authorization')) {
|
|
200
|
+
ctx.status = 401;
|
|
201
|
+
ctx.json({ error: 'Unauthorized' });
|
|
202
|
+
return; // Don't call next()
|
|
203
|
+
}
|
|
204
|
+
await next();
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Context
|
|
209
|
+
|
|
210
|
+
The Context (`ctx`) object provides access to request data and response methods.
|
|
211
|
+
|
|
212
|
+
### Request Properties (Read-only)
|
|
213
|
+
|
|
214
|
+
| Property | Type | Description |
|
|
215
|
+
| ---------------- | ----------------- | ----------------------------- |
|
|
216
|
+
| `ctx.method` | `HttpMethod` | HTTP method (GET, POST, etc.) |
|
|
217
|
+
| `ctx.url` | `string` | Full URL with query string |
|
|
218
|
+
| `ctx.path` | `string` | Path without query string |
|
|
219
|
+
| `ctx.query` | `QueryParams` | Parsed query parameters |
|
|
220
|
+
| `ctx.headers` | `IncomingHeaders` | Request headers |
|
|
221
|
+
| `ctx.ip` | `string` | Client IP address |
|
|
222
|
+
| `ctx.runtime` | `Runtime` | Current JS runtime |
|
|
223
|
+
| `ctx.raw` | `RawHttp` | Raw platform objects |
|
|
224
|
+
| `ctx.bodySource` | `BodySource` | Body stream for parsers |
|
|
225
|
+
|
|
226
|
+
### Request Body
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
// ctx.body is set by body parser middleware
|
|
230
|
+
import { json } from '@nextrush/body-parser';
|
|
231
|
+
|
|
232
|
+
app.use(json());
|
|
233
|
+
|
|
234
|
+
app.post('/users', async (ctx) => {
|
|
235
|
+
const { name, email } = ctx.body as CreateUserDto;
|
|
236
|
+
ctx.json({ name, email });
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Route Parameters
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
// Set by router when route matches
|
|
244
|
+
app.get('/users/:id', (ctx) => {
|
|
245
|
+
const { id } = ctx.params;
|
|
246
|
+
ctx.json({ id });
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Response
|
|
251
|
+
|
|
252
|
+
| Property/Method | Description |
|
|
253
|
+
| ---------------------------- | ----------------------------------- |
|
|
254
|
+
| `ctx.status` | Set HTTP status code (default: 200) |
|
|
255
|
+
| `ctx.json(data)` | Send JSON response |
|
|
256
|
+
| `ctx.send(data)` | Send text, buffer, or stream |
|
|
257
|
+
| `ctx.html(content)` | Send HTML response |
|
|
258
|
+
| `ctx.redirect(url, status?)` | Redirect to URL |
|
|
259
|
+
| `ctx.set(field, value)` | Set response header |
|
|
260
|
+
| `ctx.get(field)` | Get request header |
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
app.use(async (ctx) => {
|
|
264
|
+
// Set status
|
|
265
|
+
ctx.status = 201;
|
|
266
|
+
|
|
267
|
+
// Set headers
|
|
268
|
+
ctx.set('X-Request-Id', '12345');
|
|
269
|
+
ctx.set('Cache-Control', 'no-cache');
|
|
270
|
+
|
|
271
|
+
// Send JSON
|
|
272
|
+
ctx.json({ created: true });
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Error Helpers
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
app.use(async (ctx) => {
|
|
280
|
+
// Throw HTTP error
|
|
281
|
+
ctx.throw(404, 'User not found');
|
|
282
|
+
ctx.throw(401); // Uses default message
|
|
283
|
+
|
|
284
|
+
// Assert condition
|
|
285
|
+
ctx.assert(user, 404, 'User not found');
|
|
286
|
+
ctx.assert(user.isAdmin, 403, 'Admin required');
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### State
|
|
291
|
+
|
|
292
|
+
Share data between middleware:
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
// Auth middleware
|
|
296
|
+
app.use(async (ctx, next) => {
|
|
297
|
+
ctx.state.user = await validateToken(ctx.get('Authorization'));
|
|
298
|
+
await next();
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Handler
|
|
302
|
+
app.get('/profile', (ctx) => {
|
|
303
|
+
const user = ctx.state.user;
|
|
304
|
+
ctx.json({ user });
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Raw Access
|
|
309
|
+
|
|
310
|
+
Access platform-specific objects:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
// Node.js adapter
|
|
314
|
+
ctx.raw.req; // IncomingMessage
|
|
315
|
+
ctx.raw.res; // ServerResponse
|
|
316
|
+
|
|
317
|
+
// Bun/Deno/Edge adapters
|
|
318
|
+
ctx.raw.req; // Request (Web API)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Error Handling
|
|
322
|
+
|
|
323
|
+
### Custom Error Handler
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
app.setErrorHandler((error, ctx) => {
|
|
327
|
+
console.error('Request failed:', error);
|
|
328
|
+
|
|
329
|
+
if ('status' in error && typeof error.status === 'number') {
|
|
330
|
+
ctx.status = error.status;
|
|
331
|
+
} else {
|
|
332
|
+
ctx.status = 500;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
ctx.json({
|
|
336
|
+
error: error.message,
|
|
337
|
+
code: error.code || 'UNKNOWN',
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
> **Note:** `onError()` is deprecated. Use `setErrorHandler()` instead.
|
|
343
|
+
|
|
344
|
+
### Default Behavior
|
|
345
|
+
|
|
346
|
+
Without a custom handler:
|
|
347
|
+
|
|
348
|
+
- **Development**: Error message exposed, stack logged
|
|
349
|
+
- **Production**: Generic "Internal Server Error" message
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
// Production mode hides sensitive details
|
|
353
|
+
const app = createApp({ env: 'production' });
|
|
354
|
+
|
|
355
|
+
app.use(async () => {
|
|
356
|
+
throw new Error('Database connection failed'); // User sees "Internal Server Error"
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Error Classes
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
import {
|
|
364
|
+
HttpError,
|
|
365
|
+
NotFoundError,
|
|
366
|
+
BadRequestError,
|
|
367
|
+
UnauthorizedError,
|
|
368
|
+
ForbiddenError,
|
|
369
|
+
InternalServerError,
|
|
370
|
+
} from '@nextrush/core';
|
|
371
|
+
|
|
372
|
+
app.use(async (ctx) => {
|
|
373
|
+
throw new NotFoundError('User not found');
|
|
374
|
+
throw new BadRequestError('Invalid email');
|
|
375
|
+
throw new UnauthorizedError('Token expired');
|
|
376
|
+
});
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Plugins
|
|
380
|
+
|
|
381
|
+
### Using Plugins
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
import { createApp } from '@nextrush/core';
|
|
385
|
+
import { eventsPlugin } from '@nextrush/events';
|
|
386
|
+
import { loggerPlugin } from '@nextrush/logger';
|
|
387
|
+
|
|
388
|
+
const app = createApp();
|
|
389
|
+
|
|
390
|
+
// Sync plugins
|
|
391
|
+
app.plugin(eventsPlugin());
|
|
392
|
+
app.plugin(loggerPlugin({ level: 'info' }));
|
|
393
|
+
|
|
394
|
+
// Async plugin — plugin() handles both automatically
|
|
395
|
+
await app.plugin(databasePlugin({ uri: '...' }));
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
> **Note:** `pluginAsync()` is deprecated. Use `plugin()` instead — it detects async `install()` methods and returns a `Promise` when needed.
|
|
399
|
+
|
|
400
|
+
### Plugin API
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
app.plugin(plugin); // Install plugin (handles sync and async)
|
|
404
|
+
app.hasPlugin('plugin-name'); // Check if installed
|
|
405
|
+
app.getPlugin('plugin-name'); // Get plugin instance
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Creating Plugins
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
import type { Plugin } from '@nextrush/types';
|
|
412
|
+
|
|
413
|
+
interface MyPluginOptions {
|
|
414
|
+
debug: boolean;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function myPlugin(options: MyPluginOptions): Plugin {
|
|
418
|
+
return {
|
|
419
|
+
name: 'my-plugin',
|
|
420
|
+
|
|
421
|
+
install(app) {
|
|
422
|
+
// Add middleware
|
|
423
|
+
app.use(async (ctx, next) => {
|
|
424
|
+
if (options.debug) {
|
|
425
|
+
console.log(`${ctx.method} ${ctx.path}`);
|
|
426
|
+
}
|
|
427
|
+
await next();
|
|
428
|
+
});
|
|
429
|
+
},
|
|
430
|
+
|
|
431
|
+
destroy() {
|
|
432
|
+
// Cleanup on app.close()
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Usage
|
|
438
|
+
app.plugin(myPlugin({ debug: true }));
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
## Middleware Composition
|
|
442
|
+
|
|
443
|
+
### compose()
|
|
444
|
+
|
|
445
|
+
Combine multiple middleware into one:
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
import { compose } from '@nextrush/core';
|
|
449
|
+
|
|
450
|
+
const security = compose([cors(), helmet(), rateLimit()]);
|
|
451
|
+
|
|
452
|
+
app.use(security);
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Utilities
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
import { isMiddleware, flattenMiddleware } from '@nextrush/core';
|
|
459
|
+
|
|
460
|
+
// Check if value is middleware
|
|
461
|
+
isMiddleware(fn); // true/false
|
|
462
|
+
|
|
463
|
+
// Flatten nested arrays
|
|
464
|
+
flattenMiddleware([mw1, [mw2, mw3]]); // [mw1, mw2, mw3]
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## Router Composition
|
|
468
|
+
|
|
469
|
+
Mount routers directly on the application using `app.route()` — Hono-style composition:
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
import { createApp } from '@nextrush/core';
|
|
473
|
+
import { createRouter } from '@nextrush/router';
|
|
474
|
+
|
|
475
|
+
const app = createApp();
|
|
476
|
+
|
|
477
|
+
// Create feature routers
|
|
478
|
+
const users = createRouter();
|
|
479
|
+
users.get('/', (ctx) => ctx.json([]));
|
|
480
|
+
users.get('/:id', (ctx) => ctx.json({ id: ctx.params.id }));
|
|
481
|
+
|
|
482
|
+
const posts = createRouter();
|
|
483
|
+
posts.get('/', (ctx) => ctx.json([]));
|
|
484
|
+
|
|
485
|
+
// Mount directly — clean like Hono!
|
|
486
|
+
app.route('/api/users', users);
|
|
487
|
+
app.route('/api/posts', posts);
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Benefits over Classic Pattern
|
|
491
|
+
|
|
492
|
+
| Classic Pattern | Hono-Style Composition |
|
|
493
|
+
| ------------------------------------------------------------------- | ---------------------------- |
|
|
494
|
+
| `router.use('/users', usersRouter)` then `app.use(router.routes())` | `app.route('/users', users)` |
|
|
495
|
+
| Requires main router | Direct mounting |
|
|
496
|
+
| Extra `.routes()` call | No extra calls |
|
|
497
|
+
|
|
498
|
+
### Classic Pattern Still Works
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
// The traditional approach still works
|
|
502
|
+
const router = createRouter();
|
|
503
|
+
router.use('/users', usersRouter);
|
|
504
|
+
router.use('/posts', postsRouter);
|
|
505
|
+
app.route('/', router);
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
## Lifecycle
|
|
509
|
+
|
|
510
|
+
### Starting
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
// Adapters call app.start() internally
|
|
514
|
+
app.start();
|
|
515
|
+
console.log(app.isRunning); // true
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Shutdown
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
// Graceful shutdown — returns errors from plugins that failed to destroy
|
|
522
|
+
const errors = await app.close();
|
|
523
|
+
|
|
524
|
+
// What happens:
|
|
525
|
+
// 1. Sets isRunning = false
|
|
526
|
+
// 2. Calls destroy() on all plugins (reverse registration order)
|
|
527
|
+
// 3. Collects errors via Promise.allSettled
|
|
528
|
+
// 4. Clears plugin registry
|
|
529
|
+
// 5. Returns Error[] (empty on success)
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
## Request Handler
|
|
533
|
+
|
|
534
|
+
Get the callback for HTTP server integration:
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
const callback = app.callback();
|
|
538
|
+
|
|
539
|
+
// Use with Node.js http
|
|
540
|
+
import http from 'http';
|
|
541
|
+
http.createServer(callback).listen(3000);
|
|
542
|
+
|
|
543
|
+
// Or use an adapter (recommended)
|
|
544
|
+
import { listen } from '@nextrush/adapter-node';
|
|
545
|
+
listen(app, { port: 3000 });
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
## API Reference
|
|
549
|
+
|
|
550
|
+
### Exports
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
import {
|
|
554
|
+
// Application
|
|
555
|
+
createApp,
|
|
556
|
+
Application,
|
|
557
|
+
|
|
558
|
+
// Middleware
|
|
559
|
+
compose,
|
|
560
|
+
isMiddleware,
|
|
561
|
+
flattenMiddleware,
|
|
562
|
+
|
|
563
|
+
// Errors
|
|
564
|
+
HttpError,
|
|
565
|
+
NextRushError,
|
|
566
|
+
NotFoundError,
|
|
567
|
+
BadRequestError,
|
|
568
|
+
UnauthorizedError,
|
|
569
|
+
ForbiddenError,
|
|
570
|
+
InternalServerError,
|
|
571
|
+
createHttpError,
|
|
572
|
+
|
|
573
|
+
// Re-exports from @nextrush/types
|
|
574
|
+
HttpStatus,
|
|
575
|
+
ContentType,
|
|
576
|
+
} from '@nextrush/core';
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### Types
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
import type {
|
|
583
|
+
// Application
|
|
584
|
+
ApplicationOptions,
|
|
585
|
+
ErrorHandler,
|
|
586
|
+
ListenCallback,
|
|
587
|
+
Logger,
|
|
588
|
+
Routable,
|
|
589
|
+
ComposedMiddleware,
|
|
590
|
+
|
|
591
|
+
// Context & Middleware (from @nextrush/types)
|
|
592
|
+
Context,
|
|
593
|
+
ContextState,
|
|
594
|
+
Middleware,
|
|
595
|
+
Next,
|
|
596
|
+
Plugin,
|
|
597
|
+
PluginWithHooks,
|
|
598
|
+
RouteHandler,
|
|
599
|
+
RouteParams,
|
|
600
|
+
QueryParams,
|
|
601
|
+
HttpMethod,
|
|
602
|
+
HttpStatusCode,
|
|
603
|
+
} from '@nextrush/core';
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
## Runtime Compatibility
|
|
607
|
+
|
|
608
|
+
| Runtime | Supported |
|
|
609
|
+
| ------------------- | --------- |
|
|
610
|
+
| Node.js 22+ | ✅ |
|
|
611
|
+
| Bun 1.0+ | ✅ |
|
|
612
|
+
| Deno 2.0+ | ✅ |
|
|
613
|
+
| Cloudflare Workers | ✅ |
|
|
614
|
+
| Vercel Edge Runtime | ✅ |
|
|
615
|
+
|
|
616
|
+
The core package uses only standard JavaScript APIs. Runtime-specific code lives in adapters.
|
|
617
|
+
|
|
618
|
+
## Package Size
|
|
619
|
+
|
|
620
|
+
- **Bundle**: ~10 KB
|
|
621
|
+
- **Types**: ~8 KB
|
|
622
|
+
- **Dependencies**: `@nextrush/types`, `@nextrush/errors`
|
|
623
|
+
|
|
624
|
+
## License
|
|
625
|
+
|
|
626
|
+
MIT
|