vortez 5.0.0-dev.18 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitignore +11 -4
- package/README.md +699 -177
- package/build/Template/Compiler.d.ts +70 -0
- package/build/Template/Compiler.js +135 -0
- package/build/Template/Compiler.js.map +1 -0
- package/build/Template/StreamCompiler.d.ts +16 -0
- package/build/Template/StreamCompiler.js +54 -0
- package/build/Template/StreamCompiler.js.map +1 -0
- package/build/Template/Template.d.ts +49 -0
- package/build/Template/Template.js +77 -0
- package/build/Template/Template.js.map +1 -0
- package/build/Vortez.d.ts +2 -1
- package/build/Vortez.js +2 -1
- package/build/Vortez.js.map +1 -1
- package/build/beta/JwtManager/JwtError.d.ts +8 -0
- package/build/beta/JwtManager/JwtError.js +10 -0
- package/build/beta/JwtManager/JwtError.js.map +1 -0
- package/build/beta/JwtManager/JwtManager.d.ts +3 -0
- package/build/beta/JwtManager/JwtManager.js +24 -7
- package/build/beta/JwtManager/JwtManager.js.map +1 -1
- package/build/server/BodyParser.d.ts +6 -0
- package/build/server/BodyParser.js +18 -3
- package/build/server/BodyParser.js.map +1 -1
- package/build/server/Request.d.ts +6 -4
- package/build/server/Request.js +15 -10
- package/build/server/Request.js.map +1 -1
- package/build/server/Response.d.ts +25 -10
- package/build/server/Response.js +161 -75
- package/build/server/Response.js.map +1 -1
- package/build/server/Server.d.ts +4 -4
- package/build/server/Server.js +34 -11
- package/build/server/Server.js.map +1 -1
- package/build/server/ServerDebug.d.ts +10 -1
- package/build/server/ServerDebug.js +85 -17
- package/build/server/ServerDebug.js.map +1 -1
- package/build/server/config/Config.d.ts +276 -47
- package/build/server/config/Config.js +70 -47
- package/build/server/config/Config.js.map +1 -1
- package/build/server/config/{ConfigLoader.d.ts → Loader.d.ts} +4 -5
- package/build/server/config/{ConfigLoader.js → Loader.js} +8 -11
- package/build/server/config/Loader.js.map +1 -0
- package/build/server/router/Router.d.ts +88 -31
- package/build/server/router/Router.js +113 -51
- package/build/server/router/Router.js.map +1 -1
- package/build/server/router/algorithm/Algorithm.d.ts +39 -0
- package/build/server/router/algorithm/Algorithm.js +20 -0
- package/build/server/router/algorithm/Algorithm.js.map +1 -0
- package/build/server/router/algorithm/FIFO.d.ts +15 -0
- package/build/server/router/algorithm/FIFO.js +24 -0
- package/build/server/router/algorithm/FIFO.js.map +1 -0
- package/build/server/router/algorithm/Tree.d.ts +38 -0
- package/build/server/router/algorithm/Tree.js +126 -0
- package/build/server/router/algorithm/Tree.js.map +1 -0
- package/build/server/router/middleware/HttpMiddleware.js +1 -1
- package/build/server/router/middleware/HttpMiddleware.js.map +1 -1
- package/build/server/router/middleware/WsMiddleware.js +1 -1
- package/build/server/router/middleware/WsMiddleware.js.map +1 -1
- package/build/server/security/PathSecurity.d.ts +45 -0
- package/build/server/security/PathSecurity.js +108 -0
- package/build/server/security/PathSecurity.js.map +1 -0
- package/build/server/websocket/Websocket.js +4 -1
- package/build/server/websocket/Websocket.js.map +1 -1
- package/build/utilities/ConsoleUI.d.ts +2 -1
- package/build/utilities/ConsoleUI.js +2 -1
- package/build/utilities/ConsoleUI.js.map +1 -1
- package/build/utilities/DebugUI.d.ts +1 -1
- package/build/utilities/DebugUI.js +1 -1
- package/build/utilities/Encoding.d.ts +22 -0
- package/build/utilities/Encoding.js +26 -0
- package/build/utilities/Encoding.js.map +1 -0
- package/build/utilities/Env.js +7 -2
- package/build/utilities/Env.js.map +1 -1
- package/build/utilities/File.d.ts +10 -0
- package/build/utilities/File.js +19 -0
- package/build/utilities/File.js.map +1 -0
- package/build/utilities/Flatten.d.ts +73 -0
- package/build/utilities/Flatten.js +76 -0
- package/build/utilities/Flatten.js.map +1 -0
- package/build/utilities/Object.d.ts +16 -0
- package/build/utilities/Object.js +48 -0
- package/build/utilities/Object.js.map +1 -0
- package/build/utilities/Path.d.ts +31 -11
- package/build/utilities/Path.js +48 -15
- package/build/utilities/Path.js.map +1 -1
- package/build/utilities/Time.d.ts +21 -0
- package/build/utilities/Time.js +25 -0
- package/build/utilities/Time.js.map +1 -0
- package/build/utilities/Utilities.d.ts +50 -92
- package/build/utilities/Utilities.js +56 -71
- package/build/utilities/Utilities.js.map +1 -1
- package/build/utilities/schema/Introspection.d.ts +24 -0
- package/build/utilities/schema/Introspection.js +87 -0
- package/build/utilities/schema/Introspection.js.map +1 -0
- package/build/utilities/schema/JSONSchema.d.ts +68 -0
- package/build/utilities/schema/JSONSchema.js +13 -0
- package/build/utilities/schema/JSONSchema.js.map +1 -0
- package/build/utilities/schema/Schema.d.ts +253 -0
- package/build/utilities/schema/Schema.js +241 -0
- package/build/utilities/schema/Schema.js.map +1 -0
- package/build/utilities/schema/SchemaError.d.ts +10 -0
- package/build/utilities/schema/SchemaError.js +13 -0
- package/build/utilities/schema/SchemaError.js.map +1 -0
- package/build/utilities/schema/Validator.d.ts +94 -0
- package/build/utilities/schema/Validator.js +246 -0
- package/build/utilities/schema/Validator.js.map +1 -0
- package/changes.md +4 -0
- package/docs/ARCHITECTURE.md +142 -0
- package/global/style/template/error.css +29 -0
- package/global/style/template/folder.css +79 -0
- package/global/{Style/Template/Template.css → style/template/template.css} +60 -68
- package/global/template/error.vhtml +29 -0
- package/global/template/folder.vhtml +54 -0
- package/package.json +2 -2
- package/build/Template.d.ts +0 -46
- package/build/Template.js +0 -81
- package/build/Template.js.map +0 -1
- package/build/server/config/ConfigLoader.js.map +0 -1
- package/build/server/config/ConfigValidator.d.ts +0 -71
- package/build/server/config/ConfigValidator.js +0 -131
- package/build/server/config/ConfigValidator.js.map +0 -1
- package/examples/in-docs.js +0 -96
- package/global/Style/Template/Error.css +0 -30
- package/global/Style/Template/Folder.css +0 -77
- package/global/Template/Error.vhtml +0 -29
- package/global/Template/Folder.vhtml +0 -41
- package/tests/Template/template.js +0 -18
- package/tests/Template/template.txt +0 -13
- package/tests/Template/template.vhtml +0 -23
- package/tests/debug.js +0 -34
- package/tests/jwtManager/jwtManager.js +0 -342
- package/tests/test.js +0 -131
- package/tests/test.vhtml +0 -14
- package/tests/utilities.js +0 -28
- package/tests/websocket.vhtml +0 -86
- /package/global/{Source/Logo_960.png → source/logo_960.png} +0 -0
- /package/global/{Source/Logo_SM_960.png → source/logo_SM_960.png} +0 -0
package/README.md
CHANGED
|
@@ -1,52 +1,310 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Vortez
|
|
2
|
+
|
|
3
|
+
A lightweight, production-ready Node.js web framework for building APIs, websites, single-page applications (SPAs), and progressive web apps (PWAs). Built with TypeScript and designed for simplicity without sacrificing power.
|
|
4
|
+
|
|
5
|
+
The package exports a default `Vortez` server class, plus named exports for `Router`, `Config`, `Template`, `Utilities`, `ServerError`, `Logger`, and `Beta`.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/vortez)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://nodejs.org/)
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- 🚀 **Fast and Lightweight** — Minimal overhead, maximum performance
|
|
14
|
+
- 📦 **Built with TypeScript** — Full type safety with compiled JavaScript output
|
|
15
|
+
- 🔄 **Flexible Routing** — Pattern-based routing with wildcards and dynamic parameters
|
|
16
|
+
- 🔌 **WebSocket Support** — Real-time bidirectional communication out of the box
|
|
17
|
+
- 🛡️ **HTTPS/SSL Ready** — Secure connections with easy configuration
|
|
18
|
+
- 🎯 **Middleware Pipeline** — Composable, snapshot-based middleware system
|
|
19
|
+
- 📝 **Request/Response Helpers** — Simplified APIs for JSON, files, and text responses
|
|
20
|
+
- ⚙️ **Modular Architecture** — Use what you need, extend what you want
|
|
21
|
+
- 🔐 **Beta Features** — JWT authentication and email support (development version)
|
|
22
|
+
|
|
23
|
+
## Table of Contents
|
|
24
|
+
|
|
25
|
+
- [Quick Start](#quick-start)
|
|
26
|
+
- [Installation](#installation)
|
|
27
|
+
- [Getting Started](#getting-started)
|
|
28
|
+
- [Configuration Files](#configuration-files)
|
|
29
|
+
- [Architecture Guide](#architecture-guide)
|
|
30
|
+
- [Examples Repository](#examples-repository)
|
|
31
|
+
- [Core Concepts](#core-concepts)
|
|
32
|
+
- [Routing Rules](#routing-rules)
|
|
33
|
+
- [Server Configuration](#server-configuration)
|
|
34
|
+
- [Use Cases](#use-cases)
|
|
35
|
+
- [Development Features](#development-features)
|
|
2
36
|
|
|
3
|
-
|
|
4
|
-
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
### Installation
|
|
42
|
+
|
|
43
|
+
```console
|
|
44
|
+
npm install vortez
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Development version** (with beta features):
|
|
48
|
+
|
|
49
|
+
```console
|
|
50
|
+
npm install vortez@dev
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Requirements
|
|
54
|
+
|
|
55
|
+
- **Node.js** ≥ 16.0.0
|
|
56
|
+
- **ES Modules** — Set `"type": "module"` in your `package.json`
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"name": "my-app",
|
|
61
|
+
"type": "module",
|
|
62
|
+
"main": "index.js"
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
> [!IMPORTANT]
|
|
67
|
+
> CommonJS support will be added in a future release. For now, ES modules are required.
|
|
68
|
+
|
|
69
|
+
### Minimal Example
|
|
5
70
|
|
|
6
|
-
|
|
7
|
-
|
|
71
|
+
```js
|
|
72
|
+
import Vortez from 'vortez';
|
|
8
73
|
|
|
9
|
-
|
|
10
|
-
I actively use this module in my own web projects, which means I’m always finding new ideas, enhancements, and opportunities to fix bugs based on feedback.
|
|
74
|
+
const server = new Vortez({ port: 3000 });
|
|
11
75
|
|
|
12
|
-
|
|
13
|
-
|
|
76
|
+
// Define a route
|
|
77
|
+
server.router.addAction('GET', '/hello', (request, response) => {
|
|
78
|
+
response.sendJson({ message: 'Hello World' });
|
|
79
|
+
});
|
|
14
80
|
|
|
15
|
-
|
|
16
|
-
|
|
81
|
+
// Start the server
|
|
82
|
+
await server.start();
|
|
83
|
+
// Server running on http://localhost:3000
|
|
84
|
+
```
|
|
17
85
|
|
|
18
86
|
---
|
|
19
87
|
|
|
20
|
-
|
|
88
|
+
## Installation
|
|
21
89
|
|
|
22
|
-
|
|
90
|
+
Vortez is available on npm:
|
|
23
91
|
|
|
24
|
-
|
|
92
|
+
```console
|
|
93
|
+
npm install vortez
|
|
94
|
+
```
|
|
25
95
|
|
|
26
|
-
|
|
27
|
-
mpm install vortez
|
|
28
|
-
```
|
|
29
|
-
* **Development version**
|
|
96
|
+
For the latest development version with experimental features:
|
|
30
97
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
98
|
+
```console
|
|
99
|
+
npm install vortez@dev
|
|
100
|
+
```
|
|
34
101
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Getting Started
|
|
105
|
+
|
|
106
|
+
### Creating a Server
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
import Vortez from 'vortez';
|
|
110
|
+
|
|
111
|
+
const server = new Vortez();
|
|
112
|
+
|
|
113
|
+
// Configure (optional)
|
|
114
|
+
server.config.set('port', 3000);
|
|
115
|
+
server.config.set('host', 'localhost');
|
|
116
|
+
|
|
117
|
+
// Add routes
|
|
118
|
+
server.router.addAction('GET', '/', (request, response) => {
|
|
119
|
+
response.sendJson({ status: 'ok' });
|
|
120
|
+
});
|
|
121
|
+
server.router.addFile('/favicon.ico', '/assets/icon.ico');
|
|
122
|
+
|
|
123
|
+
// Start listening
|
|
124
|
+
await server.start();
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Constructor Options
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
const server = new Vortez({
|
|
131
|
+
host: 'localhost', // Default: 'localhost'
|
|
132
|
+
port: 80, // Default: 80
|
|
133
|
+
ssl: null // Optional HTTPS configuration
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Configuration Files
|
|
138
|
+
|
|
139
|
+
You can also build a `Config` directly or load one from disk.
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
import Vortez, { Config } from 'vortez';
|
|
143
|
+
|
|
144
|
+
const config = new Config({
|
|
145
|
+
host: 'localhost',
|
|
146
|
+
port: 3000,
|
|
147
|
+
routing: {
|
|
148
|
+
algorithm: 'FIFO'
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
console.log(config.toJson());
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
import Vortez, { Config } from 'vortez';
|
|
157
|
+
|
|
158
|
+
const config = await Config.Loader.load('config.json');
|
|
159
|
+
// You can directly use of Vortez import
|
|
160
|
+
const config = await Vortez.Config.Loader.load('config.json');
|
|
161
|
+
|
|
162
|
+
const server = new Vortez(config);
|
|
163
|
+
await server.start();
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
> [!NOTE]
|
|
167
|
+
> Template defaults are absolute by design and point to this module's bundled `global/Template/*` files.
|
|
168
|
+
> If you set `templates.error` or `templates.folder` yourself, you can still use paths relative to your own project.
|
|
169
|
+
|
|
170
|
+
This workflow is also covered in the dedicated examples repository.
|
|
171
|
+
|
|
172
|
+
### Architecture Guide
|
|
173
|
+
|
|
174
|
+
For a comprehensive guide on Vortez's internal architecture, request lifecycle, performance considerations, and deployment patterns, see [Architecture Documentation](docs/ARCHITECTURE.md).
|
|
175
|
+
|
|
176
|
+
### Examples Repository
|
|
177
|
+
|
|
178
|
+
The runnable examples are being moved to a separate repository to keep this package focused.
|
|
179
|
+
|
|
180
|
+
Until that repository is published, the snippets in this README remain the canonical reference.
|
|
46
181
|
|
|
47
182
|
---
|
|
48
183
|
|
|
49
|
-
|
|
184
|
+
## Core Concepts
|
|
185
|
+
|
|
186
|
+
### URL Rules
|
|
187
|
+
|
|
188
|
+
URL rules define how requests are matched to routes. They use patterns with special markers:
|
|
189
|
+
|
|
190
|
+
| Pattern | Behavior | Example |
|
|
191
|
+
|---------|----------|---------|
|
|
192
|
+
| `/` | Root path | `/` |
|
|
193
|
+
| `/path` | Literal segment | `/api/users` |
|
|
194
|
+
| `/$param` | Dynamic parameter | `/$id` captures `123` as `id` |
|
|
195
|
+
| `/*` | Wildcard (matches all sub-routes) | `/files/*` matches `/files/a/b/c` |
|
|
196
|
+
| `/path/$id/sub` | Mixed patterns | `/user/$id/posts` |
|
|
197
|
+
|
|
198
|
+
**Examples:**
|
|
199
|
+
|
|
200
|
+
```js
|
|
201
|
+
// Static routes
|
|
202
|
+
server.router.addAction('GET', '/', homeHandler);
|
|
203
|
+
server.router.addAction('GET', '/api/status', statusHandler);
|
|
204
|
+
|
|
205
|
+
// Dynamic routes with parameters
|
|
206
|
+
server.router.addAction('GET', '/api/users/$id', (request, response) => {
|
|
207
|
+
const userId = request.ruleParams.id;
|
|
208
|
+
// Handle request...
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Wildcard routes
|
|
212
|
+
server.router.addAction('GET', '/api/*', catchAllHandler);
|
|
213
|
+
server.router.addFile('/docs/*', 'public/docs/index.html');
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Request Object
|
|
217
|
+
|
|
218
|
+
The `request` object contains information about the incoming HTTP request:
|
|
219
|
+
|
|
220
|
+
```js
|
|
221
|
+
server.router.addAction('GET', '/api/$action/$id', (request, response) => {
|
|
222
|
+
console.log(request.method); // 'GET', 'POST', etc.
|
|
223
|
+
console.log(request.url); // Full URL
|
|
224
|
+
console.log(request.ruleParams); // { action: '...', id: '...' }
|
|
225
|
+
console.log(request.searchParams); // Query parameters object (Record<string, string | undefined>)
|
|
226
|
+
console.log(request.headers); // HTTP headers
|
|
227
|
+
// For body, use: const body = await request.post;
|
|
228
|
+
});
|
|
229
|
+
```. All methods are **async** and must be awaited:
|
|
230
|
+
|
|
231
|
+
```js
|
|
232
|
+
// Send JSON
|
|
233
|
+
await response.sendJson({ key: 'value' });
|
|
234
|
+
|
|
235
|
+
// Send HTML/text
|
|
236
|
+
await response.send('Hello World');
|
|
237
|
+
|
|
238
|
+
// Send file
|
|
239
|
+
await response.sendFile('path/to/file.html');
|
|
240
|
+
|
|
241
|
+
// Send with custom status and headers
|
|
242
|
+
await response.sendJson({ id: 123 }, {
|
|
243
|
+
status: 201,
|
|
244
|
+
headers: {
|
|
245
|
+
'X-Test': 'TestHeader'
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// The same options shape is supported by send, sendJson, sendFile and sendTemplate
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
> **Breaking Change in 5.0.0**: All response methods are now async. Update your route handlers to use `await` when calling response methods.
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// The same options shape is supported by send, sendJson, sendFile and sendTemplate
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Middleware Execution Model
|
|
259
|
+
|
|
260
|
+
Vortez uses a **snapshot middleware composition model**:
|
|
261
|
+
|
|
262
|
+
- Global middleware registered via `router.httpMiddleware.use()` / `router.httpMiddleware.useError()`
|
|
263
|
+
and `router.wsMiddleware.use()` / `router.wsMiddleware.useError()` is captured at rule registration time
|
|
264
|
+
- Middleware is not updated retroactively for existing rules
|
|
265
|
+
- When mounting sub-routers, child middleware is appended after parent middleware
|
|
266
|
+
|
|
267
|
+
**Key principle:** Register all global middleware first, then add routes.
|
|
268
|
+
|
|
269
|
+
The `router.use()` helper merges middleware instances; individual middleware functions are added on `router.httpMiddleware` or `router.wsMiddleware`.
|
|
270
|
+
|
|
271
|
+
```js
|
|
272
|
+
const router = server.router;
|
|
273
|
+
|
|
274
|
+
// Register middleware first
|
|
275
|
+
router.httpMiddleware.use(authMiddleware);
|
|
276
|
+
router.httpMiddleware.use(loggingMiddleware);
|
|
277
|
+
|
|
278
|
+
// Add routes (they capture current middleware)
|
|
279
|
+
router.addAction('GET', '/old', oldHandler);
|
|
280
|
+
// old → [authMiddleware, loggingMiddleware]
|
|
281
|
+
|
|
282
|
+
// Add more middleware
|
|
283
|
+
router.httpMiddleware.use(newMiddleware);
|
|
284
|
+
|
|
285
|
+
// New routes get updated middleware
|
|
286
|
+
router.addAction('GET', '/new', newHandler)
|
|
287
|
+
// Add a middleware only to a single rule
|
|
288
|
+
.httpMiddleware.use(otherMiddleware);
|
|
289
|
+
// new → [authMiddleware, loggingMiddleware, newMiddleware]
|
|
290
|
+
|
|
291
|
+
// Old routes still use original middleware!
|
|
292
|
+
// old → [authMiddleware, loggingMiddleware]
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Practical example with sub-routers:**
|
|
296
|
+
|
|
297
|
+
```js
|
|
298
|
+
const apiRouter = new Vortez.Router();
|
|
299
|
+
apiRouter.httpMiddleware.use(apiAuthMiddleware);
|
|
300
|
+
apiRouter.addAction('GET', '/users', getUsersHandler);
|
|
301
|
+
|
|
302
|
+
server.router.httpMiddleware.use(globalMiddleware);
|
|
303
|
+
server.router.mount(apiRouter, '/api');
|
|
304
|
+
// Final middleware chain: [globalMiddleware, apiAuthMiddleware]
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
50
308
|
|
|
51
309
|
## Static Pages
|
|
52
310
|
|
|
@@ -63,256 +321,520 @@ const server = new Vortez();
|
|
|
63
321
|
server.router.addFolder('/source', 'source');
|
|
64
322
|
server.router.addFile('/', 'source/index.html');
|
|
65
323
|
|
|
324
|
+
// Set all other routes or an specific rule for the static page
|
|
325
|
+
server.router.addFile('/*', 'source/index.html');
|
|
326
|
+
server.router.addFile('/app/*', 'source/index.html');
|
|
327
|
+
|
|
66
328
|
// Starting the server
|
|
67
|
-
server.start();
|
|
329
|
+
await server.start();
|
|
68
330
|
```
|
|
69
331
|
|
|
70
332
|
---
|
|
71
333
|
|
|
72
|
-
## APIs
|
|
334
|
+
## REST APIs
|
|
73
335
|
|
|
74
|
-
|
|
336
|
+
Build a complete REST API:
|
|
75
337
|
|
|
76
338
|
```js
|
|
77
339
|
import Vortez from 'vortez';
|
|
78
340
|
|
|
79
|
-
const server = new Vortez();
|
|
341
|
+
const server = new Vortez({ port: 3000 });
|
|
80
342
|
|
|
81
|
-
//
|
|
82
|
-
server.router.addAction('GET', '/api/
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
343
|
+
// Users resource
|
|
344
|
+
server.router.addAction('GET', '/api/users', (req, res) => {
|
|
345
|
+
res.sendJson([
|
|
346
|
+
{ id: 1, name: 'Alice' },
|
|
347
|
+
{ id: 2, name: 'Bob' }
|
|
348
|
+
]);
|
|
87
349
|
});
|
|
88
350
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
server.router.addAction('GET', '/api/params/$id', (request, response) => {
|
|
93
|
-
response.sendJson({
|
|
94
|
-
message: 'Hello World',
|
|
95
|
-
route: `[${request.method}] -> ${request.url}`,
|
|
96
|
-
params: request.ruleParams,
|
|
97
|
-
query: request.searchParams
|
|
98
|
-
});
|
|
351
|
+
server.router.addAction('GET', '/api/users/$id', (req, res) => {
|
|
352
|
+
const id = req.ruleParams.id;
|
|
353
|
+
res.sendJson({ id, name: `User ${id}` });
|
|
99
354
|
});
|
|
100
355
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
356
|
+
server.router.addAction('POST', '/api/users', async (req, res) => {
|
|
357
|
+
const user = await req.post;
|
|
358
|
+
res.sendJson({ id: 123, ...user }, { status: 201 });
|
|
104
359
|
});
|
|
105
360
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
361
|
+
server.router.addAction('PUT', '/api/users/$id', async (req, res) => {
|
|
362
|
+
const id = req.ruleParams.id;
|
|
363
|
+
const body = await req.post;
|
|
364
|
+
res.sendJson({ id, ...body });
|
|
109
365
|
});
|
|
110
366
|
|
|
111
|
-
|
|
112
|
-
|
|
367
|
+
server.router.addAction('DELETE', '/api/users/$id', (req, res) => {
|
|
368
|
+
res.send('', { status: 204 });
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
await server.start();
|
|
113
372
|
```
|
|
114
373
|
|
|
115
374
|
---
|
|
116
375
|
|
|
117
|
-
##
|
|
376
|
+
## Single Page Applications
|
|
118
377
|
|
|
119
|
-
|
|
378
|
+
Serve an SPA with client-side routing:
|
|
120
379
|
|
|
121
380
|
```js
|
|
122
381
|
import Vortez from 'vortez';
|
|
123
382
|
|
|
124
383
|
const server = new Vortez();
|
|
125
384
|
|
|
126
|
-
|
|
127
|
-
server.router.addFile('/app
|
|
385
|
+
// Serve the main app shell for all app routes
|
|
386
|
+
server.router.addFile('/app', 'public/app.html');
|
|
387
|
+
server.router.addFile('/app/*', 'public/app.html');
|
|
388
|
+
|
|
389
|
+
// Serve static assets
|
|
128
390
|
server.router.addFolder('/public', 'public');
|
|
129
391
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
392
|
+
// Optional: API routes
|
|
393
|
+
server.router.addAction('GET', '/api/data', (req, res) => {
|
|
394
|
+
res.sendJson({ /* ... */ });
|
|
395
|
+
});
|
|
134
396
|
|
|
135
|
-
server.start();
|
|
397
|
+
await server.start();
|
|
136
398
|
```
|
|
137
399
|
|
|
138
400
|
---
|
|
139
401
|
|
|
402
|
+
## Routing Rules
|
|
403
|
+
|
|
404
|
+
### Serving Folders
|
|
405
|
+
|
|
406
|
+
Serve an entire directory and its subdirectories:
|
|
407
|
+
|
|
408
|
+
```js
|
|
409
|
+
server.router.addFolder('/public', 'public');
|
|
410
|
+
server.router.addFolder('/docs', 'documentation');
|
|
411
|
+
|
|
412
|
+
// Absolute paths are supported
|
|
413
|
+
server.router.addFolder('/cdn', '/var/www/cdn');
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
> [!WARNING]
|
|
417
|
+
> Never expose sensitive directories:
|
|
418
|
+
> - ❌ `server.router.addFolder('/', '.')` — Exposes the entire project
|
|
419
|
+
> - ❌ `server.router.addFolder('/', 'src')` — Exposes source code
|
|
420
|
+
>
|
|
421
|
+
> Exposed contents include:
|
|
422
|
+
> - Private certificate keys
|
|
423
|
+
> - Database credentials in configuration files
|
|
424
|
+
> - API tokens and secrets
|
|
425
|
+
> - Source code and internal structure
|
|
426
|
+
|
|
427
|
+
### Serving Files
|
|
428
|
+
|
|
429
|
+
Serve a single file at a specific route:
|
|
430
|
+
|
|
431
|
+
```js
|
|
432
|
+
server.router.addFile('/', 'public/index.html');
|
|
433
|
+
server.router.addFile('/sitemap.xml', 'public/sitemap.xml');
|
|
434
|
+
|
|
435
|
+
// Useful for SPAs - serve the same file for multiple routes
|
|
436
|
+
server.router.addFile('/app/*', 'public/app.html');
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Action Routes
|
|
440
|
+
|
|
441
|
+
Execute code to handle requests dynamically:
|
|
442
|
+
|
|
443
|
+
```js
|
|
444
|
+
// Simple JSON API
|
|
445
|
+
server.router.addAction('GET', '/api/status', (request, response) => {
|
|
446
|
+
response.sendJson({
|
|
447
|
+
status: 'online',
|
|
448
|
+
timestamp: new Date().toISOString()
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// With route parameters
|
|
453
|
+
server.router.addAction('GET', '/api/users/$id', (request, response) => {
|
|
454
|
+
const userId = request.ruleParams.id;
|
|
455
|
+
response.sendJson({ id: userId, name: 'User ' + userId });
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// With query parameters
|
|
459
|
+
server.router.addAction('GET', '/api/search', (request, response) => {
|
|
460
|
+
const query = request.searchParams.q;
|
|
461
|
+
response.sendJson({ results: [], query });
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// POST with body
|
|
465
|
+
server.router.addAction('POST', '/api/data', async (request, response) => {
|
|
466
|
+
const body = await request.post;
|
|
467
|
+
response.sendJson({ received: body }, { status: 201 });
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// Multiple methods
|
|
471
|
+
server.router.addAction('GET', '/resource/$id', getResourceHandler);
|
|
472
|
+
server.router.addAction('PUT', '/resource/$id', updateResourceHandler);
|
|
473
|
+
server.router.addAction('DELETE', '/resource/$id', deleteResourceHandler);
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### WebSocket Routes
|
|
477
|
+
|
|
478
|
+
Handle real-time WebSocket connections:
|
|
479
|
+
|
|
480
|
+
```js
|
|
481
|
+
const connections = new Set();
|
|
482
|
+
|
|
483
|
+
server.router.addWebsocket('/chat', (request, socket) => {
|
|
484
|
+
console.log('[WS] New connection from:', request.url);
|
|
485
|
+
|
|
486
|
+
// Notify others
|
|
487
|
+
connections.forEach(conn => {
|
|
488
|
+
conn.send('A user connected');
|
|
489
|
+
});
|
|
490
|
+
connections.add(socket);
|
|
491
|
+
|
|
492
|
+
// Handle incoming messages
|
|
493
|
+
socket.on('message', (data, info) => {
|
|
494
|
+
console.log('Message:', data.toString());
|
|
495
|
+
|
|
496
|
+
// Broadcast to all connections
|
|
497
|
+
connections.forEach(conn => {
|
|
498
|
+
if (conn !== socket) {
|
|
499
|
+
conn.send(data);
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// Handle disconnection
|
|
505
|
+
socket.on('finish', () => {
|
|
506
|
+
connections.delete(socket);
|
|
507
|
+
connections.forEach(conn => {
|
|
508
|
+
conn.send('A user disconnected');
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// Handle errors
|
|
513
|
+
socket.on('error', (error) => {
|
|
514
|
+
console.error('[WS-Error]:', error);
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
> [!NOTE]
|
|
520
|
+
> WebSocket routes use a separate namespace from HTTP routes, so you can have `/api` as both an HTTP action and a WebSocket route without conflicts.
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
140
524
|
## Server Configuration
|
|
141
525
|
|
|
142
|
-
|
|
526
|
+
### Configuration Methods
|
|
527
|
+
|
|
528
|
+
Configure the server using `server.config`:
|
|
143
529
|
|
|
144
530
|
```js
|
|
145
531
|
import Vortez from 'vortez';
|
|
146
532
|
const server = new Vortez();
|
|
147
533
|
|
|
148
|
-
|
|
149
|
-
server.config.
|
|
150
|
-
server.config.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
server.config.
|
|
155
|
-
|
|
534
|
+
// Set individual values
|
|
535
|
+
server.config.set('port', 3000);
|
|
536
|
+
server.config.set('host', '0.0.0.0');
|
|
537
|
+
|
|
538
|
+
// Set nested values
|
|
539
|
+
server.config.set('ssl.cert', 'path/to/cert.pem');
|
|
540
|
+
server.config.set('ssl.key', 'path/to/key.pem');
|
|
541
|
+
|
|
542
|
+
// Get values
|
|
543
|
+
const port = server.config.get('port');
|
|
544
|
+
const algorithm = server.config.get('routing.algorithm');
|
|
156
545
|
```
|
|
157
546
|
|
|
158
|
-
|
|
547
|
+
### Constructor Configuration
|
|
159
548
|
|
|
160
|
-
|
|
549
|
+
Pass configuration to the constructor:
|
|
161
550
|
|
|
162
551
|
```js
|
|
163
552
|
const server = new Vortez({
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
ssl:
|
|
553
|
+
host: '0.0.0.0',
|
|
554
|
+
port: 8080,
|
|
555
|
+
ssl: {
|
|
556
|
+
cert: 'path/to/cert.pem',
|
|
557
|
+
key: 'path/to/key.pem',
|
|
558
|
+
port: 443
|
|
559
|
+
}
|
|
167
560
|
});
|
|
168
561
|
```
|
|
169
562
|
|
|
170
|
-
|
|
563
|
+
### HTTPS/SSL Configuration
|
|
564
|
+
|
|
565
|
+
Enable HTTPS with SSL certificates:
|
|
171
566
|
|
|
172
567
|
```js
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
568
|
+
server.config.set('ssl', {
|
|
569
|
+
cert: 'path/to/certificate.pem',
|
|
570
|
+
key: 'path/to/private-key.pem',
|
|
571
|
+
port: 443 // Optional: HTTPS port (default: 443)
|
|
572
|
+
});
|
|
573
|
+
```
|
|
177
574
|
|
|
178
|
-
|
|
575
|
+
The framework will automatically create an HTTPS server alongside the HTTP server.
|
|
576
|
+
|
|
577
|
+
### Template Path Behavior
|
|
578
|
+
|
|
579
|
+
- `templates.error` and `templates.folder` default to absolute module paths inside Vortez's own `global/Template` directory.
|
|
580
|
+
- This is intentional so defaults work consistently regardless of where your app is executed from.
|
|
581
|
+
- When you provide your own template paths, relative paths are allowed and resolved from your app/project context.
|
|
582
|
+
|
|
583
|
+
### Available Configuration Keys
|
|
584
|
+
|
|
585
|
+
```js
|
|
586
|
+
server.config.set('host', 'localhost'); // Server hostname
|
|
587
|
+
server.config.set('port', 80); // HTTP port
|
|
588
|
+
server.config.set('ssl.cert', 'cert.pem'); // SSL certificate path
|
|
589
|
+
server.config.set('ssl.key', 'key.pem'); // SSL private key path
|
|
590
|
+
server.config.set('ssl.port', 443); // HTTPS port
|
|
591
|
+
server.config.set('templates.error', 'error.html'); // Error page template
|
|
592
|
+
server.config.set('templates.folder', 'folder.html'); // Folder listing template
|
|
593
|
+
server.config.set('routing.algorithm', 'FIFO'); // Routing algorithm ('FIFO' or 'Tree')
|
|
179
594
|
```
|
|
180
595
|
|
|
181
596
|
---
|
|
182
597
|
|
|
183
|
-
##
|
|
598
|
+
## Real-World Example (ArtFolder Style)
|
|
184
599
|
|
|
185
|
-
|
|
600
|
+
This is a complete composition pattern based on the real-world project structure used in ArtFolder,
|
|
601
|
+
with separated routers for UI pages, API endpoints, and WebSocket endpoints.
|
|
186
602
|
|
|
187
|
-
|
|
603
|
+
```js
|
|
604
|
+
import Vortez, { ServerError, Template } from 'vortez';
|
|
188
605
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
* **`<string>`**: A literal segment that must match exactly.
|
|
606
|
+
const config = await Vortez.Config.Loader.load('.config.json');
|
|
607
|
+
const server = new Vortez(config);
|
|
192
608
|
|
|
193
|
-
|
|
609
|
+
const clientRouter = new Vortez.Router();
|
|
610
|
+
const apiRouter = new Vortez.Router();
|
|
611
|
+
const socketRouter = new Vortez.Router();
|
|
612
|
+
|
|
613
|
+
// -------------------------
|
|
614
|
+
// Shared API middleware
|
|
615
|
+
// -------------------------
|
|
616
|
+
apiRouter.httpMiddleware.useError((error, request, response, next, state) => {
|
|
617
|
+
if (error instanceof ServerError) {
|
|
618
|
+
return response.sendJson({ error: error.message }, { status: error.status });
|
|
619
|
+
}
|
|
620
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
621
|
+
return response.sendJson({ error: message }, { status: 500 });
|
|
622
|
+
});
|
|
194
623
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
624
|
+
apiRouter.httpMiddleware.use(async (request, response, next, state) => {
|
|
625
|
+
const page = Number(request.searchParams.page ?? '1');
|
|
626
|
+
const limit = Number(request.searchParams.limit ?? '20');
|
|
627
|
+
if (!Number.isFinite(page) || !Number.isFinite(limit)) {
|
|
628
|
+
throw new ServerError('Invalid pagination params', 400);
|
|
629
|
+
}
|
|
630
|
+
state.page = page;
|
|
631
|
+
state.limit = limit;
|
|
632
|
+
return next();
|
|
633
|
+
});
|
|
198
634
|
|
|
199
|
-
|
|
635
|
+
// -------------------------
|
|
636
|
+
// Client router (SSR + static)
|
|
637
|
+
// -------------------------
|
|
638
|
+
clientRouter.addAction('GET', '/', async (request, response) => {
|
|
639
|
+
const importMap = await Template.load('assets/importmap.json', {});
|
|
640
|
+
await response.sendTemplate('assets/app.html', {
|
|
641
|
+
title: 'Art Folder',
|
|
642
|
+
style: '/client/styles/styles.css',
|
|
643
|
+
logic: '/client/logic/build/logic.js',
|
|
644
|
+
importMap
|
|
645
|
+
});
|
|
646
|
+
});
|
|
200
647
|
|
|
201
|
-
|
|
648
|
+
clientRouter.addAction('GET', '/app/*', async (request, response) => {
|
|
649
|
+
const importMap = await Template.load('assets/importmap.json', {});
|
|
650
|
+
await response.sendTemplate('assets/app.html', {
|
|
651
|
+
title: 'Art Folder',
|
|
652
|
+
style: '/client/styles/styles.css',
|
|
653
|
+
logic: '/client/logic/build/logic.js',
|
|
654
|
+
importMap
|
|
655
|
+
});
|
|
656
|
+
});
|
|
202
657
|
|
|
203
|
-
|
|
658
|
+
clientRouter.addFolder('/client', 'client');
|
|
659
|
+
clientRouter.addFile('/favicon.ico', 'client/source/images/Mochis.gif');
|
|
204
660
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
661
|
+
// -------------------------
|
|
662
|
+
// API router
|
|
663
|
+
// -------------------------
|
|
664
|
+
apiRouter.addAction('GET', '/health', (request, response, state) => {
|
|
665
|
+
response.sendJson({ ok: true, page: state.page, limit: state.limit });
|
|
666
|
+
});
|
|
211
667
|
|
|
212
|
-
|
|
668
|
+
apiRouter.addAction('POST', '/echo', async (request, response) => {
|
|
669
|
+
const body = await request.post;
|
|
670
|
+
response.sendJson({ received: body }, { status: 201 });
|
|
671
|
+
});
|
|
213
672
|
|
|
214
|
-
|
|
673
|
+
apiRouter.addAction('GET', '/user/$uuid', (request, response) => {
|
|
674
|
+
response.sendJson({ userId: request.ruleParams.uuid });
|
|
675
|
+
});
|
|
215
676
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
677
|
+
apiRouter.addAction('GET', '*', (request) => {
|
|
678
|
+
throw new ServerError(`No route found for ${request.method} -> ${request.url}`, 404);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
// -------------------------
|
|
682
|
+
// WebSocket router
|
|
683
|
+
// -------------------------
|
|
684
|
+
socketRouter.addWebsocket('/user/$uuid', (request, socket) => {
|
|
685
|
+
socket.sendJson({
|
|
686
|
+
type: 'welcome',
|
|
687
|
+
uuid: request.ruleParams.uuid,
|
|
688
|
+
queryUuid: request.searchParams.uuid
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
socket.on('message', (data) => {
|
|
692
|
+
socket.sendJson({ type: 'echo', data: data.toString('utf8') });
|
|
693
|
+
});
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
// Mount everything
|
|
697
|
+
server.router.mount(clientRouter);
|
|
698
|
+
server.router.mount(apiRouter, '/api');
|
|
699
|
+
server.router.mount(socketRouter, '/rtc');
|
|
700
|
+
|
|
701
|
+
await server.start();
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
This pattern keeps each concern isolated and mirrors how larger applications organize routes in production.
|
|
705
|
+
|
|
706
|
+
---
|
|
707
|
+
|
|
708
|
+
## Use Cases
|
|
709
|
+
|
|
710
|
+
### Static Website
|
|
711
|
+
|
|
712
|
+
Simple static site with assets:
|
|
229
713
|
|
|
230
714
|
```js
|
|
231
|
-
|
|
232
|
-
|
|
715
|
+
import Vortez from 'vortez';
|
|
716
|
+
|
|
717
|
+
const server = new Vortez();
|
|
718
|
+
|
|
719
|
+
server.router.addFile('/', 'public/index.html');
|
|
720
|
+
server.router.addFolder('/assets', 'public');
|
|
721
|
+
|
|
722
|
+
await server.start();
|
|
233
723
|
```
|
|
234
724
|
|
|
235
|
-
###
|
|
725
|
+
### Full-Stack Application
|
|
236
726
|
|
|
237
|
-
|
|
727
|
+
Combine API endpoints with static frontend:
|
|
238
728
|
|
|
239
729
|
```js
|
|
240
|
-
|
|
241
|
-
|
|
730
|
+
import Vortez from 'vortez';
|
|
731
|
+
|
|
732
|
+
const server = new Vortez();
|
|
733
|
+
|
|
734
|
+
// API
|
|
735
|
+
server.router.addAction('GET', '/api/posts', getPosts);
|
|
736
|
+
server.router.addAction('POST', '/api/posts', createPost);
|
|
737
|
+
|
|
738
|
+
// Frontend
|
|
739
|
+
server.router.addFile('/', 'public/index.html');
|
|
740
|
+
server.router.addFolder('/assets', 'public');
|
|
741
|
+
|
|
742
|
+
await server.start();
|
|
242
743
|
```
|
|
243
744
|
|
|
244
|
-
###
|
|
745
|
+
### Single Page Application
|
|
245
746
|
|
|
246
|
-
|
|
747
|
+
Serve a client-side routed application shell:
|
|
247
748
|
|
|
248
749
|
```js
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
750
|
+
import Vortez from 'vortez';
|
|
751
|
+
|
|
752
|
+
const server = new Vortez();
|
|
753
|
+
|
|
754
|
+
server.router.addFile('/', 'public/app.html');
|
|
755
|
+
server.router.addFile('/app/*', 'public/app.html');
|
|
756
|
+
server.router.addFolder('/assets', 'public');
|
|
757
|
+
|
|
758
|
+
server.router.addAction('GET', '/api/status', (request, response) => {
|
|
759
|
+
response.sendJson({ status: 'ok' });
|
|
254
760
|
});
|
|
761
|
+
|
|
762
|
+
await server.start();
|
|
255
763
|
```
|
|
256
764
|
|
|
257
|
-
###
|
|
765
|
+
### Real-Time Chat Application
|
|
258
766
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
> [!NOTE]
|
|
262
|
-
> WebSocket URLs use a separate namespace from Files, Folders, and Actions,
|
|
263
|
-
> so they won’t conflict even if they share the same route patterns.
|
|
767
|
+
WebSocket-powered chat:
|
|
264
768
|
|
|
265
769
|
```js
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
server.addWebSocket('/Test/WS-Chat', (request, socket) => {
|
|
269
|
-
console.log('[WS] New connection');
|
|
270
|
-
connections.forEach(user => user.Send('A user has connected.'));
|
|
271
|
-
connections.add(socket);
|
|
770
|
+
import Vortez from 'vortez';
|
|
272
771
|
|
|
273
|
-
|
|
274
|
-
|
|
772
|
+
const server = new Vortez();
|
|
773
|
+
const clients = new Set();
|
|
275
774
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
});
|
|
282
|
-
} else if (info.opCode === 8) {
|
|
283
|
-
connections.forEach(user => user.Send('A user has disconnected.'));
|
|
284
|
-
}
|
|
775
|
+
server.router.addWebsocket('/chat', (request, socket) => {
|
|
776
|
+
clients.add(socket);
|
|
777
|
+
|
|
778
|
+
socket.on('message', (data) => {
|
|
779
|
+
clients.forEach(client => client.send(data));
|
|
285
780
|
});
|
|
781
|
+
|
|
782
|
+
socket.on('finish', () => clients.delete(socket));
|
|
286
783
|
});
|
|
784
|
+
|
|
785
|
+
server.router.addFile('/', 'public/index.html');
|
|
786
|
+
await server.start();
|
|
287
787
|
```
|
|
288
788
|
|
|
289
789
|
---
|
|
290
790
|
|
|
291
|
-
# Development
|
|
791
|
+
# Development Features
|
|
292
792
|
|
|
293
|
-
##
|
|
793
|
+
## Beta Features
|
|
294
794
|
|
|
295
|
-
The
|
|
795
|
+
The development version includes experimental functionality:
|
|
296
796
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
797
|
+
```console
|
|
798
|
+
npm install vortez@dev
|
|
799
|
+
```
|
|
300
800
|
|
|
301
|
-
|
|
801
|
+
Access beta features:
|
|
302
802
|
|
|
303
|
-
|
|
803
|
+
```js
|
|
804
|
+
import { Beta } from 'vortez';
|
|
805
|
+
const { Mail, JwtManager } = Beta;
|
|
304
806
|
|
|
305
|
-
|
|
306
|
-
|
|
807
|
+
// JWT token management
|
|
808
|
+
const jwt = new JwtManager({ alg: 'HS256', key: 'secret' });
|
|
809
|
+
const token = jwt.sign({ userId: 1 });
|
|
810
|
+
|
|
811
|
+
// Email functionality
|
|
812
|
+
const mailer = new Mail({
|
|
813
|
+
host: 'smtp.example.com',
|
|
814
|
+
port: 587,
|
|
815
|
+
username: 'user',
|
|
816
|
+
password: 'secret',
|
|
817
|
+
email: 'user@example.com',
|
|
818
|
+
useStartTLS: true
|
|
819
|
+
});
|
|
307
820
|
```
|
|
308
821
|
|
|
309
822
|
> [!WARNING]
|
|
310
|
-
>
|
|
311
|
-
> It includes the latest features that may not yet be fully tested.
|
|
823
|
+
> Beta features are experimental and may change significantly. Use with caution in production.
|
|
312
824
|
|
|
313
|
-
|
|
825
|
+
---
|
|
826
|
+
|
|
827
|
+
## Contributing
|
|
828
|
+
|
|
829
|
+
Contributions are welcome! Feel free to submit issues and pull requests on the repository.
|
|
830
|
+
|
|
831
|
+
## License
|
|
832
|
+
|
|
833
|
+
Licensed under the terms specified in the [LICENSE](LICENSE) file.
|
|
834
|
+
|
|
835
|
+
---
|
|
836
|
+
|
|
837
|
+
## Support
|
|
838
|
+
|
|
839
|
+
For questions, issues, or feature requests, please open an issue on the GitHub repository.
|
|
314
840
|
|
|
315
|
-
```js
|
|
316
|
-
import { Beta } from 'vortez';
|
|
317
|
-
const { Mail, JwtManager } = Beta;
|
|
318
|
-
```
|