owebjs 1.5.4-dev → 1.5.7-dev
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/0http.js +8 -0
- package/README.md +434 -270
- package/benchmark.txt +191 -0
- package/dist/index.d.ts +3 -0
- package/dist/structures/Oweb.js +52 -7
- package/dist/utils/assignRoutes.js +298 -173
- package/dist/utils/walk.js +5 -6
- package/dist/utils/watcher.js +21 -9
- package/dist/uwebsocket/request.js +59 -26
- package/dist/uwebsocket/response.js +21 -10
- package/dist/uwebsocket/responseSocket.js +5 -1
- package/dist/uwebsocket/server.js +81 -51
- package/express.js +14 -0
- package/fasti.js +14 -0
- package/package.json +6 -4
- package/purehttp.js +19 -0
- package/uws.js +16 -0
package/README.md
CHANGED
|
@@ -1,270 +1,434 @@
|
|
|
1
|
-
# Oweb
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- **
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
import
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
1
|
+
# Oweb
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>A high-performance file-based web framework with seamless Fastify compatibility and native real-time capabilities</strong><br/>
|
|
5
|
+
Build APIs and real-time endpoints with a clean folder structure and fast iteration.
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<a href="https://www.npmjs.com/package/owebjs"><img src="https://img.shields.io/npm/v/owebjs" alt="npm version"></a>
|
|
10
|
+
<a href="https://www.npmjs.com/package/owebjs"><img src="https://img.shields.io/npm/dm/owebjs" alt="npm downloads"></a>
|
|
11
|
+
<a href="https://github.com/owebjs/oweb/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/owebjs" alt="license"></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
## What Is Oweb?
|
|
15
|
+
|
|
16
|
+
Oweb is a route-per-file framework built on Fastify, with an optional **uWebSockets runtime mode** for high-throughput workloads.
|
|
17
|
+
|
|
18
|
+
You keep Fastify compatibility and plugin ergonomics, while getting a cleaner architecture, built-in HMR, and first-class SSE/WebSocket support.
|
|
19
|
+
|
|
20
|
+
## Feature Overview
|
|
21
|
+
|
|
22
|
+
- **Dual runtime**: Fastify-compatible default runtime + optional `uWebSockets.js` runtime
|
|
23
|
+
- File-based routing
|
|
24
|
+
- Dynamic params (`[id]`), method files (`.post`, `.put`, ...), and matcher params (`[id=integer]`)
|
|
25
|
+
- Hierarchical hooks via `_hooks.js` / `_hooks.ts`
|
|
26
|
+
- Route-level and global error handling
|
|
27
|
+
- Built-in HMR for routes and matchers
|
|
28
|
+
- SSE via iterable / async-iterable route handlers
|
|
29
|
+
- WebSocket routes with `WebSocketRoute`
|
|
30
|
+
- Fastify plugin ecosystem support
|
|
31
|
+
- TypeScript-first codebase and typings
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install owebjs
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Runtime Modes (Fastify vs uWebSockets)
|
|
40
|
+
|
|
41
|
+
Use the same Oweb API in both modes.
|
|
42
|
+
|
|
43
|
+
### Default mode (Fastify runtime)
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
import Oweb from 'owebjs';
|
|
47
|
+
|
|
48
|
+
const app = await new Oweb().setup();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### High-performance mode (uWebSockets runtime)
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
import Oweb from 'owebjs';
|
|
55
|
+
|
|
56
|
+
const app = await new Oweb({ uWebSocketsEnabled: true }).setup();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
When to prefer `uWebSocketsEnabled: true`:
|
|
60
|
+
|
|
61
|
+
- very high connection count
|
|
62
|
+
- aggressive WebSocket usage
|
|
63
|
+
- throughput-focused deployments
|
|
64
|
+
|
|
65
|
+
## Benchmark
|
|
66
|
+
|
|
67
|
+
**Machine**: Windows 11, Ryzen 5 5500, 16GB RAM, 6C/12T, SSD (Base 3.60 GHz, boost ~4.1 GHz observed)
|
|
68
|
+
|
|
69
|
+
**Method**: `autocannon -c 100 -d 40 -p 10 localhost:3000` \* 2, taking the
|
|
70
|
+
second average
|
|
71
|
+
|
|
72
|
+
| Runtime | Version | Requests/sec |
|
|
73
|
+
| ------------------------- | --------- | -----------: |
|
|
74
|
+
| uWebSockets.js | 20.52.0 | 79,149 |
|
|
75
|
+
| **Oweb (uWS)** | 1.5.7-dev | 70,535 |
|
|
76
|
+
| 0http | 4.4.0 | 46,605 |
|
|
77
|
+
| Fastify | 4.23.2 | 46,238 |
|
|
78
|
+
| **Oweb (Fastify)** | 1.5.7-dev | 42,570 |
|
|
79
|
+
| Node.js http.createServer | 24.5.0 | 42,544 |
|
|
80
|
+
| Express | 5.2.1 | 24,913 |
|
|
81
|
+
|
|
82
|
+
This is a synthetic "Hello, Word!" benchmark that aims to evaluate the framework overhead.
|
|
83
|
+
The overhead that each framework has on your application depends on your application.
|
|
84
|
+
You should always benchmark if performance matters to you.
|
|
85
|
+
|
|
86
|
+
## First App (2 Minutes)
|
|
87
|
+
|
|
88
|
+
Start with a minimal app, then we will add route conventions step by step.
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
import Oweb from 'owebjs';
|
|
92
|
+
|
|
93
|
+
const app = await new Oweb({ uWebSocketsEnabled: true }).setup();
|
|
94
|
+
|
|
95
|
+
await app.loadRoutes({
|
|
96
|
+
directory: 'routes',
|
|
97
|
+
hmr: {
|
|
98
|
+
enabled: true,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const { err, address } = await app.start({ port: 3000, host: '127.0.0.1' });
|
|
103
|
+
if (err) throw err;
|
|
104
|
+
|
|
105
|
+
console.log(`Server running at ${address}`);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
What this does:
|
|
109
|
+
|
|
110
|
+
- Creates an Oweb app instance (here in uWebSockets mode)
|
|
111
|
+
- Loads your route files from `routes/`
|
|
112
|
+
- Enables HMR for development
|
|
113
|
+
- Starts the HTTP server
|
|
114
|
+
|
|
115
|
+
## How Oweb Maps Files to URLs
|
|
116
|
+
|
|
117
|
+
The file system is the routing table. Here is a representative structure:
|
|
118
|
+
|
|
119
|
+
```txt
|
|
120
|
+
routes/
|
|
121
|
+
_hooks.js
|
|
122
|
+
hello.js
|
|
123
|
+
users/
|
|
124
|
+
[id].js
|
|
125
|
+
auth/
|
|
126
|
+
login.post.js
|
|
127
|
+
matcher/
|
|
128
|
+
[id=integer].js
|
|
129
|
+
events/
|
|
130
|
+
sse.js
|
|
131
|
+
ws/
|
|
132
|
+
echo.js
|
|
133
|
+
|
|
134
|
+
matchers/
|
|
135
|
+
integer.js
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Now let's go through each convention in isolation.
|
|
139
|
+
|
|
140
|
+
## Routing Conventions
|
|
141
|
+
|
|
142
|
+
### 1) Basic route
|
|
143
|
+
|
|
144
|
+
Use a normal file for a `GET` route.
|
|
145
|
+
|
|
146
|
+
`routes/hello.js` -> `GET /hello`
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
import { Route } from 'owebjs';
|
|
150
|
+
|
|
151
|
+
export default class HelloRoute extends Route {
|
|
152
|
+
handle() {
|
|
153
|
+
return { message: 'hello-world' };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 2) Dynamic params
|
|
159
|
+
|
|
160
|
+
Put parameter names in brackets.
|
|
161
|
+
|
|
162
|
+
`routes/users/[id].js` -> `GET /users/:id`
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
import { Route } from 'owebjs';
|
|
166
|
+
|
|
167
|
+
export default class UserRoute extends Route {
|
|
168
|
+
handle(req) {
|
|
169
|
+
return { id: req.params.id };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 3) HTTP method suffix
|
|
175
|
+
|
|
176
|
+
Use filename suffixes when an endpoint is not `GET`.
|
|
177
|
+
|
|
178
|
+
`routes/auth/login.post.js` -> `POST /auth/login`
|
|
179
|
+
|
|
180
|
+
Supported suffixes: `.get`, `.post`, `.put`, `.patch`, `.delete`
|
|
181
|
+
|
|
182
|
+
```js
|
|
183
|
+
import { Route } from 'owebjs';
|
|
184
|
+
|
|
185
|
+
export default class LoginPostRoute extends Route {
|
|
186
|
+
handle(req, res) {
|
|
187
|
+
return res.status(201).send({ method: req.method, body: req.body });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 4) Matcher params
|
|
193
|
+
|
|
194
|
+
Matcher params add filename-level validation.
|
|
195
|
+
|
|
196
|
+
`routes/matcher/[id=integer].js` + `matchers/integer.js`
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
// matchers/integer.js
|
|
200
|
+
export default function integerMatcher(value) {
|
|
201
|
+
return /^-?\d+$/.test(String(value));
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Then register the matcher directory:
|
|
206
|
+
|
|
207
|
+
```js
|
|
208
|
+
await app.loadRoutes({
|
|
209
|
+
directory: 'routes',
|
|
210
|
+
matchersDirectory: 'matchers',
|
|
211
|
+
hmr: {
|
|
212
|
+
enabled: true,
|
|
213
|
+
matchersDirectory: 'matchers',
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
If the matcher returns `false`, the route is treated as not matched.
|
|
219
|
+
|
|
220
|
+
## Hooks (Directory Middleware)
|
|
221
|
+
|
|
222
|
+
Hooks are defined with `_hooks.js` and run for that folder scope.
|
|
223
|
+
|
|
224
|
+
Example root hook:
|
|
225
|
+
|
|
226
|
+
```js
|
|
227
|
+
import { Hook } from 'owebjs';
|
|
228
|
+
|
|
229
|
+
export default class RootHook extends Hook {
|
|
230
|
+
handle(req, _res, done) {
|
|
231
|
+
req.locals ??= {};
|
|
232
|
+
req.locals.trace = ['root'];
|
|
233
|
+
done();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Key behavior:
|
|
239
|
+
|
|
240
|
+
- Hooks apply to the current directory and child directories
|
|
241
|
+
- Nested folders can add more hooks
|
|
242
|
+
- Hooks run before the route handler
|
|
243
|
+
- Scoped folders using parentheses (like `(admin)`) creates a hook boundary
|
|
244
|
+
|
|
245
|
+
### Scoped hook groups
|
|
246
|
+
|
|
247
|
+
Oweb's route walker inspects parent hook paths and looks for the nearest folder whose name is wrapped in parentheses (for example `(api)` or `(admin)`).
|
|
248
|
+
|
|
249
|
+
When such a folder exists, hook resolution is cut at that scope boundary. In practice, hooks above that scoped folder are excluded.
|
|
250
|
+
|
|
251
|
+
Example:
|
|
252
|
+
|
|
253
|
+
```txt
|
|
254
|
+
routes/
|
|
255
|
+
_hooks.js # global hook
|
|
256
|
+
(admin)/
|
|
257
|
+
_hooks.js # admin scope boundary hook
|
|
258
|
+
users/
|
|
259
|
+
_hooks.js
|
|
260
|
+
[id].js
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
For `routes/(admin)/users/[id].js`, Oweb uses hooks inside that scoped chain and stops climbing above the `(admin)` boundary.
|
|
264
|
+
|
|
265
|
+
## Error Handling
|
|
266
|
+
|
|
267
|
+
You can handle errors globally, or per route when you need custom behavior.
|
|
268
|
+
|
|
269
|
+
### Global internal error handler
|
|
270
|
+
|
|
271
|
+
```js
|
|
272
|
+
app.setInternalErrorHandler((req, res, error) => {
|
|
273
|
+
res.status(500).send({
|
|
274
|
+
source: 'global-handler',
|
|
275
|
+
message: error.message,
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Route-specific `handleError`
|
|
281
|
+
|
|
282
|
+
```js
|
|
283
|
+
import { Route } from 'owebjs';
|
|
284
|
+
|
|
285
|
+
export default class RouteWithCustomError extends Route {
|
|
286
|
+
handle() {
|
|
287
|
+
throw new Error('route-specific-error');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
handleError(_req, res, err) {
|
|
291
|
+
return res.status(409).send({
|
|
292
|
+
source: 'route-handleError',
|
|
293
|
+
message: err.message,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Use route-level handling when you want endpoint-specific status codes or payload shape.
|
|
300
|
+
|
|
301
|
+
## SSE (Server-Sent Events)
|
|
302
|
+
|
|
303
|
+
If a route returns an iterable or async iterable, Oweb streams it as SSE.
|
|
304
|
+
|
|
305
|
+
```js
|
|
306
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
307
|
+
import { Route } from 'owebjs';
|
|
308
|
+
|
|
309
|
+
export default class SseRoute extends Route {
|
|
310
|
+
async *handle(req, res) {
|
|
311
|
+
if (req.query?.deny === '1') {
|
|
312
|
+
return res.status(401).send({ code: 'error.unauthorized' });
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
yield 'event-1';
|
|
316
|
+
await delay(20);
|
|
317
|
+
|
|
318
|
+
yield { step: 2 };
|
|
319
|
+
await delay(20);
|
|
320
|
+
|
|
321
|
+
yield 3;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
This is useful for live feeds, progress updates, and long-running operations.
|
|
327
|
+
|
|
328
|
+
## WebSockets
|
|
329
|
+
|
|
330
|
+
Create a file that exports a class extending `WebSocketRoute`.
|
|
331
|
+
|
|
332
|
+
`routes/ws/echo.js` -> `WS /ws/echo`
|
|
333
|
+
|
|
334
|
+
```js
|
|
335
|
+
import { WebSocketRoute } from 'owebjs';
|
|
336
|
+
|
|
337
|
+
export default class EchoSocketRoute extends WebSocketRoute {
|
|
338
|
+
open(ws) {
|
|
339
|
+
ws.send('ready');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
message(ws, message, isBinary) {
|
|
343
|
+
ws.send(message, isBinary);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Works in both runtime modes:
|
|
349
|
+
|
|
350
|
+
- Fastify WebSocket adapter (default)
|
|
351
|
+
- Native `uWebSockets.js` mode
|
|
352
|
+
|
|
353
|
+
## HMR (Hot Module Replacement)
|
|
354
|
+
|
|
355
|
+
Enable HMR while loading routes:
|
|
356
|
+
|
|
357
|
+
```js
|
|
358
|
+
await app.loadRoutes({
|
|
359
|
+
directory: 'routes',
|
|
360
|
+
matchersDirectory: 'matchers',
|
|
361
|
+
hmr: {
|
|
362
|
+
enabled: true,
|
|
363
|
+
directory: 'routes',
|
|
364
|
+
matchersDirectory: 'matchers',
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Notes:
|
|
370
|
+
|
|
371
|
+
- HMR is disabled in `NODE_ENV=production`
|
|
372
|
+
- Route hook files (`_hooks.js` / `_hooks.ts`) are not hot-reloaded
|
|
373
|
+
- For hook changes, restart the server
|
|
374
|
+
|
|
375
|
+
## Fastify Plugin Compatibility
|
|
376
|
+
|
|
377
|
+
Because Oweb sits on Fastify, you can register Fastify plugins directly.
|
|
378
|
+
|
|
379
|
+
```js
|
|
380
|
+
import multipart from '@fastify/multipart';
|
|
381
|
+
|
|
382
|
+
await app.register(multipart, {
|
|
383
|
+
limits: {
|
|
384
|
+
fileSize: 10 * 1024 * 1024,
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## Built-in Plugin: Chunk Upload
|
|
390
|
+
|
|
391
|
+
Oweb exposes `ChunkUpload` from `owebjs/plugins` for chunked upload workflows.
|
|
392
|
+
|
|
393
|
+
```js
|
|
394
|
+
import { Route } from 'owebjs';
|
|
395
|
+
import { ChunkUpload, ChunkUploadStatus } from 'owebjs/plugins';
|
|
396
|
+
|
|
397
|
+
export default class ChunkUploadRoute extends Route {
|
|
398
|
+
async handle(req, res) {
|
|
399
|
+
const file = await req.file();
|
|
400
|
+
const buffer = await file.toBuffer();
|
|
401
|
+
|
|
402
|
+
const result = await ChunkUpload(
|
|
403
|
+
{
|
|
404
|
+
buffer,
|
|
405
|
+
fileName: file.filename,
|
|
406
|
+
currentChunk: Number(req.query.currentChunk),
|
|
407
|
+
totalChunks: Number(req.query.totalChunks),
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
path: './uploads',
|
|
411
|
+
maxChunkSize: 1024 * 1024,
|
|
412
|
+
maxFileSize: 10 * 1024 * 1024,
|
|
413
|
+
},
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
if (
|
|
417
|
+
result.status === ChunkUploadStatus.ChunkTooLarge ||
|
|
418
|
+
result.status === ChunkUploadStatus.FileTooLarge
|
|
419
|
+
) {
|
|
420
|
+
return res.status(413).send(result);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return res.send(result);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
## TypeScript
|
|
429
|
+
|
|
430
|
+
Oweb supports `.ts` route files and exports framework typings. You can keep the same file conventions and class model in TypeScript projects.
|
|
431
|
+
|
|
432
|
+
## License
|
|
433
|
+
|
|
434
|
+
MIT
|