@vertz/core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +702 -0
- package/dist/index.d.ts +268 -65
- package/dist/index.js +179 -8
- package/dist/internals.d.ts +2 -2
- package/package.json +6 -3
package/README.md
ADDED
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
# @vertz/core
|
|
2
|
+
|
|
3
|
+
> Type-safe, dependency-injection-first web framework for Node.js and Bun
|
|
4
|
+
|
|
5
|
+
The core framework package for building modular web applications with built-in routing, middleware, dependency injection, and schema validation. Designed for developer experience with end-to-end type safety.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- **Node.js** 18+ or **Bun** 1.0+
|
|
10
|
+
- **TypeScript** 5.0+
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# npm
|
|
16
|
+
npm install @vertz/core
|
|
17
|
+
|
|
18
|
+
# bun
|
|
19
|
+
bun add @vertz/core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
**Note:** This example uses only `@vertz/core` with no external dependencies. For schema validation, see the [Schema Validation](#schema-validation) section below.
|
|
25
|
+
|
|
26
|
+
Create a simple API server in under 5 minutes:
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { createApp, createModuleDef, createModule } from '@vertz/core';
|
|
30
|
+
|
|
31
|
+
// 1. Define a module
|
|
32
|
+
const moduleDef = createModuleDef({ name: 'users' });
|
|
33
|
+
|
|
34
|
+
// 2. Create a router with routes
|
|
35
|
+
const router = moduleDef.router({ prefix: '/users' });
|
|
36
|
+
|
|
37
|
+
router.get('/', {
|
|
38
|
+
handler: () => ({ users: [] }),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
router.get('/:id', {
|
|
42
|
+
handler: (ctx) => ({ id: ctx.params.id }),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// 3. Create the module
|
|
46
|
+
const usersModule = createModule(moduleDef, {
|
|
47
|
+
services: [],
|
|
48
|
+
routers: [router],
|
|
49
|
+
exports: [],
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// 4. Create and start the app
|
|
53
|
+
const app = createApp({})
|
|
54
|
+
.register(usersModule);
|
|
55
|
+
|
|
56
|
+
await app.listen(3000);
|
|
57
|
+
// Server running on http://localhost:3000
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Test it:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
curl http://localhost:3000/users
|
|
64
|
+
# {"users":[]}
|
|
65
|
+
|
|
66
|
+
curl http://localhost:3000/users/42
|
|
67
|
+
# {"id":"42"}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Core Concepts
|
|
71
|
+
|
|
72
|
+
### Modules
|
|
73
|
+
|
|
74
|
+
Modules are the building blocks of a vertz application. Each module encapsulates related functionality (routes, services, business logic) and can be registered with the app.
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { createModuleDef, createModule } from '@vertz/core';
|
|
78
|
+
|
|
79
|
+
const moduleDef = createModuleDef({ name: 'products' });
|
|
80
|
+
|
|
81
|
+
// Define services, routers, and exports...
|
|
82
|
+
const productsModule = createModule(moduleDef, {
|
|
83
|
+
services: [/* service definitions */],
|
|
84
|
+
routers: [/* router definitions */],
|
|
85
|
+
exports: [/* exported services for other modules */],
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Services (Dependency Injection)
|
|
90
|
+
|
|
91
|
+
Services encapsulate business logic and can be injected into route handlers or other services.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const moduleDef = createModuleDef({ name: 'users' });
|
|
95
|
+
|
|
96
|
+
// Define a service
|
|
97
|
+
const userService = moduleDef.service({
|
|
98
|
+
methods: () => ({
|
|
99
|
+
findById: (id: string) => ({ id, name: 'Jane Doe' }),
|
|
100
|
+
create: (name: string) => ({ id: '123', name }),
|
|
101
|
+
}),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Inject service into router
|
|
105
|
+
const router = moduleDef.router({
|
|
106
|
+
prefix: '/users',
|
|
107
|
+
inject: { userService }, // ✅ Type-safe injection
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
router.get('/:id', {
|
|
111
|
+
handler: (ctx) => {
|
|
112
|
+
// ctx.userService is fully typed!
|
|
113
|
+
return ctx.userService.findById(ctx.params.id);
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const usersModule = createModule(moduleDef, {
|
|
118
|
+
services: [userService],
|
|
119
|
+
routers: [router],
|
|
120
|
+
exports: [userService], // Export for other modules
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Routing
|
|
125
|
+
|
|
126
|
+
Routers define HTTP endpoints with full type safety for params, query, headers, and body.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const router = moduleDef.router({ prefix: '/api' });
|
|
130
|
+
|
|
131
|
+
// GET /api/items
|
|
132
|
+
router.get('/items', {
|
|
133
|
+
handler: () => ({ items: [] }),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// GET /api/items/:id
|
|
137
|
+
router.get('/items/:id', {
|
|
138
|
+
handler: (ctx) => {
|
|
139
|
+
const { id } = ctx.params; // Type-safe params
|
|
140
|
+
return { id, name: 'Item' };
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// POST /api/items
|
|
145
|
+
router.post('/items', {
|
|
146
|
+
handler: (ctx) => {
|
|
147
|
+
const data = ctx.body; // Parsed request body
|
|
148
|
+
return { created: true, data };
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// All HTTP methods supported
|
|
153
|
+
router.put('/items/:id', { handler: (ctx) => ({ updated: true }) });
|
|
154
|
+
router.patch('/items/:id', { handler: (ctx) => ({ patched: true }) });
|
|
155
|
+
router.delete('/items/:id', { handler: (ctx) => ({ deleted: true }) });
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Middleware
|
|
159
|
+
|
|
160
|
+
Middleware can inject values into the request context, perform authentication, logging, etc.
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { createMiddleware, createApp } from '@vertz/core';
|
|
164
|
+
|
|
165
|
+
// Define middleware that provides user info
|
|
166
|
+
const authMiddleware = createMiddleware({
|
|
167
|
+
name: 'auth',
|
|
168
|
+
handler: (ctx) => {
|
|
169
|
+
// Validate auth token, fetch user, etc.
|
|
170
|
+
return { user: { id: '1', role: 'admin' } };
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Apply middleware globally
|
|
175
|
+
const app = createApp({})
|
|
176
|
+
.middlewares([authMiddleware])
|
|
177
|
+
.register(usersModule);
|
|
178
|
+
|
|
179
|
+
// Now all routes have access to ctx.user
|
|
180
|
+
router.get('/profile', {
|
|
181
|
+
handler: (ctx) => {
|
|
182
|
+
return { profile: ctx.user }; // Type-safe!
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Exception Handling
|
|
188
|
+
|
|
189
|
+
Built-in HTTP exceptions with proper status codes:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import {
|
|
193
|
+
NotFoundException,
|
|
194
|
+
UnauthorizedException,
|
|
195
|
+
ValidationException,
|
|
196
|
+
BadRequestException,
|
|
197
|
+
ForbiddenException,
|
|
198
|
+
InternalServerErrorException,
|
|
199
|
+
ServiceUnavailableException,
|
|
200
|
+
} from '@vertz/core';
|
|
201
|
+
|
|
202
|
+
router.get('/users/:id', {
|
|
203
|
+
handler: (ctx) => {
|
|
204
|
+
const user = findUser(ctx.params.id);
|
|
205
|
+
if (!user) {
|
|
206
|
+
throw new NotFoundException('User not found');
|
|
207
|
+
}
|
|
208
|
+
return user;
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
All exceptions are automatically converted to proper JSON responses:
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"error": "NotFoundException",
|
|
218
|
+
"message": "User not found",
|
|
219
|
+
"statusCode": 404
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Schema Validation
|
|
224
|
+
|
|
225
|
+
`@vertz/core` can be used with [@vertz/schema](../schema) for powerful request and response validation. This is **optional** — the Quick Start example above works without any validation library.
|
|
226
|
+
|
|
227
|
+
**Installation:**
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
npm install @vertz/schema
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Usage:**
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { createApp, createModuleDef, createModule } from '@vertz/core';
|
|
237
|
+
import { s } from '@vertz/schema';
|
|
238
|
+
|
|
239
|
+
const moduleDef = createModuleDef({ name: 'users' });
|
|
240
|
+
|
|
241
|
+
// Define a validation schema
|
|
242
|
+
const createUserSchema = s.object({
|
|
243
|
+
name: s.string().min(1),
|
|
244
|
+
email: s.string().email(),
|
|
245
|
+
age: s.number().int().min(18),
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const router = moduleDef.router({ prefix: '/users' });
|
|
249
|
+
|
|
250
|
+
// Use schema for request body validation
|
|
251
|
+
router.post('/', {
|
|
252
|
+
body: createUserSchema,
|
|
253
|
+
handler: (ctx) => {
|
|
254
|
+
// ctx.body is fully typed as { name: string; email: string; age: number }
|
|
255
|
+
const user = ctx.body;
|
|
256
|
+
return { created: true, user };
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const usersModule = createModule(moduleDef, {
|
|
261
|
+
services: [],
|
|
262
|
+
routers: [router],
|
|
263
|
+
exports: [],
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const app = createApp({}).register(usersModule);
|
|
267
|
+
await app.listen(3000);
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Automatic Validation:**
|
|
271
|
+
|
|
272
|
+
When a request body doesn't match the schema, a `ValidationException` is thrown automatically with a 400 status code and detailed error messages:
|
|
273
|
+
|
|
274
|
+
```json
|
|
275
|
+
{
|
|
276
|
+
"error": "ValidationException",
|
|
277
|
+
"message": "Validation failed",
|
|
278
|
+
"statusCode": 400,
|
|
279
|
+
"issues": [
|
|
280
|
+
{
|
|
281
|
+
"path": ["email"],
|
|
282
|
+
"message": "Invalid email format"
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"path": ["age"],
|
|
286
|
+
"message": "Must be at least 18"
|
|
287
|
+
}
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Learn More:**
|
|
293
|
+
|
|
294
|
+
For comprehensive documentation on schema definition, validation methods, type inference, and advanced features, see the [@vertz/schema README](../schema).
|
|
295
|
+
|
|
296
|
+
## API Reference
|
|
297
|
+
|
|
298
|
+
### `createApp(config)`
|
|
299
|
+
|
|
300
|
+
Creates a new application builder.
|
|
301
|
+
|
|
302
|
+
**Parameters:**
|
|
303
|
+
- `config: AppConfig` — App configuration
|
|
304
|
+
- `basePath?: string` — Base path prefix for all routes (e.g., `/api`)
|
|
305
|
+
- `cors?: CorsConfig` — CORS configuration
|
|
306
|
+
- `origins?: boolean | string[]` — Allow all origins (`true`) or specific origins
|
|
307
|
+
- `methods?: string[]` — Allowed HTTP methods
|
|
308
|
+
- `headers?: string[]` — Allowed headers
|
|
309
|
+
- `credentials?: boolean` — Allow credentials
|
|
310
|
+
|
|
311
|
+
**Returns:** `AppBuilder<TMiddlewareCtx>`
|
|
312
|
+
|
|
313
|
+
**Example:**
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
const app = createApp({
|
|
317
|
+
basePath: '/api',
|
|
318
|
+
cors: {
|
|
319
|
+
origins: true,
|
|
320
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
321
|
+
credentials: true,
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### `AppBuilder` Methods
|
|
327
|
+
|
|
328
|
+
#### `.register(module, options?)`
|
|
329
|
+
|
|
330
|
+
Registers a module with the app.
|
|
331
|
+
|
|
332
|
+
**Parameters:**
|
|
333
|
+
- `module: NamedModule` — The module to register
|
|
334
|
+
- `options?: Record<string, unknown>` — Module-specific options (available via `ctx.options`)
|
|
335
|
+
|
|
336
|
+
**Returns:** `AppBuilder` (chainable)
|
|
337
|
+
|
|
338
|
+
**Example:**
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
app.register(usersModule, { maxRetries: 3 });
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
#### `.middlewares(list)`
|
|
345
|
+
|
|
346
|
+
Registers global middleware that runs before all route handlers.
|
|
347
|
+
|
|
348
|
+
**Parameters:**
|
|
349
|
+
- `list: NamedMiddlewareDef[]` — Array of middleware definitions
|
|
350
|
+
|
|
351
|
+
**Returns:** `AppBuilder` (chainable)
|
|
352
|
+
|
|
353
|
+
**Example:**
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
app.middlewares([authMiddleware, loggingMiddleware]);
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
#### `.handler`
|
|
360
|
+
|
|
361
|
+
The request handler function. Use this with custom server adapters or testing.
|
|
362
|
+
|
|
363
|
+
**Type:** `(request: Request) => Promise<Response>`
|
|
364
|
+
|
|
365
|
+
**Example:**
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
const response = await app.handler(new Request('http://localhost/users'));
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
#### `.listen(port?, options?)`
|
|
372
|
+
|
|
373
|
+
Starts the HTTP server.
|
|
374
|
+
|
|
375
|
+
**Parameters:**
|
|
376
|
+
- `port?: number` — Port to listen on (default: 3000)
|
|
377
|
+
- `options?: ListenOptions`
|
|
378
|
+
- `logRoutes?: boolean` — Log registered routes on startup (default: `true`)
|
|
379
|
+
|
|
380
|
+
**Returns:** `Promise<ServerHandle>`
|
|
381
|
+
|
|
382
|
+
**Example:**
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
const server = await app.listen(3000, { logRoutes: true });
|
|
386
|
+
console.log(`Server running on http://${server.hostname}:${server.port}`);
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### `createModuleDef(config)`
|
|
390
|
+
|
|
391
|
+
Creates a module definition — the factory for services and routers.
|
|
392
|
+
|
|
393
|
+
**Parameters:**
|
|
394
|
+
- `config: { name: string }` — Module name (must be unique)
|
|
395
|
+
|
|
396
|
+
**Returns:** Module definition builder
|
|
397
|
+
|
|
398
|
+
**Example:**
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
const moduleDef = createModuleDef({ name: 'products' });
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Module Definition Methods
|
|
405
|
+
|
|
406
|
+
#### `.service(config)`
|
|
407
|
+
|
|
408
|
+
Defines a service with optional dependencies, state, and methods.
|
|
409
|
+
|
|
410
|
+
**Parameters:**
|
|
411
|
+
- `config: ServiceConfig`
|
|
412
|
+
- `methods: (deps, state) => TMethods` — Factory function that returns service methods
|
|
413
|
+
|
|
414
|
+
**Returns:** `NamedServiceDef`
|
|
415
|
+
|
|
416
|
+
**Example:**
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
const productService = moduleDef.service({
|
|
420
|
+
methods: () => ({
|
|
421
|
+
findAll: () => [{ id: '1', name: 'Product 1' }],
|
|
422
|
+
findById: (id: string) => ({ id, name: 'Product' }),
|
|
423
|
+
}),
|
|
424
|
+
});
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
#### `.router(config)`
|
|
428
|
+
|
|
429
|
+
Defines a router with routes.
|
|
430
|
+
|
|
431
|
+
**Parameters:**
|
|
432
|
+
- `config: RouterConfig`
|
|
433
|
+
- `prefix: string` — URL prefix for all routes in this router (e.g., `/users`)
|
|
434
|
+
- `inject?: Record<string, NamedServiceDef>` — Services to inject into route handlers
|
|
435
|
+
|
|
436
|
+
**Returns:** Router builder with HTTP method functions
|
|
437
|
+
|
|
438
|
+
**Example:**
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
const router = moduleDef.router({
|
|
442
|
+
prefix: '/products',
|
|
443
|
+
inject: { productService },
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
router.get('/', { handler: (ctx) => ctx.productService.findAll() });
|
|
447
|
+
router.get('/:id', { handler: (ctx) => ctx.productService.findById(ctx.params.id) });
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Router Methods
|
|
451
|
+
|
|
452
|
+
All routers expose these HTTP method functions:
|
|
453
|
+
|
|
454
|
+
- `get(path, config)`
|
|
455
|
+
- `post(path, config)`
|
|
456
|
+
- `put(path, config)`
|
|
457
|
+
- `patch(path, config)`
|
|
458
|
+
- `delete(path, config)`
|
|
459
|
+
- `head(path, config)`
|
|
460
|
+
|
|
461
|
+
**Route Config:**
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
interface RouteConfig {
|
|
465
|
+
params?: SchemaType; // Schema for URL params
|
|
466
|
+
query?: SchemaType; // Schema for query string
|
|
467
|
+
headers?: SchemaType; // Schema for headers
|
|
468
|
+
body?: SchemaType; // Schema for request body
|
|
469
|
+
response?: SchemaType; // Schema for response (documentation)
|
|
470
|
+
handler: (ctx) => unknown; // Route handler
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### `createModule(moduleDef, config)`
|
|
475
|
+
|
|
476
|
+
Creates a concrete module instance from a module definition.
|
|
477
|
+
|
|
478
|
+
**Parameters:**
|
|
479
|
+
- `moduleDef: NamedModuleDef` — Module definition
|
|
480
|
+
- `config: ModuleConfig`
|
|
481
|
+
- `services: NamedServiceDef[]` — List of services in this module
|
|
482
|
+
- `routers: NamedRouterDef[]` — List of routers in this module
|
|
483
|
+
- `exports: NamedServiceDef[]` — Services to export for other modules (subset of `services`)
|
|
484
|
+
|
|
485
|
+
**Returns:** `NamedModule`
|
|
486
|
+
|
|
487
|
+
**Example:**
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
const usersModule = createModule(moduleDef, {
|
|
491
|
+
services: [userService, authService],
|
|
492
|
+
routers: [userRouter, authRouter],
|
|
493
|
+
exports: [userService], // Only userService is accessible to other modules
|
|
494
|
+
});
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### `createMiddleware(config)`
|
|
498
|
+
|
|
499
|
+
Creates reusable middleware.
|
|
500
|
+
|
|
501
|
+
**Parameters:**
|
|
502
|
+
- `config: MiddlewareConfig`
|
|
503
|
+
- `name: string` — Middleware name
|
|
504
|
+
- `handler: (ctx) => TProvides` — Middleware handler that returns context contributions
|
|
505
|
+
|
|
506
|
+
**Returns:** `NamedMiddlewareDef<TRequires, TProvides>`
|
|
507
|
+
|
|
508
|
+
**Example:**
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
const loggingMiddleware = createMiddleware({
|
|
512
|
+
name: 'logging',
|
|
513
|
+
handler: (ctx) => {
|
|
514
|
+
console.log(`${ctx.method} ${ctx.path}`);
|
|
515
|
+
return {}; // No context contributions
|
|
516
|
+
},
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
const authMiddleware = createMiddleware({
|
|
520
|
+
name: 'auth',
|
|
521
|
+
handler: (ctx) => {
|
|
522
|
+
const token = ctx.headers.get('authorization');
|
|
523
|
+
const user = validateToken(token);
|
|
524
|
+
if (!user) throw new UnauthorizedException('Invalid token');
|
|
525
|
+
return { user }; // Adds `user` to context
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Handler Context (`ctx`)
|
|
531
|
+
|
|
532
|
+
Every route handler receives a context object with:
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
interface HandlerCtx {
|
|
536
|
+
// Request properties
|
|
537
|
+
method: string; // HTTP method (GET, POST, etc.)
|
|
538
|
+
path: string; // Request path
|
|
539
|
+
params: Record<string, string>; // URL params
|
|
540
|
+
query: Record<string, unknown>; // Query string (parsed)
|
|
541
|
+
headers: Headers; // Request headers
|
|
542
|
+
body: unknown; // Parsed request body (JSON)
|
|
543
|
+
request: Request; // Raw Request object
|
|
544
|
+
|
|
545
|
+
// Module context
|
|
546
|
+
options: Record<string, unknown>; // Module registration options
|
|
547
|
+
|
|
548
|
+
// Injected services (from router inject)
|
|
549
|
+
// ... (typed based on inject config)
|
|
550
|
+
|
|
551
|
+
// Middleware contributions (from global/route middlewares)
|
|
552
|
+
// ... (typed based on middleware chain)
|
|
553
|
+
}
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
## Configuration
|
|
557
|
+
|
|
558
|
+
### App Configuration
|
|
559
|
+
|
|
560
|
+
```typescript
|
|
561
|
+
interface AppConfig {
|
|
562
|
+
basePath?: string; // Global path prefix (e.g., "/api")
|
|
563
|
+
cors?: CorsConfig; // CORS settings
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### CORS Configuration
|
|
568
|
+
|
|
569
|
+
```typescript
|
|
570
|
+
interface CorsConfig {
|
|
571
|
+
origins?: boolean | string[]; // true = allow all, or array of allowed origins
|
|
572
|
+
methods?: string[]; // Allowed HTTP methods
|
|
573
|
+
headers?: string[]; // Allowed headers
|
|
574
|
+
credentials?: boolean; // Allow credentials
|
|
575
|
+
maxAge?: number; // Preflight cache duration (seconds)
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
## Related Packages
|
|
580
|
+
|
|
581
|
+
- **[@vertz/schema](../schema)** — Type-safe schema definition and validation (`s.string()`, `s.object()`, etc.)
|
|
582
|
+
- **[@vertz/testing](../testing)** — Testing utilities for vertz apps (`createTestApp`, `createTestService`)
|
|
583
|
+
- **[@vertz/cli](../cli)** — CLI framework for building command-line tools
|
|
584
|
+
- **[@vertz/compiler](../compiler)** — Static analysis and code generation
|
|
585
|
+
- **[@vertz/db](../db)** — Type-safe database ORM with migrations
|
|
586
|
+
- **[@vertz/fetch](../fetch)** — Type-safe HTTP client with retry and streaming support
|
|
587
|
+
- **[@vertz/ui](../ui)** — Reactive UI framework for vertz apps
|
|
588
|
+
|
|
589
|
+
## Examples
|
|
590
|
+
|
|
591
|
+
See the [examples/](./examples) directory for complete working examples:
|
|
592
|
+
|
|
593
|
+
- **[basic-api](./examples/basic-api)** — Simple REST API with CRUD operations
|
|
594
|
+
- (More examples coming soon)
|
|
595
|
+
|
|
596
|
+
## Advanced Topics
|
|
597
|
+
|
|
598
|
+
### Multiple Modules
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
const usersModule = createModule(/* ... */);
|
|
602
|
+
const productsModule = createModule(/* ... */);
|
|
603
|
+
const ordersModule = createModule(/* ... */);
|
|
604
|
+
|
|
605
|
+
const app = createApp({})
|
|
606
|
+
.register(usersModule)
|
|
607
|
+
.register(productsModule)
|
|
608
|
+
.register(ordersModule);
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### Module Options
|
|
612
|
+
|
|
613
|
+
Pass configuration to modules at registration time:
|
|
614
|
+
|
|
615
|
+
```typescript
|
|
616
|
+
const emailModule = createModule(/* ... */);
|
|
617
|
+
|
|
618
|
+
app.register(emailModule, {
|
|
619
|
+
smtpHost: 'smtp.example.com',
|
|
620
|
+
smtpPort: 587,
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
// Access in route handlers:
|
|
624
|
+
router.post('/send', {
|
|
625
|
+
handler: (ctx) => {
|
|
626
|
+
const { smtpHost, smtpPort } = ctx.options;
|
|
627
|
+
// Use config...
|
|
628
|
+
},
|
|
629
|
+
});
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### Custom Server Adapters
|
|
633
|
+
|
|
634
|
+
Use the `.handler` property to integrate with custom servers:
|
|
635
|
+
|
|
636
|
+
```typescript
|
|
637
|
+
import { serve } from 'bun';
|
|
638
|
+
|
|
639
|
+
const app = createApp({}).register(usersModule);
|
|
640
|
+
|
|
641
|
+
serve({
|
|
642
|
+
port: 3000,
|
|
643
|
+
fetch: app.handler,
|
|
644
|
+
});
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
### Development vs Production
|
|
648
|
+
|
|
649
|
+
The framework automatically provides immutable context objects in development mode to catch mutation bugs early.
|
|
650
|
+
|
|
651
|
+
```typescript
|
|
652
|
+
// In development (NODE_ENV=development):
|
|
653
|
+
router.get('/users', {
|
|
654
|
+
handler: (ctx) => {
|
|
655
|
+
ctx.params = {}; // ❌ Throws error — cannot mutate frozen object
|
|
656
|
+
},
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
// In production: no immutability checks for performance
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
## TypeScript Support
|
|
663
|
+
|
|
664
|
+
All APIs are fully typed with generics for end-to-end type safety:
|
|
665
|
+
|
|
666
|
+
```typescript
|
|
667
|
+
import { s } from '@vertz/schema';
|
|
668
|
+
|
|
669
|
+
const userSchema = s.object({
|
|
670
|
+
name: s.string(),
|
|
671
|
+
email: s.string().email(),
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
router.post('/users', {
|
|
675
|
+
body: userSchema,
|
|
676
|
+
handler: (ctx) => {
|
|
677
|
+
// ctx.body is typed as { name: string; email: string }
|
|
678
|
+
const { name, email } = ctx.body; // ✅ Autocomplete works!
|
|
679
|
+
return { created: true };
|
|
680
|
+
},
|
|
681
|
+
});
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
Service injection is also fully typed:
|
|
685
|
+
|
|
686
|
+
```typescript
|
|
687
|
+
const router = moduleDef.router({
|
|
688
|
+
prefix: '/users',
|
|
689
|
+
inject: { userService, authService },
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
router.get('/', {
|
|
693
|
+
handler: (ctx) => {
|
|
694
|
+
// ctx.userService and ctx.authService are fully typed
|
|
695
|
+
ctx.userService.findAll(); // ✅ Autocomplete shows all methods
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
## License
|
|
701
|
+
|
|
702
|
+
MIT
|