express-fastify-runtime 0.1.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 +326 -0
- package/changelog/README.md +5 -0
- package/changelog/log-2025-02-07.md +10 -0
- package/changelog/log-2026-06-25.md +151 -0
- package/dist/app/ExpressLikeApp.d.ts +9 -0
- package/dist/app/ExpressLikeApp.d.ts.map +1 -0
- package/dist/app/ExpressLikeApp.js +64 -0
- package/dist/app/ExpressLikeApp.js.map +1 -0
- package/dist/app/RouteStore.d.ts +17 -0
- package/dist/app/RouteStore.d.ts.map +1 -0
- package/dist/app/RouteStore.js +43 -0
- package/dist/app/RouteStore.js.map +1 -0
- package/dist/app/classify.d.ts +8 -0
- package/dist/app/classify.d.ts.map +1 -0
- package/dist/app/classify.js +25 -0
- package/dist/app/classify.js.map +1 -0
- package/dist/app/compile.d.ts +25 -0
- package/dist/app/compile.d.ts.map +1 -0
- package/dist/app/compile.js +195 -0
- package/dist/app/compile.js.map +1 -0
- package/dist/app/flattenRouter.d.ts +51 -0
- package/dist/app/flattenRouter.d.ts.map +1 -0
- package/dist/app/flattenRouter.js +126 -0
- package/dist/app/flattenRouter.js.map +1 -0
- package/dist/app/introspectExpress.d.ts +29 -0
- package/dist/app/introspectExpress.d.ts.map +1 -0
- package/dist/app/introspectExpress.js +60 -0
- package/dist/app/introspectExpress.js.map +1 -0
- package/dist/examples/auth.d.ts +6 -0
- package/dist/examples/auth.d.ts.map +1 -0
- package/dist/examples/auth.js +26 -0
- package/dist/examples/auth.js.map +1 -0
- package/dist/examples/logging.d.ts +6 -0
- package/dist/examples/logging.d.ts.map +1 -0
- package/dist/examples/logging.js +25 -0
- package/dist/examples/logging.js.map +1 -0
- package/dist/examples/uploads.d.ts +6 -0
- package/dist/examples/uploads.d.ts.map +1 -0
- package/dist/examples/uploads.js +21 -0
- package/dist/examples/uploads.js.map +1 -0
- package/dist/express/engine.d.ts +6 -0
- package/dist/express/engine.d.ts.map +1 -0
- package/dist/express/engine.js +14 -0
- package/dist/express/engine.js.map +1 -0
- package/dist/express/middleware.d.ts +15 -0
- package/dist/express/middleware.d.ts.map +1 -0
- package/dist/express/middleware.js +32 -0
- package/dist/express/middleware.js.map +1 -0
- package/dist/express/mount.d.ts +35 -0
- package/dist/express/mount.d.ts.map +1 -0
- package/dist/express/mount.js +78 -0
- package/dist/express/mount.js.map +1 -0
- package/dist/fastify/adapters/middleware.d.ts +8 -0
- package/dist/fastify/adapters/middleware.d.ts.map +1 -0
- package/dist/fastify/adapters/middleware.js +29 -0
- package/dist/fastify/adapters/middleware.js.map +1 -0
- package/dist/fastify/adapters/request.d.ts +19 -0
- package/dist/fastify/adapters/request.d.ts.map +1 -0
- package/dist/fastify/adapters/request.js +258 -0
- package/dist/fastify/adapters/request.js.map +1 -0
- package/dist/fastify/adapters/response.d.ts +19 -0
- package/dist/fastify/adapters/response.d.ts.map +1 -0
- package/dist/fastify/adapters/response.js +667 -0
- package/dist/fastify/adapters/response.js.map +1 -0
- package/dist/fastify/register.d.ts +12 -0
- package/dist/fastify/register.d.ts.map +1 -0
- package/dist/fastify/register.js +15 -0
- package/dist/fastify/register.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/decorators.d.ts +7 -0
- package/dist/runtime/decorators.d.ts.map +1 -0
- package/dist/runtime/decorators.js +17 -0
- package/dist/runtime/decorators.js.map +1 -0
- package/dist/runtime/errorHandler.d.ts +13 -0
- package/dist/runtime/errorHandler.d.ts.map +1 -0
- package/dist/runtime/errorHandler.js +95 -0
- package/dist/runtime/errorHandler.js.map +1 -0
- package/dist/runtime/expressLane.d.ts +40 -0
- package/dist/runtime/expressLane.d.ts.map +1 -0
- package/dist/runtime/expressLane.js +71 -0
- package/dist/runtime/expressLane.js.map +1 -0
- package/dist/runtime/fast.d.ts +43 -0
- package/dist/runtime/fast.d.ts.map +1 -0
- package/dist/runtime/fast.js +150 -0
- package/dist/runtime/fast.js.map +1 -0
- package/dist/runtime/lifecycle.d.ts +10 -0
- package/dist/runtime/lifecycle.d.ts.map +1 -0
- package/dist/runtime/lifecycle.js +152 -0
- package/dist/runtime/lifecycle.js.map +1 -0
- package/dist/runtime/populateExpress.d.ts +7 -0
- package/dist/runtime/populateExpress.d.ts.map +1 -0
- package/dist/runtime/populateExpress.js +27 -0
- package/dist/runtime/populateExpress.js.map +1 -0
- package/dist/types/express.d.ts +97 -0
- package/dist/types/express.d.ts.map +1 -0
- package/dist/types/express.js +12 -0
- package/dist/types/express.js.map +1 -0
- package/dist/types/internal.d.ts +60 -0
- package/dist/types/internal.d.ts.map +1 -0
- package/dist/types/internal.js +7 -0
- package/dist/types/internal.js.map +1 -0
- package/dist/utils/assert.d.ts +6 -0
- package/dist/utils/assert.d.ts.map +1 -0
- package/dist/utils/assert.js +17 -0
- package/dist/utils/assert.js.map +1 -0
- package/dist/utils/detect.d.ts +14 -0
- package/dist/utils/detect.d.ts.map +1 -0
- package/dist/utils/detect.js +64 -0
- package/dist/utils/detect.js.map +1 -0
- package/dist/utils/maxListeners.d.ts +28 -0
- package/dist/utils/maxListeners.d.ts.map +1 -0
- package/dist/utils/maxListeners.js +50 -0
- package/dist/utils/maxListeners.js.map +1 -0
- package/dist/utils/patchRouterLayer.d.ts +12 -0
- package/dist/utils/patchRouterLayer.d.ts.map +1 -0
- package/dist/utils/patchRouterLayer.js +96 -0
- package/dist/utils/patchRouterLayer.js.map +1 -0
- package/dist/utils/path.d.ts +6 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +23 -0
- package/dist/utils/path.js.map +1 -0
- package/dist/utils/runtimeLogger.d.ts +15 -0
- package/dist/utils/runtimeLogger.d.ts.map +1 -0
- package/dist/utils/runtimeLogger.js +28 -0
- package/dist/utils/runtimeLogger.js.map +1 -0
- package/dist/utils/unwrap.d.ts +12 -0
- package/dist/utils/unwrap.d.ts.map +1 -0
- package/dist/utils/unwrap.js +24 -0
- package/dist/utils/unwrap.js.map +1 -0
- package/package.json +94 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 express-fastify-runtime contributors
|
|
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,326 @@
|
|
|
1
|
+
# express-fastify-runtime
|
|
2
|
+
|
|
3
|
+
> Your Express app. Fastify's speed. One line. No rewrite.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { fast } from "express-fastify-runtime";
|
|
7
|
+
import express from "express";
|
|
8
|
+
|
|
9
|
+
const app = express();
|
|
10
|
+
app.get("/", (req, res) => res.json({ hello: "world" }));
|
|
11
|
+
|
|
12
|
+
fast(app).listen({ port: 3000 }); // 👈 that's the whole trick
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## A short, slightly emotional story
|
|
18
|
+
|
|
19
|
+
I love Express. I love it the way you love a comfortable pair of shoes — `req`, `res`,
|
|
20
|
+
`next`, a thousand middlewares on npm, and muscle memory built over years. Express is *home*.
|
|
21
|
+
|
|
22
|
+
Then one day someone showed me a Fastify benchmark and my coffee went cold. "Two-ish times the
|
|
23
|
+
throughput," they said, smiling like they'd discovered fire. And I thought: do I really have to
|
|
24
|
+
abandon my comfortable shoes and rewrite everything in a new framework just to go faster?
|
|
25
|
+
|
|
26
|
+
So I went looking for a shortcut. I found tools that "run Express on Fastify" — and many of them
|
|
27
|
+
do *exactly* one thing: they hand your Express app to Fastify and let Fastify… call Express for
|
|
28
|
+
every request. Your app technically runs "on Fastify." It is not one bit faster. It's a very
|
|
29
|
+
polite handshake between two frameworks where nothing actually changes. Cool sticker, no engine.
|
|
30
|
+
|
|
31
|
+
**`express-fastify-runtime` is the engine.** It doesn't just *expose* your Express app to
|
|
32
|
+
Fastify — it **compiles** your safe routes and middleware onto Fastify's real request pipeline,
|
|
33
|
+
and only falls back to actual Express for the things that genuinely need it (multipart uploads,
|
|
34
|
+
`res.render`, streaming bodies, anything unusual). You keep writing Express. It actually gets
|
|
35
|
+
fast.
|
|
36
|
+
|
|
37
|
+
You don't rewrite a thing. You wrap one line. Your shoes stay on.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## What you get
|
|
42
|
+
|
|
43
|
+
- **It's still Express.** `app.use`, `app.get`, `req`, `res`, `next`, your middleware, your
|
|
44
|
+
routers, your error handlers. Nothing to relearn. **Express 4 and 5 both welcome.**
|
|
45
|
+
- **It's actually fast.** Safe routes run compiled on Fastify — faster than plain Express across
|
|
46
|
+
the board, and matching or beating Fastify on middleware-heavy and small-payload workloads
|
|
47
|
+
(see [Benchmarks](#benchmarks)).
|
|
48
|
+
- **Nothing breaks.** Anything that can't safely run on Fastify is transparently handled by a
|
|
49
|
+
real embedded `express()` instance. No silent behavior changes; morgan logs, helmet headers,
|
|
50
|
+
auth, cookies, JSON parsing, streaming/SSE, and error middleware all behave like Express.
|
|
51
|
+
- **It's a real Fastify instance.** `fast(app)` returns a `FastifyInstance`, so Fastify fans get
|
|
52
|
+
their plugins, hooks, and the raw Node server for WebSockets/Socket.IO.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install express-fastify-runtime
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`express` is a **peer dependency** — bring your own (`^4.18` or `^5`). `fast()` uses whatever
|
|
63
|
+
Express you already have.
|
|
64
|
+
|
|
65
|
+
> **One rule:** import `express-fastify-runtime` **before** you create your Express app or any
|
|
66
|
+
> `express.Router()`. It patches the router layer at load time so middleware mounted with a path
|
|
67
|
+
> (`app.use('/api', mw)`, `router.use('/x', mw)`) can be compiled onto the Fastify lane. Import it
|
|
68
|
+
> late and those bits still work — they just fall back to the (slower) Express lane.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## For Express fans — wrap what you already have
|
|
73
|
+
|
|
74
|
+
You wrote a normal Express app. Wrap it. Done.
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import "express-fastify-runtime"; // load first (the one rule)
|
|
78
|
+
import express from "express";
|
|
79
|
+
import morgan from "morgan";
|
|
80
|
+
import helmet from "helmet";
|
|
81
|
+
import { fast } from "express-fastify-runtime";
|
|
82
|
+
|
|
83
|
+
const app = express();
|
|
84
|
+
app.use(morgan("tiny")); // logs, correctly, per request
|
|
85
|
+
app.use(helmet()); // security headers, intact
|
|
86
|
+
app.use(express.json()); // parsed by Fastify's fast parser under the hood
|
|
87
|
+
|
|
88
|
+
app.get("/users/:id", (req, res) => {
|
|
89
|
+
res.json({ id: req.params.id });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
app.use((err, req, res, next) => { // your error middleware still works
|
|
93
|
+
res.status(500).json({ error: err.message });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const fastApp = fast(app); // returns a Fastify instance
|
|
97
|
+
fastApp.listen({ port: 3000 });
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Routers, controllers, `req.body`, `req.query`, `req.params`, `res.status().json()`,
|
|
101
|
+
`res.redirect()`, `res.cookie()`, async handlers, `next(err)` — all the Express you already
|
|
102
|
+
write. No changes.
|
|
103
|
+
|
|
104
|
+
## For Fastify fans — it's a real Fastify instance
|
|
105
|
+
|
|
106
|
+
`fast(app)` hands you back a genuine `FastifyInstance`. Add plugins, register hooks, use the
|
|
107
|
+
Fastify ecosystem — your Express routes just ride along on the Fastify lane.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import "express-fastify-runtime";
|
|
111
|
+
import express from "express";
|
|
112
|
+
import rateLimit from "@fastify/rate-limit";
|
|
113
|
+
import { fast } from "express-fastify-runtime";
|
|
114
|
+
|
|
115
|
+
const app = express();
|
|
116
|
+
app.get("/", (req, res) => res.json({ ok: true }));
|
|
117
|
+
|
|
118
|
+
const fastApp = fast(app, { fastify: { logger: true } }); // pass Fastify options through
|
|
119
|
+
await fastApp.register(rateLimit, { max: 100 }); // real Fastify plugins
|
|
120
|
+
await fastApp.ready(); // let plugins load before listen
|
|
121
|
+
await fastApp.listen({ port: 3000 });
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Plain Node `http`
|
|
125
|
+
|
|
126
|
+
`fastApp.server` is the real Node HTTP server, and `server.listen(...)` is wired to run
|
|
127
|
+
Fastify's full lifecycle (so 404s and internals work):
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const fastApp = fast(app);
|
|
131
|
+
const server = fastApp.server; // http.Server
|
|
132
|
+
server.listen(3000, () => console.log("up on 3000"));
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Socket.IO / WebSockets
|
|
136
|
+
|
|
137
|
+
Because you can reach the underlying HTTP server, real-time works exactly like it does in any
|
|
138
|
+
Node app — attach your socket server to `fastApp.server`:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { Server as IOServer } from "socket.io";
|
|
142
|
+
|
|
143
|
+
const fastApp = fast(app);
|
|
144
|
+
const io = new IOServer(fastApp.server); // share the same HTTP server
|
|
145
|
+
io.on("connection", (socket) => socket.emit("hello", "from express-fastify-runtime"));
|
|
146
|
+
|
|
147
|
+
fastApp.server.listen(3000);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The same pattern works for `ws`, `@fastify/websocket`, or anything that takes an
|
|
151
|
+
`http.Server`.
|
|
152
|
+
|
|
153
|
+
## Controlling which lane a route runs on
|
|
154
|
+
|
|
155
|
+
Every request runs on one of two lanes ([how it works](#how-it-works-30-seconds)):
|
|
156
|
+
|
|
157
|
+
- **Fastify lane (default, fast)** — safe routes and middleware are compiled onto Fastify
|
|
158
|
+
automatically. You don't annotate anything for the common case.
|
|
159
|
+
- **Express lane (real Express)** — anything unsafe (multipart/uploads, `res.render`, `res.sendFile`,
|
|
160
|
+
stream-piping middleware) is detected and transparently handled by the embedded real Express app.
|
|
161
|
+
|
|
162
|
+
When detection can't be sure, it already errs toward the Express lane. The two helpers below let you
|
|
163
|
+
**force the Express lane** for a specific route when you want Express-only behavior guaranteed
|
|
164
|
+
(e.g. a view engine) without relying on detection.
|
|
165
|
+
|
|
166
|
+
### `expressLane(fn)` — force the Express lane (works anywhere)
|
|
167
|
+
|
|
168
|
+
Wrap the handler/middleware. Works with plain functions and arrow functions:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { expressLane } from "express-fastify-runtime";
|
|
172
|
+
|
|
173
|
+
app.get("/page", expressLane((req, res) => res.render("index", { title: "Hi" })));
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### `@ExpressLane` — decorator form (class-method controllers)
|
|
177
|
+
|
|
178
|
+
Requires `"experimentalDecorators": true` in your `tsconfig.json`. The Express-lane marker lives on
|
|
179
|
+
the decorated method itself — register that method **directly** as the handler; don't `.bind()` it
|
|
180
|
+
or wrap it in an arrow first (that creates a new function and drops the marker — use `expressLane()`
|
|
181
|
+
for those cases).
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { ExpressLane } from "express-fastify-runtime";
|
|
185
|
+
|
|
186
|
+
class PageController {
|
|
187
|
+
@ExpressLane
|
|
188
|
+
page(req, res) {
|
|
189
|
+
res.render("index", { title: "Hi" });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const pages = new PageController();
|
|
194
|
+
app.get("/page", pages.page); // the decorated method carries the Express-lane marker
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Everything else stays on the fast lane.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## How it works (30 seconds)
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
your Express app (unchanged)
|
|
205
|
+
│ compiled once, at startup
|
|
206
|
+
┌─────────────┴──────────────┐
|
|
207
|
+
▼ ▼
|
|
208
|
+
Fastify lane Embedded Express lane
|
|
209
|
+
(safe routes & middleware (real express() instance —
|
|
210
|
+
compiled onto Fastify) uploads, res.render, streams…)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
A request is matched by Fastify. If it's a compiled (safe) route, it runs on Fastify's pipeline
|
|
214
|
+
with a thin Express-compatible `req`/`res`. If it isn't, Fastify hands the raw request to the
|
|
215
|
+
embedded real Express app. **When in doubt, it uses real Express** — it never guesses and
|
|
216
|
+
silently changes behavior.
|
|
217
|
+
|
|
218
|
+
Lane classification, body parsing (`express.json()` → Fastify's parser), router flattening, and
|
|
219
|
+
error-handler wiring all happen **once at startup**. There's no per-request framework juggling.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Benchmarks
|
|
224
|
+
|
|
225
|
+
Numbers are req/s, **median of repeated runs with warmup** (single-shot HTTP benchmarks swing
|
|
226
|
+
20–30%, so don't trust one sample — including ours; reproduce with `npm run benchmark:table`).
|
|
227
|
+
10 connections, Node on an Apple-silicon laptop. Higher is better.
|
|
228
|
+
|
|
229
|
+
| Scenario | Express | Fastify | **fast()** | fast/Express | fast/Fastify |
|
|
230
|
+
|---|--:|--:|--:|--:|--:|
|
|
231
|
+
| Plain JSON route (5 middleware) | 39,976 | 68,112 | **50,552** | **1.26×** | 0.74× |
|
|
232
|
+
| JSON DB read | 41,848 | 68,624 | **50,584** | **1.21×** | 0.74× |
|
|
233
|
+
| Middleware stack (helmet+morgan+json+custom) | 29,424 | 32,736 | **40,184** | **1.37×** | **1.23×** |
|
|
234
|
+
| POST 1KB JSON | 33,018 | 46,752 | **46,696** | **1.41×** | **1.00×** |
|
|
235
|
+
| Auth (JWT verify) | 17,636 | 33,976 | **19,360** | **1.10×** | 0.57× |
|
|
236
|
+
|
|
237
|
+
**Reading the table:**
|
|
238
|
+
|
|
239
|
+
- `fast()` is **faster than plain Express** across the board — same code, more throughput.
|
|
240
|
+
- It **matches or beats Fastify** on middleware-heavy and small-payload workloads.
|
|
241
|
+
- On a tight pure-JSON hot path it trails raw Fastify a little — that's the unavoidable cost of
|
|
242
|
+
presenting a real Express `req`/`res`, and it's still well ahead of Express.
|
|
243
|
+
- **Auth (JWT):** the gap there isn't the adapter — it's the *library*. Fastify's benchmark uses
|
|
244
|
+
`@fastify/jwt` (built on `fast-jwt`), which is simply faster than `jsonwebtoken`. Want that
|
|
245
|
+
speed in your Express app? Use `fast-jwt` directly in your auth middleware — it's
|
|
246
|
+
framework-agnostic.
|
|
247
|
+
- **Uploads** (multipart) run on the Express lane by design (multer), so Fastify's native
|
|
248
|
+
`@fastify/multipart` wins there — expected, not a regression.
|
|
249
|
+
|
|
250
|
+
Run them yourself:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
npm run benchmark:table # the table above (warmup + median)
|
|
254
|
+
npm run benchmark:fast-vs-fastify # fast() vs plain Fastify, MW=0 and MW=5
|
|
255
|
+
npm run benchmark # full suite: express / fastify / node-http / runtime
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Compatibility & guarantees
|
|
261
|
+
|
|
262
|
+
- **Express 4 → 5 upward.** Legacy apps and modern apps. The goal is that adding `fast()`
|
|
263
|
+
changes your throughput, not your behavior.
|
|
264
|
+
- **Concurrency-safe.** Every in-flight request gets its own `req`/`res` — heavily tested under
|
|
265
|
+
concurrent async load (no cross-talk, correct morgan logs per request).
|
|
266
|
+
- **Supported:** `app.use` / `app.METHOD` / `app.all`, `express.Router()` (flattened when safe),
|
|
267
|
+
`req.body|query|params|headers|cookies`, `res.status|json|send|sendStatus|set|get|redirect|cookie|type|end`,
|
|
268
|
+
streaming via `res.write`/`res.end`, async handlers, global `(err, req, res, next)` error
|
|
269
|
+
middleware, `express.json()`/`urlencoded`.
|
|
270
|
+
- **Express lane (real Express):** multipart/uploads, `res.render` + view engines,
|
|
271
|
+
`res.sendFile`, stream-based middleware, RegExp route paths — or anything you mark with
|
|
272
|
+
`expressLane()`.
|
|
273
|
+
|
|
274
|
+
**Middleware semantics:** the chain is continuation-based, exactly like Express's router —
|
|
275
|
+
`next()` advances the chain however it's called: synchronously, from an `async/await` handler,
|
|
276
|
+
or from a detached callback / `setTimeout` / promise (classic callback-style middleware). A
|
|
277
|
+
middleware that ends the response without calling `next()` stops the chain. The only thing that
|
|
278
|
+
"hangs" is a handler that never calls `next()` and never responds — which hangs in real Express
|
|
279
|
+
too.
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Contributing
|
|
284
|
+
|
|
285
|
+
PRs welcome — the bar is "it stays Express-correct **and** fast."
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
npm install
|
|
289
|
+
npm run build # tsc → dist (CommonJS)
|
|
290
|
+
npm test # unit + integration (incl. concurrency / parity / error-handling)
|
|
291
|
+
npm run benchmark:table
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Rules of the road:
|
|
295
|
+
|
|
296
|
+
1. **Correctness beats speed, always.** Adding `fast()` must never change Express behavior. If a
|
|
297
|
+
perf idea risks that, it doesn't ship.
|
|
298
|
+
2. **No shared per-request state.** Each request gets its own `req`/`res`. Never reintroduce a
|
|
299
|
+
single mutated adapter object — it corrupts concurrent async requests. Validate every
|
|
300
|
+
perf change with concurrent **async** handlers (`test/integration/concurrency.test.js`).
|
|
301
|
+
3. **Imports are CommonJS, no file extensions** (matches the current `tsconfig`).
|
|
302
|
+
4. **Add a test** for any behavior you touch; keep the full suite green (`npm test`).
|
|
303
|
+
5. **Measure with `npm run benchmark:table`** (warmup + median), not single-shot runs.
|
|
304
|
+
6. Update `docs/` and `changelog/log-YYYY-MM-DD.md` for notable changes.
|
|
305
|
+
|
|
306
|
+
See `docs/` for deeper notes: `SPEC.md`, `HOW_EXPRESS_LANE_WORKS.md`,
|
|
307
|
+
`FAST_PRODUCTION_CHECKLIST.md`, `OPTIMIZATION.md`, `EXPRESS_FEATURES.md`.
|
|
308
|
+
|
|
309
|
+
## Reporting issues
|
|
310
|
+
|
|
311
|
+
Found a bug or have a feature request? Open an issue on
|
|
312
|
+
**[GitHub Issues](https://github.com/John-Daniels/express-fastify-runtime/issues)**.
|
|
313
|
+
|
|
314
|
+
To help us reproduce quickly, please include:
|
|
315
|
+
|
|
316
|
+
- **Versions** — `express-fastify-runtime`, `express` (4 or 5), `fastify`, and Node.
|
|
317
|
+
- **Lane** — run `fast(app, { experimental: { diagnostics: true } })` and note whether the failing
|
|
318
|
+
request logs `Fastify lane` or `Express lane`.
|
|
319
|
+
- **A minimal repro** — the smallest app/route that triggers it, plus the full error stack (not just
|
|
320
|
+
the message) and the request (method, path, content-type, body).
|
|
321
|
+
|
|
322
|
+
Security issue? Please report it privately to the maintainer rather than opening a public issue.
|
|
323
|
+
|
|
324
|
+
## License
|
|
325
|
+
|
|
326
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# 2025-02-07
|
|
2
|
+
|
|
3
|
+
- Dev fallback logger: warn when Router falls back to Express lane (middleware/RegExp path). `createApp({ dev: true })` or `NODE_ENV !== 'production'`.
|
|
4
|
+
- Changelog folder: `changelog/log-YYYY-MM-DD.md` per day.
|
|
5
|
+
- NPM publish prep: repository, license, keywords, files in package.json.
|
|
6
|
+
- compile.ts: kept current version (nextCalled for error/flow clarity); removed compile.old.ts.
|
|
7
|
+
- EXPRESS_FEATURES.md: added Fallback behavior section, createApp(options), clarified supported vs not yet.
|
|
8
|
+
- docs/EDGE_CASES.md: added edge-case checklist (Tier 1–3, production guarantees, benchmark ideas).
|
|
9
|
+
- **fast(app, ops):** New API to compile an existing Express app onto Fastify. Introspects `app.router.stack`, classifies and registers safe routes on Fastify, mounts Express for fallback. Returns real Fastify instance. Requires loading express-fastify-runtime before creating the app (for Layer _path). See `src/runtime/fast.ts`, `src/app/introspectExpress.ts`.
|
|
10
|
+
- **Benchmark notes:** Documented in benchmarks/README.md why Fastify wins on uploads (multer vs native multipart) and auth (native JWT plugin vs userland); payloads and CRUD are within target.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# 2026-06-25
|
|
2
|
+
|
|
3
|
+
## Publish prep
|
|
4
|
+
|
|
5
|
+
- **Fastify upgraded to 5.8.5** (latest; includes the recent security/CVE fixes). Dependency floor
|
|
6
|
+
raised to `^5.8.5`. We use only stable Fastify-5 APIs and the one 5.x behavior change (stricter
|
|
7
|
+
RFC-9110 content-type parsing, 5.7.2) predates our prior 5.7.4 — full test suite stays green on
|
|
8
|
+
**Express 4 and 5** after the bump.
|
|
9
|
+
- **Single self-discovering test command**: `npm test` now runs `node --test test/*/*.test.js`
|
|
10
|
+
(shell-expanded; works on every Node version) instead of a hand-maintained file list; added
|
|
11
|
+
`test:compat`. New `test/<category>/*.test.js` files are picked up automatically.
|
|
12
|
+
- **Packaging**: added `publishConfig.access = "public"`; verified `npm pack` (64 kB, dist + README
|
|
13
|
+
+ LICENSE + changelog) consumes cleanly from CJS (`require`) and ESM (`import`), Express not
|
|
14
|
+
bundled (peer dep). `npm publish` is run by the maintainer (`npm login` required).
|
|
15
|
+
|
|
16
|
+
## Fix: APM-instrumented apps (Sentry/OpenTelemetry), string ports, wrapped body parsers
|
|
17
|
+
|
|
18
|
+
Found while running a real Express 4 app (Sentry + helmet + morgan + hpp) through `fast()`:
|
|
19
|
+
|
|
20
|
+
- **`server.listen(process.env.PORT, cb)` bound a random port.** `process.env.PORT` is a string;
|
|
21
|
+
our wrapper coerced non-numbers to 0 (OS-assigned), so the server logged the intended port but
|
|
22
|
+
was unreachable. `runtime/fast.ts` now coerces string ports (and `{ port: "3000" }`) like
|
|
23
|
+
Node/Express.
|
|
24
|
+
- **Helmet crashed with "Cannot set properties of undefined (setting 'content-security-policy')".**
|
|
25
|
+
Sentry's OpenTelemetry instrumentation wraps Express handlers, so Express 4's `expressInit` (which
|
|
26
|
+
reassigns req/res prototypes) had `handle.name === "patched"` and slipped past our skip — it ran
|
|
27
|
+
on the Fastify lane and broke the response object. We now skip `query`/`expressInit` by
|
|
28
|
+
**`layer.name`** (Express preserves it through APM wrapping), in `app/flattenRouter.ts`.
|
|
29
|
+
- **`express.json()` crashed with "argument stream must be a stream" on POST routes.** The wrapped
|
|
30
|
+
`jsonParser` wasn't recognized (so it wasn't mapped to Fastify's parser) and tried to read the
|
|
31
|
+
already-consumed request stream. We now identify `express.json` by `layer.name` and replace it
|
|
32
|
+
with a no-op on the Fastify lane (Fastify provides `req.body`); `utils/detect.ts` and
|
|
33
|
+
`express/middleware.ts` also unwrap APM wrappers (`__original`) as a secondary safety. New
|
|
34
|
+
`test/integration/instrumentation.test.js` simulates the wrapping.
|
|
35
|
+
- `express.urlencoded()`/`raw()`/`text()` continue to route to the Express lane (Fastify has no
|
|
36
|
+
parser for them); `express.json()` stays fast on Fastify. Verified end-to-end against the real
|
|
37
|
+
Express 4 app: `GET /`, `POST /v1/user/login` (JSON), and a duplicate urlencoded webhook route
|
|
38
|
+
all work, no crashes.
|
|
39
|
+
|
|
40
|
+
## Fix: repeated route registrations + stream body parsers
|
|
41
|
+
|
|
42
|
+
- **"Method 'POST' already declared" crash fixed.** Declaring the same method+path more than once
|
|
43
|
+
is valid Express (`router.post('/x', parser); router.post('/x', handler)` — the layers chain via
|
|
44
|
+
`next()`), but Fastify forbids duplicate method+path, so we crashed at startup. `compile.ts` now
|
|
45
|
+
counts route entries per concrete method+path (across both lanes, expanding `all`) and registers
|
|
46
|
+
a method on the Fastify lane only when it's declared exactly once; any method+path declared >1
|
|
47
|
+
time (incl. `all`+method overlaps, or a same-path Express-lane entry) defers entirely to the real
|
|
48
|
+
embedded Express app, which runs every layer in order. Single-route hot path unchanged.
|
|
49
|
+
- **Stream body parsers routed to the Express lane.** `detect.ts` now treats `express.urlencoded`
|
|
50
|
+
(`urlencodedParser`), `express.raw` (`rawParser`), and `express.text` (`textParser`) as
|
|
51
|
+
Express-required — they consume the raw request stream, which Fastify has already drained and has
|
|
52
|
+
no parser for, so `req.body` would be empty on the Fastify lane. (`express.json` is unchanged: it
|
|
53
|
+
stays mapped to Fastify's native JSON parser.) Fixes both the duplicate case and a single inline
|
|
54
|
+
`app.post('/x', express.urlencoded(...), handler)`.
|
|
55
|
+
- New `test/integration/duplicate-routes.test.js` (incl. the exact reported WhatsApp pattern);
|
|
56
|
+
verified against a real Express 4 consumer: no crash, 200, body parsed.
|
|
57
|
+
|
|
58
|
+
## Production-readiness pass (npm-ready, Express 4→5, freaking fast)
|
|
59
|
+
|
|
60
|
+
- **`fast()` no longer couples its types to a specific `@types/express`.** Its parameter is now a
|
|
61
|
+
structural `ExpressApp` type (a request-handler function with Express's methods), exported from
|
|
62
|
+
the package, instead of `Application` from `@types/express`. A host project on Express 4 (or any
|
|
63
|
+
@types/express version that differs from the one this package was built with) previously got
|
|
64
|
+
`TS2345: 'Express' is not assignable to 'Application'` (PathParams/RegExp vs string). Verified:
|
|
65
|
+
`fast(express())` type-checks under a real Express-4 (`@types/express@4`) consumer.
|
|
66
|
+
|
|
67
|
+
- **Express 4 support.** `fast()` previously crashed on Express 4 (its `app.router` getter
|
|
68
|
+
throws; the router is `app._router`) and Express 4 injects `query`/`expressInit` middleware
|
|
69
|
+
into the app router stack — `expressInit` reassigns `req`/`res` prototypes and broke our
|
|
70
|
+
adapter. Fixes: `introspectExpress.ts` reads `app._router` first (guarded), `patchRouterLayer.ts`
|
|
71
|
+
also patches Express 4's bundled `express/lib/router/layer`, and `flattenRouter.ts` skips the
|
|
72
|
+
`query`/`expressInit` built-ins. **The full test suite passes under Express 4.22 and Express 5.2.**
|
|
73
|
+
- **`express` is now a peerDependency** (`^4.18 || ^5`) instead of a hard dependency — consumers
|
|
74
|
+
bring their own Express; nothing is bundled/forced.
|
|
75
|
+
- **Error handling parity.** Un-skipped the Fastify-lane error test; `next(err)`/throw/async
|
|
76
|
+
rejection reach Express 4-arg error middleware on both `fast()` and `createApp()`
|
|
77
|
+
(`createApp` now wires `setErrorHandler`). 4-arg error middleware is excluded from the normal
|
|
78
|
+
chain. New `test/integration/error-handling.test.js`.
|
|
79
|
+
- **Streaming / SSE.** Added `res.write`/`res.writeHead`/`res.flushHeaders` (and hijack-aware
|
|
80
|
+
`res.end`) to the response adapter via `reply.hijack()`, so streaming and Server-Sent Events
|
|
81
|
+
work on the Fastify lane. Covered (incl. under concurrency) in new
|
|
82
|
+
`test/integration/parity.test.js` alongside redirect, send variants, 204, 404 fall-through,
|
|
83
|
+
and 1MB echo.
|
|
84
|
+
- **npm packaging:** added an `exports` map (types/require/import); verified `npm pack` consumes
|
|
85
|
+
cleanly from both CJS (`require`) and ESM (`import`) apps, with Express not bundled.
|
|
86
|
+
- **Benchmarks:** new `benchmark:table` (warmup + interleaved median, Markdown output). Median
|
|
87
|
+
numbers: fast() is ~1.2–1.5× plain Express across the board and ~1.1–1.3× Fastify on
|
|
88
|
+
middleware-heavy / small-payload workloads; ~0.7× Fastify on a pure-JSON hot path (the cost of
|
|
89
|
+
a real Express req/res). README rewritten (story-driven) with the table and Socket.IO/http/
|
|
90
|
+
Fastify-plugin examples.
|
|
91
|
+
- **Continuation-based middleware chain (correctness, kept fast).** Replaced the
|
|
92
|
+
"advance synchronously or await the returned promise" runner with a continuation runner that
|
|
93
|
+
matches Express's router: `next()` advances the chain however it's called — sync, `async/await`,
|
|
94
|
+
or a **detached callback / setTimeout / microtask** (classic callback-style middleware, e.g.
|
|
95
|
+
`db.query(..., () => next())`). Previously such middleware silently broke the chain on the fast
|
|
96
|
+
lane. A handler that ends the response without `next()` stops the chain; errors (sync throw,
|
|
97
|
+
`next(err)`, async rejection) reach Fastify's error handler. The runner is **sync-first** — it
|
|
98
|
+
returns synchronously (no Promise) when the chain settles synchronously, and the single
|
|
99
|
+
no-`next` handler hot path bypasses it entirely — so the benchmarks held (fast() stays
|
|
100
|
+
~1.1–1.4× Express on every scenario and ≥1.0× Fastify on middleware-stack and payloads). New
|
|
101
|
+
`test/integration/middleware-chain.test.js`. This removes the previously-documented
|
|
102
|
+
"detached next()" limitation.
|
|
103
|
+
- **CI:** GitHub Actions matrix (Node 18/20/22 × Express 4/5) + benchmark smoke.
|
|
104
|
+
- **Deferred (deliberately):** decorating Fastify's own `request`/`reply` for pure-JSON parity —
|
|
105
|
+
it would override Fastify's reply API (`send`/`type`/`header`/`status`/`code`/`getHeader`) with
|
|
106
|
+
Express semantics and break Fastify internals. Correctness ("nothing breaks") outranks that
|
|
107
|
+
win; the safe separate-`res` design stays. Tracked in `docs/PLAN_FASTIFY_CLOSER.md`.
|
|
108
|
+
|
|
109
|
+
## Correctness (release blocker fixed)
|
|
110
|
+
|
|
111
|
+
- **Per-request state isolation in the Fastify lane.** The previous adapter reused ONE `req`
|
|
112
|
+
and ONE `res` object app-wide (`createRequestAdapter`/`createResponseAdapter`). That only
|
|
113
|
+
held for synchronous handlers; under concurrent **async** handlers (or morgan's deferred
|
|
114
|
+
finish listener) later requests overwrote earlier ones. Repro: 29/30 concurrent async
|
|
115
|
+
`GET /echo/:id` returned empty/scrambled bodies. Fixed with a shared method prototype + a
|
|
116
|
+
small per-request instance (`Object.create`). `src/fastify/adapters/{request,response}.ts`.
|
|
117
|
+
- **Morgan logs now correct under concurrency.** Lifecycle events (finish/end/close) are
|
|
118
|
+
delegated to the real `reply.raw` response, so morgan/on-finished fire on the actual
|
|
119
|
+
response finish (status, headers, `res._startAt` all set). Removed the synthetic
|
|
120
|
+
double-`setImmediate` finish emit that raced under load and produced "- - ms - -".
|
|
121
|
+
- New regression tests: `test/integration/concurrency.test.js` (async echo, distinct
|
|
122
|
+
status/headers/body, res.locals isolation, morgan under concurrent load) and
|
|
123
|
+
`test/integration/middleware-parity.test.js` (auth `req.user` isolation, helmet,
|
|
124
|
+
express.json, cookies). Wired into `npm test`.
|
|
125
|
+
|
|
126
|
+
## Performance (Fastify lane)
|
|
127
|
+
|
|
128
|
+
- compile.ts: one `next()` per chain (not per handler); `baseUrl` precomputed at registration;
|
|
129
|
+
zero-middleware routes folded into a single Fastify handler (no preHandler stage); handler
|
|
130
|
+
returns the user handler's promise directly (no extra async frame). `res.locals` is lazy.
|
|
131
|
+
- Removed the per-request `reply.raw.writeHead` morgan patch; `res._startAt` is recorded in
|
|
132
|
+
`res.json/send` instead.
|
|
133
|
+
- `benchmarks/fast-vs-fastify/run.js`: warmup + interleaved median over N rounds (env `ROUNDS`,
|
|
134
|
+
`WARMUP`) for trustworthy numbers.
|
|
135
|
+
- Baseline after fixes: fast() ≈ 0.72× Fastify (MW=0 and MW=5), stable; fast() **beats** plain
|
|
136
|
+
Express on most scenarios and **beats** Fastify on middleware-stack. CPU profile shows ~84%
|
|
137
|
+
of time in Node's C++ HTTP layer and our own JS <1% — remaining gap is per-request Express
|
|
138
|
+
req/res allocation over Fastify. Auth gap vs Fastify is the JWT library (jsonwebtoken vs
|
|
139
|
+
@fastify/jwt), not the adapter (fast() ≈ Express there).
|
|
140
|
+
|
|
141
|
+
## Next step (documented, not yet done)
|
|
142
|
+
|
|
143
|
+
- Path to ~parity: decorate Fastify's own `request`/`reply` (`decorateRequest`/`decorateReply`)
|
|
144
|
+
so no extra per-request objects are allocated. See `docs/PLAN_FASTIFY_CLOSER.md` status note.
|
|
145
|
+
|
|
146
|
+
## Known limitation — RESOLVED
|
|
147
|
+
|
|
148
|
+
- The previously-noted "detached `next()` skips subsequent middleware" limitation is **fixed** by
|
|
149
|
+
the continuation-based middleware chain (see above). All middleware styles now behave as in
|
|
150
|
+
Express. The only thing that hangs is a handler that never calls `next()` and never responds —
|
|
151
|
+
which hangs in real Express too.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Express-like app stub: app.use, app.get, app.listen.
|
|
3
|
+
* Routes are immutable after listen(). Full Router handling is in lifecycle.ts.
|
|
4
|
+
*/
|
|
5
|
+
import type { ExpressLikeApp as IExpressLikeApp } from '../types/internal';
|
|
6
|
+
export declare function createExpressLikeApp(routeStore: import('./RouteStore').RouteStore, locked: {
|
|
7
|
+
current: boolean;
|
|
8
|
+
}): IExpressLikeApp;
|
|
9
|
+
//# sourceMappingURL=ExpressLikeApp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpressLikeApp.d.ts","sourceRoot":"","sources":["../../src/app/ExpressLikeApp.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,cAAc,IAAI,eAAe,EAA0B,MAAM,mBAAmB,CAAC;AAGnG,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,OAAO,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,mBAqE/G"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Express-like app stub: app.use, app.get, app.listen.
|
|
4
|
+
* Routes are immutable after listen(). Full Router handling is in lifecycle.ts.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.createExpressLikeApp = createExpressLikeApp;
|
|
8
|
+
const assert_1 = require("../utils/assert");
|
|
9
|
+
function createExpressLikeApp(routeStore, locked) {
|
|
10
|
+
const app = {
|
|
11
|
+
use(pathOrHandler, ...handlers) {
|
|
12
|
+
(0, assert_1.assertNotLocked)(locked.current);
|
|
13
|
+
const path = typeof pathOrHandler === 'string' ? pathOrHandler : '/';
|
|
14
|
+
const allHandlers = typeof pathOrHandler === 'string' ? handlers : [pathOrHandler, ...handlers];
|
|
15
|
+
routeStore.addMiddleware(path, ...allHandlers);
|
|
16
|
+
return app;
|
|
17
|
+
},
|
|
18
|
+
get(path, ...handlers) {
|
|
19
|
+
(0, assert_1.assertNotLocked)(locked.current);
|
|
20
|
+
routeStore.addRoute('get', path, ...handlers);
|
|
21
|
+
return app;
|
|
22
|
+
},
|
|
23
|
+
post(path, ...handlers) {
|
|
24
|
+
(0, assert_1.assertNotLocked)(locked.current);
|
|
25
|
+
routeStore.addRoute('post', path, ...handlers);
|
|
26
|
+
return app;
|
|
27
|
+
},
|
|
28
|
+
put(path, ...handlers) {
|
|
29
|
+
(0, assert_1.assertNotLocked)(locked.current);
|
|
30
|
+
routeStore.addRoute('put', path, ...handlers);
|
|
31
|
+
return app;
|
|
32
|
+
},
|
|
33
|
+
patch(path, ...handlers) {
|
|
34
|
+
(0, assert_1.assertNotLocked)(locked.current);
|
|
35
|
+
routeStore.addRoute('patch', path, ...handlers);
|
|
36
|
+
return app;
|
|
37
|
+
},
|
|
38
|
+
delete(path, ...handlers) {
|
|
39
|
+
(0, assert_1.assertNotLocked)(locked.current);
|
|
40
|
+
routeStore.addRoute('delete', path, ...handlers);
|
|
41
|
+
return app;
|
|
42
|
+
},
|
|
43
|
+
head(path, ...handlers) {
|
|
44
|
+
(0, assert_1.assertNotLocked)(locked.current);
|
|
45
|
+
routeStore.addRoute('head', path, ...handlers);
|
|
46
|
+
return app;
|
|
47
|
+
},
|
|
48
|
+
options(path, ...handlers) {
|
|
49
|
+
(0, assert_1.assertNotLocked)(locked.current);
|
|
50
|
+
routeStore.addRoute('options', path, ...handlers);
|
|
51
|
+
return app;
|
|
52
|
+
},
|
|
53
|
+
all(path, ...handlers) {
|
|
54
|
+
(0, assert_1.assertNotLocked)(locked.current);
|
|
55
|
+
routeStore.addRoute('all', path, ...handlers);
|
|
56
|
+
return app;
|
|
57
|
+
},
|
|
58
|
+
listen(_port, _host, _callback) {
|
|
59
|
+
throw new Error('Use createApp() from the main entry; listen() is implemented there.');
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
return app;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=ExpressLikeApp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpressLikeApp.js","sourceRoot":"","sources":["../../src/app/ExpressLikeApp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stores routes and middleware before compile. Immutable after listen().
|
|
3
|
+
*/
|
|
4
|
+
import type { RouteEntry } from '../types/internal';
|
|
5
|
+
import type { ExpressHandler } from '../types/express';
|
|
6
|
+
export declare class RouteStore {
|
|
7
|
+
private entries;
|
|
8
|
+
addMiddleware(path: string, ...handlers: ExpressHandler[]): void;
|
|
9
|
+
addRoute(method: string, path: string, ...handlers: ExpressHandler[]): void;
|
|
10
|
+
/** Add a single entry (used when flattening Router). */
|
|
11
|
+
addEntry(entry: RouteEntry): void;
|
|
12
|
+
/** Add multiple entries (used when flattening Router). */
|
|
13
|
+
addEntries(entries: readonly RouteEntry[]): void;
|
|
14
|
+
getAll(): readonly RouteEntry[];
|
|
15
|
+
clear(): void;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=RouteStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RouteStore.d.ts","sourceRoot":"","sources":["../../src/app/RouteStore.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGvD,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAoB;IAEnC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,cAAc,EAAE,GAAG,IAAI;IAIhE,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,cAAc,EAAE,GAAG,IAAI;IAQ3E,wDAAwD;IACxD,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAQjC,0DAA0D;IAC1D,UAAU,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,GAAG,IAAI;IAMhD,MAAM,IAAI,SAAS,UAAU,EAAE;IAI/B,KAAK,IAAI,IAAI;CAGd"}
|