@nextrush/core 3.0.6 → 4.0.0-beta.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 +322 -493
- package/dist/index.d.ts +193 -182
- package/dist/index.js +448 -296
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,626 +1,455 @@
|
|
|
1
1
|
# @nextrush/core
|
|
2
2
|
|
|
3
|
-
> The
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
3
|
+
> The engine behind every NextRush app — the `Application` object, the Koa-style middleware pipeline (`compose` + `ctx.next()`), router mounting, the extension lifecycle, and the default error response — for framework and adapter authors.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@nextrush/core)
|
|
6
|
+
[](https://www.npmjs.com/package/@nextrush/core)
|
|
7
|
+
[](https://bundlephobia.com/package/@nextrush/core)
|
|
8
|
+
[](https://www.npmjs.com/package/@nextrush/core)
|
|
9
|
+
[](https://nodejs.org/api/esm.html)
|
|
10
|
+
[](https://github.com/0xTanzim/nextRush/blob/main/LICENSE)
|
|
11
|
+
|
|
12
|
+
| | |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| **Purpose** | The `Application` object, middleware composition, router mounting, extension lifecycle, and default error handling for NextRush |
|
|
15
|
+
| **Package type** | Core |
|
|
16
|
+
| **Status** | Stable ✅ |
|
|
17
|
+
| **Included in `nextrush`?** | ✅ Yes — `createApp`, `compose`, and the common error classes are re-exported. The `nextrush` meta-package's `createApp` wraps this one and auto-injects a router. |
|
|
18
|
+
| **Support tier** | Public — core (stable, semver-guarded) — see [ADR-0005](https://github.com/0xTanzim/nextRush/blob/main/docs/adr/ADR-0005-package-tiers-sealed-surface-deprecation.md) |
|
|
19
|
+
| **Maintenance** | Active |
|
|
20
|
+
| **Runtime** | Universal — Node · Bun · Deno · Edge |
|
|
21
|
+
| **Requires** | Node `>=22` · ESM-only · TypeScript `>=5.x` |
|
|
22
|
+
| **Introduced** | `v3.0.0` |
|
|
23
|
+
|
|
24
|
+
## Highlights
|
|
25
|
+
|
|
26
|
+
- ✅ **No third-party dependencies** — depends only on `@nextrush/errors` and `@nextrush/types`
|
|
27
|
+
- ✅ **ESM-only**, tree-shakable, side-effect-free (`"sideEffects": false`)
|
|
28
|
+
- ✅ **Fully typed** — strict TypeScript, zero `any`; extension shapes inferred through `extend()`
|
|
29
|
+
- ✅ **Runtime-independent** — no `node:*` / `process` / runtime globals; the same code runs on every adapter
|
|
30
|
+
|
|
31
|
+
<details>
|
|
32
|
+
<summary><strong>Table of contents</strong></summary>
|
|
33
|
+
|
|
34
|
+
[The problem](#the-problem) · [When to use](#when-to-use) · [Installation](#installation) · [Quick start](#quick-start) · [Capabilities](#capabilities) · [Mental model](#mental-model) · [Common tasks](#common-tasks) · [API overview](#api-overview) · [Options](#options) · [Performance](#performance) · [Compatibility](#compatibility) · [Troubleshooting](#troubleshooting) · [FAQ](#faq) · [Package relationships](#package-relationships) · [Architecture](#architecture) · [Resources](#resources)
|
|
35
|
+
|
|
36
|
+
</details>
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## The problem
|
|
41
|
+
|
|
42
|
+
Every HTTP framework needs the same plumbing before it can serve a single request: a place to register middleware, a way to run that middleware in order and let each step wrap the next, somewhere to mount routers, a boot step for long-lived services, and a single error path so a thrown error never crashes the process or leaks a stack trace. Hand-rolled, that plumbing is where subtle bugs live.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// TODAY, without a composition engine — chaining middleware by hand:
|
|
46
|
+
async function handle(ctx) {
|
|
47
|
+
try {
|
|
48
|
+
await mwA(ctx, () => // each layer must remember to await the next…
|
|
49
|
+
mwB(ctx, () => // …and pass a `next` that's only safe to call once…
|
|
50
|
+
handler(ctx))); // …and one forgotten try/catch leaks the raw error.
|
|
51
|
+
} catch (err) {
|
|
52
|
+
ctx.status = 500;
|
|
53
|
+
ctx.json({ error: String(err) }); // shape drifts; internals leak
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
18
57
|
|
|
19
|
-
-
|
|
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
|
|
58
|
+
`@nextrush/core` owns exactly this plumbing so applications never write it. `compose()` runs the chain with correct ordering and single-call `next()` semantics, `Application` manages registration and a frozen-after-boot lifecycle, and one default error path serializes any thrown error through the same contract as `@nextrush/errors`.
|
|
23
59
|
|
|
24
|
-
|
|
60
|
+
## When to use
|
|
25
61
|
|
|
26
|
-
|
|
62
|
+
`@nextrush/core` is the engine the `nextrush` meta-package is built on. Most applications import `createApp` and `listen` from [`nextrush`](../nextrush) and never depend on `@nextrush/core` directly.
|
|
27
63
|
|
|
28
|
-
|
|
64
|
+
**Use `@nextrush/core` if:**
|
|
29
65
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
Before Before Execute After After
|
|
34
|
-
```
|
|
66
|
+
- ✓ You're building your own meta-package, runtime, or adapter on top of NextRush and need the raw `Application`
|
|
67
|
+
- ✓ You want `compose()` — the Koa-style middleware composer — as a standalone building block
|
|
68
|
+
- ✓ You're writing an extension or registrar and need the `Extension` / `ExtensionContext` contracts and the boot lifecycle
|
|
35
69
|
|
|
36
|
-
|
|
70
|
+
**Reach for something else if:**
|
|
37
71
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
72
|
+
- ✗ You're building an application — import `createApp`, `createRouter`, and `listen` from [`nextrush`](../nextrush); its `createApp` wires a router for you so `app.get(...)` works out of the box
|
|
73
|
+
- ✗ You need route matching — that's [`@nextrush/router`](../router); core only *mounts* a router, it doesn't match paths
|
|
74
|
+
- ✗ You need the concrete request/response object — the `Context` implementation is built by [`@nextrush/adapter-*`](../adapters); core defines the pipeline that operates on it
|
|
41
75
|
|
|
42
|
-
|
|
76
|
+
---
|
|
43
77
|
|
|
44
78
|
## Installation
|
|
45
79
|
|
|
46
80
|
```bash
|
|
47
81
|
pnpm add @nextrush/core
|
|
82
|
+
# npm i @nextrush/core · yarn add @nextrush/core · bun add @nextrush/core
|
|
48
83
|
```
|
|
49
84
|
|
|
50
|
-
|
|
85
|
+
> [!NOTE]
|
|
86
|
+
> Already using `nextrush`? `createApp`, `compose`, `isMiddleware`, and the common error classes are
|
|
87
|
+
> re-exported from the meta package — `import { createApp } from 'nextrush'` works without installing
|
|
88
|
+
> this directly, and that `createApp` auto-injects a router. Install `@nextrush/core` only to build
|
|
89
|
+
> your own meta-package/adapter or to use `compose()` on its own.
|
|
51
90
|
|
|
52
|
-
|
|
53
|
-
import { createApp } from '@nextrush/core';
|
|
54
|
-
import { listen } from '@nextrush/adapter-node';
|
|
91
|
+
## Quick start
|
|
55
92
|
|
|
56
|
-
|
|
93
|
+
```ts
|
|
94
|
+
import { createApp, createRouter, listen } from 'nextrush';
|
|
57
95
|
|
|
58
|
-
|
|
59
|
-
app.use(async (ctx, next) => {
|
|
60
|
-
console.log(`→ ${ctx.method} ${ctx.path}`);
|
|
61
|
-
await next();
|
|
62
|
-
console.log(`← ${ctx.status}`);
|
|
63
|
-
});
|
|
96
|
+
const app = createApp();
|
|
64
97
|
|
|
65
|
-
//
|
|
98
|
+
// Middleware runs in registration order; each may await ctx.next().
|
|
66
99
|
app.use(async (ctx) => {
|
|
67
|
-
|
|
100
|
+
const start = Date.now();
|
|
101
|
+
await ctx.next();
|
|
102
|
+
ctx.set('x-response-time', `${Date.now() - start}ms`);
|
|
68
103
|
});
|
|
69
104
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
### Creating an Application
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
import { createApp, Application } from '@nextrush/core';
|
|
79
|
-
|
|
80
|
-
// Factory function (recommended)
|
|
81
|
-
const app = createApp();
|
|
105
|
+
// Feature router, mounted under a prefix (Hono-style composition).
|
|
106
|
+
const users = createRouter();
|
|
107
|
+
users.get('/:id', (ctx) => ctx.json({ id: ctx.params.id }));
|
|
108
|
+
app.route('/users', users);
|
|
82
109
|
|
|
83
|
-
|
|
84
|
-
const app = createApp({
|
|
85
|
-
env: 'production', // 'development' | 'production' | 'test'
|
|
86
|
-
proxy: true, // Trust proxy headers (X-Forwarded-*)
|
|
87
|
-
});
|
|
110
|
+
listen(app, 8080);
|
|
88
111
|
```
|
|
89
112
|
|
|
90
|
-
|
|
113
|
+
`createApp()` (from `nextrush`) gives you an `Application` with a router already wired in. You register middleware and routers against it; `listen()` calls `app.ready()` to boot, then starts the adapter. That boot step is why extensions and mounted routers are all in place before the first request.
|
|
91
114
|
|
|
92
|
-
|
|
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. |
|
|
115
|
+
## Capabilities
|
|
97
116
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
app.middlewareCount; // number - count of registered middleware
|
|
104
|
-
app.options; // ApplicationOptions - readonly config
|
|
105
|
-
app.logger; // Logger - readonly configured logger instance
|
|
106
|
-
```
|
|
117
|
+
**Application**
|
|
118
|
+
- **Middleware registration** — `app.use(...mw)`, chainable, validated (a non-function throws)
|
|
119
|
+
- **Router mounting** — `app.route(prefix, router)` mounts a feature router, rewriting `ctx.path` under the prefix
|
|
120
|
+
- **Route shortcuts** — `app.get/post/put/patch/delete/head/all(...)` delegate to the app-owned router
|
|
121
|
+
- **Frozen-after-boot lifecycle** — `ready()` -> `start()` -> `close()`, with config frozen once booted
|
|
107
122
|
|
|
108
|
-
|
|
123
|
+
**Middleware pipeline**
|
|
124
|
+
- **`compose()`** — Koa-style composition; each middleware may `await ctx.next()` to wrap downstream
|
|
125
|
+
- **Single-call `next()`** — a second `next()` rejects with `next() called multiple times`
|
|
126
|
+
- **Double-response warning** — opt-in dev warning when a middleware responds *and* calls `next()`
|
|
109
127
|
|
|
110
|
-
|
|
128
|
+
**Extensions**
|
|
129
|
+
- **`app.extend(ext)`** — register a long-lived service; `setup()` runs at `ready()`, `destroy()` at `close()`
|
|
130
|
+
- **Typed decorations** — `extend()` returns `this & TDecorated`, so `app.events` is statically inferred
|
|
131
|
+
- **Dependency assertion** — an extension's `needs` are checked at boot (registration order, no auto-sort)
|
|
111
132
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
app.
|
|
115
|
-
await next();
|
|
116
|
-
});
|
|
133
|
+
**Error handling**
|
|
134
|
+
- **One default error path** — any thrown error is serialized through the `@nextrush/errors` contract
|
|
135
|
+
- **Custom handler** — `app.setErrorHandler((err, ctx) => ...)` overrides the default
|
|
117
136
|
|
|
118
|
-
|
|
119
|
-
|
|
137
|
+
**Performance**
|
|
138
|
+
- **Compose fast paths** — dedicated zero- and single-middleware paths avoid the recursive dispatch closure
|
|
139
|
+
- **Snapshot at compose** — the middleware array is snapshotted once, not walked per request
|
|
120
140
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
141
|
+
**Developer experience**
|
|
142
|
+
- **Fully typed, zero `any`** — contracts shared via `@nextrush/types`
|
|
143
|
+
- **Actionable errors** — misconfiguration (no router, config frozen, duplicate extension) throws with a fix
|
|
124
144
|
|
|
125
|
-
|
|
145
|
+
## Mental model
|
|
126
146
|
|
|
127
|
-
|
|
147
|
+
An `Application` is a **list of middleware plus a lifecycle**. `compose()` turns that list into one function where each middleware can wrap the next by `await`-ing `ctx.next()`; the app-owned router is mounted **last**, so it runs after all middleware.
|
|
128
148
|
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
});
|
|
149
|
+
```text
|
|
150
|
+
createApp() --> app.use(mw) --> app.route(prefix, router) --> await app.ready()
|
|
151
|
+
|
|
|
152
|
+
compose([...mw, router.routes()])
|
|
153
|
+
|
|
|
154
|
+
request --> ctx --> mw1 -> mw2 -> ... -> router --> handler --> ctx.json(...)
|
|
155
|
+
|______ await next() wraps downstream, unwinds back up ______|
|
|
143
156
|
```
|
|
144
157
|
|
|
145
|
-
|
|
158
|
+
**Rule:** `use`, `route`, and `extend` configure the app; `ready()` freezes that config and boots extensions. Configure before `ready()`, serve after — calling a configuration method post-boot throws.
|
|
146
159
|
|
|
147
|
-
|
|
160
|
+
> [!TIP]
|
|
161
|
+
> The full request lifecycle, the app state machine, and the `Application`/`Context` structure (with
|
|
162
|
+
> Mermaid diagrams) are in [`ARCHITECTURE.md`](./ARCHITECTURE.md).
|
|
148
163
|
|
|
149
|
-
|
|
164
|
+
---
|
|
150
165
|
|
|
151
|
-
|
|
152
|
-
app.use(async (ctx, next) => {
|
|
153
|
-
console.log('1: Start');
|
|
154
|
-
await next();
|
|
155
|
-
console.log('1: End');
|
|
156
|
-
});
|
|
166
|
+
## Common tasks
|
|
157
167
|
|
|
158
|
-
|
|
159
|
-
console.log('2: Start');
|
|
160
|
-
await next();
|
|
161
|
-
console.log('2: End');
|
|
162
|
-
});
|
|
168
|
+
### Register middleware
|
|
163
169
|
|
|
170
|
+
```ts
|
|
171
|
+
// Modern form — ctx.next()
|
|
164
172
|
app.use(async (ctx) => {
|
|
165
|
-
|
|
166
|
-
ctx.
|
|
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`);
|
|
173
|
+
await ctx.next();
|
|
174
|
+
ctx.set('x-powered-by', 'nextrush');
|
|
190
175
|
});
|
|
191
|
-
```
|
|
192
176
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
Skip remaining middleware by not calling `next()`:
|
|
196
|
-
|
|
197
|
-
```typescript
|
|
177
|
+
// Traditional form — (ctx, next)
|
|
198
178
|
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
179
|
await next();
|
|
205
180
|
});
|
|
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
181
|
|
|
242
|
-
|
|
243
|
-
// Set by router when route matches
|
|
244
|
-
app.get('/users/:id', (ctx) => {
|
|
245
|
-
const { id } = ctx.params;
|
|
246
|
-
ctx.json({ id });
|
|
247
|
-
});
|
|
182
|
+
app.use(mwA, mwB, mwC); // register several at once; runs in this order
|
|
248
183
|
```
|
|
249
184
|
|
|
250
|
-
|
|
185
|
+
Both signatures are supported. Middleware runs in registration order, and each layer wraps the ones after it.
|
|
251
186
|
|
|
252
|
-
|
|
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 |
|
|
187
|
+
### Mount feature routers
|
|
261
188
|
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
// Set status
|
|
265
|
-
ctx.status = 201;
|
|
189
|
+
```ts
|
|
190
|
+
import { createApp, createRouter } from 'nextrush';
|
|
266
191
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
192
|
+
const app = createApp();
|
|
193
|
+
const api = createRouter();
|
|
194
|
+
api.get('/health', (ctx) => ctx.json({ ok: true }));
|
|
270
195
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
});
|
|
196
|
+
app.route('/api', api); // mounted under /api; ctx.path is rewritten for the sub-router
|
|
197
|
+
app.route('/', api); // root mount — no prefix processing
|
|
274
198
|
```
|
|
275
199
|
|
|
276
|
-
|
|
200
|
+
`app.route()` strips the prefix from `ctx.path` while the sub-router runs and restores it afterwards, so middleware registered after the mount still sees the original path.
|
|
277
201
|
|
|
278
|
-
|
|
279
|
-
app.use(async (ctx) => {
|
|
280
|
-
// Throw HTTP error
|
|
281
|
-
ctx.throw(404, 'User not found');
|
|
282
|
-
ctx.throw(401); // Uses default message
|
|
202
|
+
### Set a custom error handler
|
|
283
203
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
ctx.
|
|
204
|
+
```ts
|
|
205
|
+
app.setErrorHandler((err, ctx) => {
|
|
206
|
+
ctx.status = 500;
|
|
207
|
+
ctx.json({ error: 'Something went wrong' });
|
|
287
208
|
});
|
|
288
209
|
```
|
|
289
210
|
|
|
290
|
-
|
|
211
|
+
If you set no handler, core writes the same JSON shape as `@nextrush/errors`' `errorHandler()` — a `NextRushError` serializes via its own `toJSON()`; any other error becomes a safe, coded `500` (its internal message is never sent to the client).
|
|
291
212
|
|
|
292
|
-
|
|
213
|
+
### Register an extension (long-lived service)
|
|
293
214
|
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
ctx.state.user = await validateToken(ctx.get('Authorization'));
|
|
298
|
-
await next();
|
|
299
|
-
});
|
|
215
|
+
```ts
|
|
216
|
+
import { createApp } from 'nextrush';
|
|
217
|
+
import { events } from '@nextrush/events';
|
|
300
218
|
|
|
301
|
-
//
|
|
302
|
-
app.
|
|
303
|
-
|
|
304
|
-
ctx.json({ user });
|
|
305
|
-
});
|
|
219
|
+
const app = createApp().extend(events()); // chain in one expression
|
|
220
|
+
await app.ready(); // adapters call this automatically
|
|
221
|
+
app.events.emit('user:created', { id: '1' }); // available only AFTER ready()
|
|
306
222
|
```
|
|
307
223
|
|
|
308
|
-
|
|
224
|
+
`extend()` queues the extension and returns `this & TDecorated`; `setup()` runs once at `ready()` in registration order, and `destroy()` runs at `close()` in reverse. Anything an extension decorates (like `app.events`) exists only after `ready()`.
|
|
309
225
|
|
|
310
|
-
|
|
226
|
+
### Compose middleware standalone
|
|
311
227
|
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
ctx.raw.req; // IncomingMessage
|
|
315
|
-
ctx.raw.res; // ServerResponse
|
|
228
|
+
```ts
|
|
229
|
+
import { compose } from '@nextrush/core';
|
|
316
230
|
|
|
317
|
-
|
|
318
|
-
ctx
|
|
231
|
+
const chain = compose([mwA, mwB, handler], { warnDoubleResponse: true });
|
|
232
|
+
await chain(ctx); // run the composed chain against a Context
|
|
319
233
|
```
|
|
320
234
|
|
|
321
|
-
|
|
235
|
+
`compose()` is the pipeline on its own — useful for building sub-pipelines or your own runtime. It validates its input, snapshots the array, and enforces single-call `next()` semantics.
|
|
322
236
|
|
|
323
|
-
|
|
237
|
+
## API overview
|
|
324
238
|
|
|
325
|
-
|
|
326
|
-
app.setErrorHandler((error, ctx) => {
|
|
327
|
-
console.error('Request failed:', error);
|
|
239
|
+
The sealed public surface (ADR-0005).
|
|
328
240
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
241
|
+
| Export | Signature | Since | Stability | Description |
|
|
242
|
+
| ------ | --------- | ----- | --------- | ----------- |
|
|
243
|
+
| `createApp` | `(options?: ApplicationOptions) => Application` | `3.0.0` | Stable ✅ | Create an `Application`. (The `nextrush` meta-package wraps this to inject a router.) |
|
|
244
|
+
| `Application` | `class` | `3.0.0` | Stable ✅ | The application object (methods below). |
|
|
245
|
+
| `compose` | `(middleware: Middleware[], options?: ComposeOptions) => ComposedMiddleware` | `3.0.0` | Stable ✅ | Koa-style middleware composer. |
|
|
246
|
+
| `isMiddleware` | `(fn: unknown) => fn is Middleware` | `3.0.0` | Stable ✅ | Type guard — a middleware is any function. |
|
|
247
|
+
| `flattenMiddleware` | `(arr: (Middleware \| Middleware[])[]) => Middleware[]` | `3.0.0` | Stable ✅ | Flatten nested middleware arrays (bounded depth), validating each entry. |
|
|
248
|
+
| `type ApplicationOptions` · `ErrorHandler` · `ListenCallback` · `Routable` | — | `3.0.0` | Stable ✅ | Application-level contracts. |
|
|
249
|
+
| `type ComposeOptions` · `ComposedMiddleware` | — | `3.0.0` | Stable ✅ | Composition contracts. |
|
|
250
|
+
| Error classes | `NextRushError` · `HttpError` · `BadRequestError` · `UnauthorizedError` · `ForbiddenError` · `NotFoundError` · `InternalServerError` | `3.0.0` | Stable ✅ | Re-exported from [`@nextrush/errors`](../errors) for convenience. |
|
|
251
|
+
| Type re-exports | `Context` · `ContextState` · `Extension` · `ExtensionContext` · `ExtensionHost` · `Middleware` · `Next` · `Router` · `RouteEntry` · `RouteHandler` · `RouteParams` · `QueryParams` · `HttpMethod` · `HttpStatusCode` · `Logger` | — | `3.0.0` | Stable ✅ | Re-exported from [`@nextrush/types`](../types). |
|
|
252
|
+
| Constant re-exports | `ContentType` · `HttpStatus` | — | `3.0.0` | Stable ✅ | Re-exported from `@nextrush/types`. |
|
|
334
253
|
|
|
335
|
-
|
|
336
|
-
error: error.message,
|
|
337
|
-
code: error.code || 'UNKNOWN',
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
```
|
|
254
|
+
### `Application` methods
|
|
341
255
|
|
|
342
|
-
|
|
256
|
+
| Member | Signature | Description |
|
|
257
|
+
| ------ | --------- | ----------- |
|
|
258
|
+
| `use` | `(...middleware: Middleware[]) => this` | Register middleware. Throws after boot; a non-function throws `TypeError`. |
|
|
259
|
+
| `route` | `(path: string, router: Routable) => this` | Mount anything with a `routes()` method at a prefix (`/` = root mount). |
|
|
260
|
+
| `get` / `post` / `put` / `patch` / `delete` / `head` / `all` | `(path, ...entries: RouteEntry[]) => this` | Delegate to the app-owned router. Requires a router (see FAQ). No `options()` verb — use `all()`. |
|
|
261
|
+
| `setErrorHandler` | `(handler: ErrorHandler) => this` | Replace the default error handler. |
|
|
262
|
+
| `extend` | `<TDecorated>(extension: Extension<TDecorated>) => this & TDecorated` | Queue an extension; `setup()` runs at `ready()`. |
|
|
263
|
+
| `ready` | `() => Promise<this>` | Boot: run each extension's `setup()`, mount the router last, freeze config. Idempotent. |
|
|
264
|
+
| `callback` | `() => (ctx: Context) => Promise<void>` | The request handler — snapshots the middleware stack and wraps it in the error path. |
|
|
265
|
+
| `start` | `() => void` | Mark the app running (adapters call this after `ready()`). |
|
|
266
|
+
| `close` | `() => Promise<Error[]>` | Graceful shutdown: `destroy()` extensions in reverse order; returns any failures. |
|
|
267
|
+
| `hasDecorator` | `(name: string) => boolean` | Whether a decoration already occupies `name`. |
|
|
268
|
+
| `isReady` / `isRunning` / `isProduction` | `boolean` (getters) | Lifecycle/env state. |
|
|
269
|
+
| `middlewareCount` / `extensionCount` | `number` (getters) | Registration counts. |
|
|
270
|
+
| `logger` / `options` / `router` / `container` | readonly | The configured logger, options, app-owned router, and DI container (last two optional). |
|
|
343
271
|
|
|
344
|
-
|
|
272
|
+
## Options
|
|
345
273
|
|
|
346
|
-
|
|
274
|
+
`createApp(options?)` takes `ApplicationOptions`; `compose(_, options?)` takes `ComposeOptions`.
|
|
347
275
|
|
|
348
|
-
|
|
349
|
-
|
|
276
|
+
| Option | Type | Required | Default | Security-sensitive | Description |
|
|
277
|
+
| ------ | ---- | -------- | ------- | ------------------ | ----------- |
|
|
278
|
+
| `env` | `'development' \| 'production' \| 'test'` | No | `'development'` | — | Environment mode. `production` hides dev warnings and error internals. |
|
|
279
|
+
| `proxy` | `boolean` | No | `false` | ⚠️ | Whether to trust proxy headers (`X-Forwarded-For`, etc.). Enable only behind a trusted proxy. |
|
|
280
|
+
| `logger` | `Logger` | No | no-op (silent) | — | Structured logger. Pass `console` for dev; a real logger (`@nextrush/logger`, pino) for prod. |
|
|
281
|
+
| `router` | `Router` | No | `undefined` | — | The app-owned router `app.get(...)` delegates to. The `nextrush` `createApp` injects one. |
|
|
282
|
+
| `container` | `Container` | No | `undefined` | — | Per-app DI container, exposed to extensions/registrars. Injected by `nextrush/class`. |
|
|
283
|
+
| `warnDoubleResponse` | `boolean` (on `ComposeOptions`) | No | `false` | — | Warn when a middleware responds *and* calls `next()`. `Application` enables it in non-production. |
|
|
350
284
|
|
|
351
|
-
|
|
352
|
-
// Production mode hides sensitive details
|
|
353
|
-
const app = createApp({ env: 'production' });
|
|
285
|
+
## Performance
|
|
354
286
|
|
|
355
|
-
|
|
356
|
-
throw new Error('Database connection failed'); // User sees "Internal Server Error"
|
|
357
|
-
});
|
|
358
|
-
```
|
|
287
|
+
Core is on the request hot path — `callback()` wraps every request and `compose()` runs it — so the pipeline is built to allocate as little as possible per request:
|
|
359
288
|
|
|
360
|
-
|
|
289
|
+
- **The middleware array is snapshotted once** at `compose()` time, not re-read per request.
|
|
290
|
+
- **Two fast paths bypass the general dispatcher.** An empty chain returns a trivial pass-through; the **single-middleware** case (the overwhelmingly common shape — one mounted router) uses a flat closure with a per-invocation guard instead of the recursive `dispatch` used for longer chains.
|
|
291
|
+
- **`next()` guarding is per-invocation**, declared inside the returned function, so concurrent requests never corrupt each other's call state.
|
|
361
292
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
ForbiddenError,
|
|
369
|
-
InternalServerError,
|
|
370
|
-
} from '@nextrush/core';
|
|
293
|
+
> [!NOTE]
|
|
294
|
+
> For reproducible throughput numbers on your own hardware, run the suite in
|
|
295
|
+
> [`apps/benchmark`](https://github.com/0xTanzim/nextRush/tree/main/apps/benchmark). Published
|
|
296
|
+
> figures are being re-measured on a hardened harness — see the
|
|
297
|
+
> [root README's Performance note](https://github.com/0xTanzim/nextRush#performance) — so this
|
|
298
|
+
> package documents the pipeline's allocation characteristics, not point numbers.
|
|
371
299
|
|
|
372
|
-
|
|
373
|
-
throw new NotFoundError('User not found');
|
|
374
|
-
throw new BadRequestError('Invalid email');
|
|
375
|
-
throw new UnauthorizedError('Token expired');
|
|
376
|
-
});
|
|
377
|
-
```
|
|
300
|
+
## Compatibility
|
|
378
301
|
|
|
379
|
-
|
|
302
|
+
**Requirements**
|
|
380
303
|
|
|
381
|
-
|
|
304
|
+
| Requirement | Version |
|
|
305
|
+
| ----------- | ------- |
|
|
306
|
+
| NextRush | `3.x` |
|
|
307
|
+
| Node.js | `>=22` |
|
|
308
|
+
| TypeScript | `>=5.x` |
|
|
382
309
|
|
|
383
|
-
|
|
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' }));
|
|
310
|
+
**Runtimes**
|
|
393
311
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
> **Note:** `pluginAsync()` is deprecated. Use `plugin()` instead — it detects async `install()` methods and returns a `Promise` when needed.
|
|
312
|
+
| Runtime | Supported | Notes |
|
|
313
|
+
| ------- | --------- | ----- |
|
|
314
|
+
| Node.js `>=22` | ✅ | ESM-only |
|
|
315
|
+
| Bun / Deno / Edge | ✅ / ✅ / ✅ | Uses only Web-standard JavaScript; no `node:*` API. Parity held by `@nextrush/adapter-*` |
|
|
399
316
|
|
|
400
|
-
|
|
317
|
+
**Integration**
|
|
318
|
+
- **Peer dependencies:** none — depends on `@nextrush/errors` (default error shape) and `@nextrush/types` (contracts; types erased at build).
|
|
319
|
+
- **Works with:** [`@nextrush/router`](../router) (mounted via `app.route()`), [`@nextrush/runtime`](../runtime) (`listen`), `@nextrush/adapter-*` (build the `Context`), any `@nextrush/*` middleware/extension.
|
|
320
|
+
- **Incompatible with:** none.
|
|
401
321
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
```
|
|
322
|
+
> [!IMPORTANT]
|
|
323
|
+
> NextRush is **ESM-only, permanently** — no CommonJS build. On Node `>=22`, CommonJS consumers
|
|
324
|
+
> can `require()` this ESM package natively. See the
|
|
325
|
+
> [Module Format Policy](https://github.com/0xTanzim/nextRush#module-format-policy).
|
|
407
326
|
|
|
408
|
-
|
|
327
|
+
---
|
|
409
328
|
|
|
410
|
-
|
|
411
|
-
import type { Plugin } from '@nextrush/types';
|
|
329
|
+
## Troubleshooting
|
|
412
330
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
331
|
+
<details>
|
|
332
|
+
<summary><strong><code>Error: No router configured</code> when calling <code>app.get(...)</code></strong></summary>
|
|
416
333
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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 }));
|
|
334
|
+
**Cause:** you created the app with `@nextrush/core`'s own `createApp()`, which does **not** inject a router. **Fix:** import `createApp` from `nextrush` (it injects one), or pass one explicitly.
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
import { createApp } from 'nextrush'; // ← injects a router; app.get works
|
|
338
|
+
// or, using @nextrush/core directly:
|
|
339
|
+
import { createApp } from '@nextrush/core';
|
|
340
|
+
import { createRouter } from '@nextrush/router';
|
|
341
|
+
const app = createApp({ router: createRouter() });
|
|
439
342
|
```
|
|
440
343
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
### compose()
|
|
344
|
+
</details>
|
|
444
345
|
|
|
445
|
-
|
|
346
|
+
<details>
|
|
347
|
+
<summary><strong><code>Cannot call use() after the app has booted ... configuration is frozen</code></strong></summary>
|
|
446
348
|
|
|
447
|
-
|
|
448
|
-
import { compose } from '@nextrush/core';
|
|
449
|
-
|
|
450
|
-
const security = compose([cors(), helmet(), rateLimit()]);
|
|
451
|
-
|
|
452
|
-
app.use(security);
|
|
453
|
-
```
|
|
349
|
+
**Cause:** `use` / `route` / `extend` / `get` were called after `ready()` or `start()` — configuration is frozen once the app boots. **Fix:** register everything before `await app.ready()` (and adapters call `ready()` for you inside `listen`).
|
|
454
350
|
|
|
455
|
-
|
|
351
|
+
</details>
|
|
456
352
|
|
|
457
|
-
|
|
458
|
-
|
|
353
|
+
<details>
|
|
354
|
+
<summary><strong><code>Error: next() called multiple times</code></strong></summary>
|
|
459
355
|
|
|
460
|
-
|
|
461
|
-
isMiddleware(fn); // true/false
|
|
356
|
+
**Cause:** a middleware called `next()` (or `ctx.next()`) more than once in the same invocation. Each dispatch allows exactly one advance. **Fix:** call `next()` once; to run work after downstream, `await next()` and continue below it.
|
|
462
357
|
|
|
463
|
-
|
|
464
|
-
|
|
358
|
+
```ts
|
|
359
|
+
app.use(async (ctx) => {
|
|
360
|
+
await ctx.next(); // once
|
|
361
|
+
ctx.set('x-done', '1'); // work after downstream — no second next()
|
|
362
|
+
});
|
|
465
363
|
```
|
|
466
364
|
|
|
467
|
-
|
|
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 }));
|
|
365
|
+
</details>
|
|
481
366
|
|
|
482
|
-
|
|
483
|
-
|
|
367
|
+
<details>
|
|
368
|
+
<summary><strong>Console warning: middleware called next() after the response was already committed</strong></summary>
|
|
484
369
|
|
|
485
|
-
|
|
486
|
-
app.route('/api/users', users);
|
|
487
|
-
app.route('/api/posts', posts);
|
|
488
|
-
```
|
|
370
|
+
**Cause:** a middleware sent a response (e.g. `ctx.json(...)`) *and* also called `next()`, so downstream may write to a finished response. This warning is on in non-production. **Fix:** either `await next()` to delegate, or respond without calling `next()` — not both.
|
|
489
371
|
|
|
490
|
-
|
|
372
|
+
</details>
|
|
491
373
|
|
|
492
|
-
|
|
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 |
|
|
374
|
+
<details>
|
|
375
|
+
<summary><strong>An un-awaited async call crashed the process</strong></summary>
|
|
497
376
|
|
|
498
|
-
|
|
377
|
+
**Cause:** NextRush installs **no** global `unhandledRejection` or `uncaughtException` handler by
|
|
378
|
+
default — the framework's core is deliberately silent on process-level policy, so an application
|
|
379
|
+
owns that decision rather than inheriting a hidden one. Fire-and-forget work (an un-awaited
|
|
380
|
+
`app.events.emit(...)`, a detached WebSocket message handler, a background task started from a
|
|
381
|
+
middleware) that later rejects surfaces as an **unhandled rejection**, and depending on your
|
|
382
|
+
process manager/runtime that can terminate the process. **Fix:** guard every detached call —
|
|
383
|
+
`void somePromise().catch((err) => logger.error(err))` — or explicitly install your own
|
|
384
|
+
`process.on('unhandledRejection', ...)` policy if your deployment needs one. This applies
|
|
385
|
+
everywhere NextRush hands you a promise you don't have to await synchronously: extension
|
|
386
|
+
`destroy()`/`onClose` hooks are awaited and isolated by the framework (see `app.onClose`), but a
|
|
387
|
+
promise your own handler code starts and does not return or await is your responsibility.
|
|
499
388
|
|
|
500
|
-
```
|
|
501
|
-
//
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
## Lifecycle
|
|
509
|
-
|
|
510
|
-
### Starting
|
|
389
|
+
```ts
|
|
390
|
+
// Risky — a rejection here is unhandled, not caught by anything in the pipeline.
|
|
391
|
+
app.use(async (ctx) => {
|
|
392
|
+
app.events.emit('audit:logged', { path: ctx.path }); // not awaited
|
|
393
|
+
ctx.json({ ok: true });
|
|
394
|
+
});
|
|
511
395
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
app.
|
|
515
|
-
|
|
396
|
+
// Safe — the detached call is explicitly guarded.
|
|
397
|
+
app.use(async (ctx) => {
|
|
398
|
+
void app.events.emit('audit:logged', { path: ctx.path }).catch((err: unknown) => {
|
|
399
|
+
app.logger.error('audit emit failed:', err);
|
|
400
|
+
});
|
|
401
|
+
ctx.json({ ok: true });
|
|
402
|
+
});
|
|
516
403
|
```
|
|
517
404
|
|
|
518
|
-
|
|
405
|
+
</details>
|
|
519
406
|
|
|
520
|
-
|
|
521
|
-
// Graceful shutdown — returns errors from plugins that failed to destroy
|
|
522
|
-
const errors = await app.close();
|
|
407
|
+
## FAQ
|
|
523
408
|
|
|
524
|
-
|
|
525
|
-
|
|
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
|
-
```
|
|
409
|
+
**Why does `app.get(...)` work with `nextrush`'s `createApp` but throw with `@nextrush/core`'s?**
|
|
410
|
+
The `nextrush` meta-package's `createApp` injects an app-owned router, so the route shortcuts have something to delegate to. `@nextrush/core`'s `createApp` is router-agnostic by design — pass `createApp({ router: createRouter() })` if you use it directly.
|
|
531
411
|
|
|
532
|
-
|
|
412
|
+
**Why does configuration freeze after `ready()`?**
|
|
413
|
+
Boot runs every extension's `setup()`, mounts the app-owned router last, and snapshots state. Allowing `use`/`route`/`extend` after that would mean middleware or extensions that never boot, or a router mounted in the wrong order — so those methods throw once the app is ready.
|
|
533
414
|
|
|
534
|
-
|
|
415
|
+
**Why ESM-only?**
|
|
416
|
+
See the [Module Format Policy](https://github.com/0xTanzim/nextRush#module-format-policy).
|
|
535
417
|
|
|
536
|
-
|
|
537
|
-
|
|
418
|
+
**Does it work on Bun, Deno, and Edge?**
|
|
419
|
+
Yes. Core uses only Web-standard JavaScript — no `node:*` APIs, no `process` — so it behaves identically on every supported runtime; platform specifics live in `@nextrush/adapter-*`, and parity is enforced by the conformance suite.
|
|
538
420
|
|
|
539
|
-
|
|
540
|
-
import http from 'http';
|
|
541
|
-
http.createServer(callback).listen(3000);
|
|
421
|
+
---
|
|
542
422
|
|
|
543
|
-
|
|
544
|
-
import { listen } from '@nextrush/adapter-node';
|
|
545
|
-
listen(app, { port: 3000 });
|
|
546
|
-
```
|
|
423
|
+
## Package relationships
|
|
547
424
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
```
|
|
425
|
+
```text
|
|
426
|
+
depends on @nextrush/errors (default error response shape)
|
|
427
|
+
@nextrush/core ─────────────▶ @nextrush/types (Context / Middleware / Extension contracts)
|
|
578
428
|
|
|
579
|
-
|
|
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';
|
|
429
|
+
often used with @nextrush/router (mounted via app.route())
|
|
430
|
+
usually used next @nextrush/runtime (listen) · @nextrush/adapter-* (build Context)
|
|
604
431
|
```
|
|
605
432
|
|
|
606
|
-
|
|
433
|
+
- **Depends on:** [`@nextrush/errors`](../errors) — the default error path reuses its serializer · [`@nextrush/types`](../types) — the shared `Context` / `Middleware` / `Extension` contracts (types erased at build).
|
|
434
|
+
- **Often used with:** [`@nextrush/router`](../router) — the router `app.route()` mounts and `app.get(...)` delegates to.
|
|
435
|
+
- **Usually used next:** [`@nextrush/runtime`](../runtime) — `listen(app, port)` boots and starts the app · `@nextrush/adapter-*` — build the concrete `Context` from a platform request.
|
|
436
|
+
- **Alternative:** none — `Application` and `compose` are the framework's composition core.
|
|
607
437
|
|
|
608
|
-
|
|
609
|
-
| ------------------- | --------- |
|
|
610
|
-
| Node.js 22+ | ✅ |
|
|
611
|
-
| Bun 1.0+ | ✅ |
|
|
612
|
-
| Deno 2.0+ | ✅ |
|
|
613
|
-
| Cloudflare Workers | ✅ |
|
|
614
|
-
| Vercel Edge Runtime | ✅ |
|
|
438
|
+
## Architecture
|
|
615
439
|
|
|
616
|
-
|
|
440
|
+
Maintaining or contributing to this package? The internal design — the `Application` lifecycle, the
|
|
441
|
+
`compose()` dispatcher and its fast paths, prefix mounting, the extension boot/teardown model, the
|
|
442
|
+
default error path, the architectural invariants, and the decisions and trade-offs behind them
|
|
443
|
+
(with diagrams) — is in **[`ARCHITECTURE.md`](./ARCHITECTURE.md)**. Design history:
|
|
444
|
+
[extension model RFC](https://github.com/0xTanzim/nextRush/tree/main/docs/RFC/class-runtime), [ADR-0005 (package tiers & sealed surface)](https://github.com/0xTanzim/nextRush/blob/main/docs/adr/ADR-0005-package-tiers-sealed-surface-deprecation.md).
|
|
617
445
|
|
|
618
|
-
##
|
|
446
|
+
## Resources
|
|
619
447
|
|
|
620
|
-
- **
|
|
621
|
-
- **
|
|
622
|
-
- **
|
|
448
|
+
- 📖 **Learn** — [Documentation](https://0xtanzim.github.io/nextRush/docs) · [Middleware concept](https://0xtanzim.github.io/nextRush/docs/concepts) · [Architecture](./ARCHITECTURE.md) · [RFCs](https://github.com/0xTanzim/nextRush/tree/main/docs/RFC)
|
|
449
|
+
- 📝 **Changelog** — [CHANGELOG.md](./CHANGELOG.md)
|
|
450
|
+
- 🐛 **Report an issue** — [GitHub Issues](https://github.com/0xTanzim/nextRush/issues)
|
|
451
|
+
- 🤝 **Contribute** — [CONTRIBUTING.md](https://github.com/0xTanzim/nextRush/blob/main/CONTRIBUTING.md)
|
|
623
452
|
|
|
624
|
-
|
|
453
|
+
---
|
|
625
454
|
|
|
626
|
-
MIT
|
|
455
|
+
MIT © [Tanzim Hossain](https://github.com/0xTanzim)
|