@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 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