blaizejs 0.7.1 → 0.9.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.
Files changed (32) hide show
  1. package/README.md +1021 -476
  2. package/dist/chunk-6K6GZLDJ.js +11 -0
  3. package/dist/chunk-6K6GZLDJ.js.map +1 -0
  4. package/dist/chunk-DCZPGNAM.js +11 -0
  5. package/dist/chunk-LL6TU2VN.js +11 -0
  6. package/dist/chunk-PT3XLVQL.js +11 -0
  7. package/dist/chunk-VNJJ5PCH.js +11 -0
  8. package/dist/index.cjs +18 -17
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +3616 -1366
  11. package/dist/index.d.ts +3616 -1366
  12. package/dist/index.js +18 -17
  13. package/dist/index.js.map +1 -1
  14. package/dist/{internal-server-error-3NM7IWUS.js → internal-server-error-SUR4JYIM.js} +4 -4
  15. package/dist/{payload-too-large-error-WI42VGZU.js → payload-too-large-error-V5D4UEOE.js} +4 -4
  16. package/dist/{unsupported-media-type-error-TA5IO6FC.js → unsupported-media-type-error-Q2DH3CAM.js} +4 -4
  17. package/dist/{validation-error-NO2FE2IP.js → validation-error-QMX5LAON.js} +4 -4
  18. package/package.json +2 -2
  19. package/dist/chunk-DCFSCRSA.js +0 -11
  20. package/dist/chunk-EAXYRQ3P.js +0 -11
  21. package/dist/chunk-FCV6YEV5.js +0 -11
  22. package/dist/chunk-FZ7VQAU3.js +0 -11
  23. package/dist/chunk-FZ7VQAU3.js.map +0 -1
  24. package/dist/chunk-O2UQAGER.js +0 -11
  25. /package/dist/{chunk-FCV6YEV5.js.map → chunk-DCZPGNAM.js.map} +0 -0
  26. /package/dist/{chunk-DCFSCRSA.js.map → chunk-LL6TU2VN.js.map} +0 -0
  27. /package/dist/{chunk-O2UQAGER.js.map → chunk-PT3XLVQL.js.map} +0 -0
  28. /package/dist/{chunk-EAXYRQ3P.js.map → chunk-VNJJ5PCH.js.map} +0 -0
  29. /package/dist/{internal-server-error-3NM7IWUS.js.map → internal-server-error-SUR4JYIM.js.map} +0 -0
  30. /package/dist/{payload-too-large-error-WI42VGZU.js.map → payload-too-large-error-V5D4UEOE.js.map} +0 -0
  31. /package/dist/{unsupported-media-type-error-TA5IO6FC.js.map → unsupported-media-type-error-Q2DH3CAM.js.map} +0 -0
  32. /package/dist/{validation-error-NO2FE2IP.js.map → validation-error-QMX5LAON.js.map} +0 -0
package/README.md CHANGED
@@ -1,672 +1,1217 @@
1
1
  # 🔥 BlaizeJS Core
2
2
 
3
- > **Type-safe, blazing-fast Node.js framework** with HTTP/2 support, file-based routing, powerful middleware system, and end-to-end type safety for building modern APIs
3
+ > The core BlaizeJS framework type-safe APIs with file-based routing
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/blaizejs.svg)](https://badge.fury.io/js/blaizejs)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
8
+ [![Node.js](https://img.shields.io/badge/Node.js-23.0+-green.svg)](https://nodejs.org/)
9
+ [![Build Status](https://github.com/jleajones/blaize/workflows/Test/badge.svg)](https://github.com/jleajones/blaize/actions)
8
10
 
9
- ## 📋 Table of Contents
11
+ The `blaizejs` package is the core framework providing servers, routing, middleware, plugins, and error handling with end-to-end type safety.
10
12
 
11
- - [🌟 Features](#-features)
12
- - [📦 Installation](#-installation)
13
- - [🚀 Quick Start](#-quick-start)
14
- - [📖 Core Modules](#-core-modules)
15
- - [🛡️ Error Handling](#️-error-handling)
16
- - [🎯 API Reference](#-api-reference)
17
- - [💡 Common Patterns](#-common-patterns)
18
- - [🧪 Testing](#-testing)
19
- - [📚 Type System](#-type-system)
20
- - [🗺️ Roadmap](#️-roadmap)
21
- - [🤝 Contributing](#-contributing)
22
-
23
- ## 🌟 Features
24
-
25
- - 🚀 **HTTP/2 by Default** - Modern protocol with automatic HTTPS in development
26
- - 📁 **File-Based Routing** - Routes auto-discovered from file structure *(internal)*
27
- - 🔧 **Composable Middleware** - Build reusable request/response pipelines
28
- - 🧩 **Plugin System** - Extend server functionality with lifecycle hooks
29
- - ✅ **Schema Validation** - Built-in Zod validation for type safety
30
- - 🛡️ **Semantic Errors** - Rich error classes with automatic formatting
31
- - 🔗 **Context Management** - AsyncLocalStorage-powered state isolation *(internal)*
32
- - ⚡ **Zero Configuration** - Works out of the box with sensible defaults
33
- - 📊 **Type Inference** - Full TypeScript support with automatic types
34
- - 🔄 **Hot Reloading** - Development mode with automatic route updates
13
+ ---
35
14
 
36
15
  ## 📦 Installation
37
16
 
17
+ ### Recommended: Create a New Project
18
+
38
19
  ```bash
39
20
  # Using pnpm (recommended)
40
- pnpm add blaizejs
21
+ pnpm dlx create-blaize-app my-app
41
22
 
42
23
  # Using npm
43
- npm install blaizejs
24
+ npx create-blaize-app my-app
44
25
 
45
26
  # Using yarn
46
- yarn add blaizejs
27
+ yarn dlx create-blaize-app my-app
47
28
  ```
48
29
 
49
- ## 🚀 Quick Start
30
+ This sets up a fully configured project with TypeScript, file-based routing, and example routes.
31
+
32
+ ### Manual Installation
33
+
34
+ Add BlaizeJS to an existing project:
35
+
36
+ ```bash
37
+ # Using pnpm
38
+ pnpm add blaizejs zod
39
+
40
+ # Using npm
41
+ npm install blaizejs zod
42
+
43
+ # Using yarn
44
+ yarn add blaizejs zod
45
+ ```
46
+
47
+ ---
50
48
 
51
- ### Creating Your First Server
49
+ ## 🚀 Quick Start
52
50
 
53
51
  ```typescript
54
- import { createServer, createGetRoute, createPostRoute } from 'blaizejs';
52
+ // src/app.ts
53
+ import { Blaize, type InferContext } from 'blaizejs';
55
54
  import { fileURLToPath } from 'node:url';
56
55
  import path from 'node:path';
57
- import { z } from 'zod';
58
56
 
59
- // ESM path resolution (required for route discovery)
60
57
  const __filename = fileURLToPath(import.meta.url);
61
58
  const __dirname = path.dirname(__filename);
62
59
 
63
- // Create server with file-based routing
60
+ const app = Blaize.createServer({
61
+ port: 3000,
62
+ routesDir: path.resolve(__dirname, './routes'),
63
+ });
64
+
65
+ // Create a typed route factory for use in route files
66
+ type AppContext = InferContext;
67
+ export const route = Blaize.Router.createRouteFactory<
68
+ AppContext['state'],
69
+ AppContext['services']
70
+ >();
71
+
72
+ await app.listen();
73
+ console.log('🔥 Server running at https://localhost:3000');
74
+ ```
75
+
76
+ ```typescript
77
+ // src/routes/hello.ts
78
+ import { route } from '../app';
79
+ import { z } from 'zod';
80
+
81
+ // Named export — the name becomes the client method name
82
+ export const getHello = route.get({
83
+ schema: {
84
+ response: z.object({ message: z.string() }),
85
+ },
86
+ handler: async () => ({ message: 'Hello, BlaizeJS!' }),
87
+ });
88
+ ```
89
+
90
+ ```typescript
91
+ // src/app-type.ts — Export routes registry for the client
92
+ import { getHello } from './routes/hello';
93
+
94
+ export const routes = { getHello } as const;
95
+ ```
96
+
97
+ ---
98
+
99
+ ## 📋 Table of Contents
100
+
101
+ - [Server](#-server)
102
+ - [Route Creators](#-route-creators)
103
+ - [Middleware](#-middleware)
104
+ - [Plugins](#-plugins)
105
+ - [Error Classes](#-error-classes)
106
+ - [Logging](#-logging)
107
+ - [Utilities](#-utilities)
108
+ - [Context Reference](#-context-reference)
109
+ - [Testing](#-testing)
110
+ - [Roadmap](#-roadmap)
111
+
112
+ ---
113
+
114
+ ## 🖥️ Server
115
+
116
+ ### createServer
117
+
118
+ Creates and configures a BlaizeJS server instance.
119
+
120
+ ```typescript
121
+ import { createServer } from 'blaizejs';
122
+
123
+ const server = createServer(options?: ServerOptions);
124
+ ```
125
+
126
+ #### Options
127
+
128
+ | Option | Type | Default | Description |
129
+ |--------|------|---------|-------------|
130
+ | `port` | `number` | `3000` | Port to listen on |
131
+ | `host` | `string` | `'localhost'` | Host to bind to |
132
+ | `routesDir` | `string` | — | Directory for file-based route discovery |
133
+ | `middleware` | `Middleware[]` | `[]` | Global middleware (runs for all routes) |
134
+ | `plugins` | `Plugin[]` | `[]` | Plugins to register |
135
+ | `http2` | `boolean` | `true` | Enable HTTP/2 (with HTTP/1.1 fallback) |
136
+ | `bodyLimits` | `object` | See below | Request body size limits |
137
+
138
+ **Body Limits:**
139
+
140
+ | Property | Type | Default | Description |
141
+ |----------|------|---------|-------------|
142
+ | `bodyLimits.json` | `number` | `1048576` (1MB) | Max JSON body size in bytes |
143
+ | `bodyLimits.form` | `number` | `1048576` (1MB) | Max form body size in bytes |
144
+ | `bodyLimits.text` | `number` | `1048576` (1MB) | Max text body size in bytes |
145
+
146
+ #### Server Instance
147
+
148
+ The returned server instance provides:
149
+
150
+ | Method/Property | Type | Description |
151
+ |-----------------|------|-------------|
152
+ | `listen(port?, host?)` | `Promise<Server>` | Start the server |
153
+ | `close(options?)` | `Promise<void>` | Stop the server gracefully |
154
+ | `use(middleware)` | `Server` | Add middleware (chainable) |
155
+ | `register(plugin)` | `Promise<Server>` | Register a plugin |
156
+ | `port` | `number` (readonly) | Current port |
157
+ | `host` | `string` (readonly) | Current host |
158
+ | `middleware` | `Middleware[]` (readonly) | Registered middleware |
159
+
160
+ #### Examples
161
+
162
+ **Basic Setup:**
163
+
164
+ ```typescript
165
+ import { createServer } from 'blaizejs';
166
+
64
167
  const server = createServer({
65
168
  port: 3000,
66
- host: 'localhost',
67
- routesDir: path.resolve(__dirname, './routes')
169
+ routesDir: './src/routes',
68
170
  });
69
171
 
70
172
  await server.listen();
71
- console.log(`🚀 Server running at https://localhost:3000`);
72
173
  ```
73
174
 
74
- ### Creating Routes
175
+ **With Middleware:**
75
176
 
76
- Create route files in your routes directory:
177
+ ```typescript
178
+ import { createServer, createMiddleware } from 'blaizejs';
179
+
180
+ const logger = createMiddleware({
181
+ name: 'logger',
182
+ handler: async (ctx, next) => {
183
+ console.log(`→ ${ctx.request.method} ${ctx.request.path}`);
184
+ await next();
185
+ },
186
+ });
187
+
188
+ const server = createServer({
189
+ middleware: [logger],
190
+ routesDir: './src/routes',
191
+ });
192
+ ```
193
+
194
+ **With Plugins:**
195
+
196
+ ```typescript
197
+ import { createServer } from 'blaizejs';
198
+ import { createCachePlugin } from '@blaizejs/plugin-cache';
199
+ import { createMetricsPlugin } from '@blaizejs/plugin-metrics';
200
+
201
+ const server = createServer({
202
+ plugins: [
203
+ createCachePlugin({ defaultTtl: 3600 }),
204
+ createMetricsPlugin({ enabled: true }),
205
+ ],
206
+ routesDir: './src/routes',
207
+ });
208
+ ```
209
+
210
+ **Custom Body Limits:**
77
211
 
78
212
  ```typescript
79
- // routes/users/[userId].ts
80
- import { createGetRoute, createPutRoute, NotFoundError } from 'blaizejs';
213
+ const server = createServer({
214
+ bodyLimits: {
215
+ json: 10 * 1024 * 1024, // 10MB for JSON
216
+ form: 50 * 1024 * 1024, // 50MB for forms (file uploads)
217
+ text: 1 * 1024 * 1024, // 1MB for text
218
+ },
219
+ routesDir: './src/routes',
220
+ });
221
+ ```
222
+
223
+ ---
224
+
225
+ ## 📂 Route Creators
226
+
227
+ BlaizeJS provides two approaches to creating routes:
228
+
229
+ 1. **Route Factory (Recommended)** — Create a typed router that shares context types across all routes
230
+ 2. **Individual Route Creators** — Lower-level functions for specific use cases
231
+
232
+ ### createRouteFactory (Recommended)
233
+
234
+ The route factory pattern provides automatic type inference from your server's middleware and plugins.
235
+
236
+ ```typescript
237
+ // src/app.ts
238
+ import { Blaize, type InferContext } from 'blaizejs';
239
+
240
+ // 1. Create your server with middleware and plugins
241
+ const app = Blaize.createServer({
242
+ port: 3000,
243
+ routesDir: './src/routes',
244
+ middleware: [authMiddleware],
245
+ plugins: [databasePlugin()],
246
+ });
247
+
248
+ // 2. Infer context types from the server
249
+ type AppContext = InferContext;
250
+
251
+ // 3. Create a typed route factory
252
+ export const route = Blaize.Router.createRouteFactory<
253
+ AppContext['state'], // Types from middleware (e.g., { user: User })
254
+ AppContext['services'] // Types from plugins (e.g., { db: Database })
255
+ >();
256
+
257
+ await app.listen();
258
+ ```
259
+
260
+ The route factory provides methods for all HTTP verbs:
261
+
262
+ | Method | HTTP Verb | Has Body |
263
+ |--------|-----------|----------|
264
+ | `route.get()` | GET | No |
265
+ | `route.post()` | POST | Yes |
266
+ | `route.put()` | PUT | Yes |
267
+ | `route.patch()` | PATCH | Yes |
268
+ | `route.delete()` | DELETE | No |
269
+ | `route.head()` | HEAD | No |
270
+ | `route.options()` | OPTIONS | No |
271
+ | `route.sse()` | GET (SSE) | No |
272
+
273
+ #### Using the Route Factory
274
+
275
+ ```typescript
276
+ // src/routes/users/index.ts
277
+ import { route } from '../../app';
81
278
  import { z } from 'zod';
82
279
 
83
- // GET /users/:userId
84
- export const GET = createGetRoute({
280
+ // Named exports — these names become the client method names
281
+ export const listUsers = route.get({
85
282
  schema: {
86
- params: z.object({
87
- userId: z.string().uuid()
88
- }),
89
- response: z.object({
90
- id: z.string(),
91
- name: z.string(),
92
- email: z.string()
93
- })
283
+ query: z.object({ limit: z.coerce.number().default(10) }),
284
+ response: z.array(userSchema),
285
+ },
286
+ handler: async (ctx) => {
287
+ // ctx.state.user is typed from authMiddleware!
288
+ // ctx.services.db is typed from databasePlugin!
289
+ return await ctx.services.db.users.findMany({
290
+ take: ctx.request.query.limit,
291
+ });
94
292
  },
95
- handler: async (ctx, params) => {
96
- const user = await db.users.findById(params.userId);
97
-
98
- if (!user) {
99
- throw new NotFoundError('User not found', {
100
- resourceType: 'user',
101
- resourceId: params.userId
102
- });
103
- }
104
-
105
- return user;
106
- }
107
293
  });
108
294
 
109
- // PUT /users/:userId
110
- export const PUT = createPutRoute({
295
+ export const createUser = route.post({
111
296
  schema: {
112
- params: z.object({
113
- userId: z.string().uuid()
114
- }),
115
297
  body: z.object({
116
298
  name: z.string().min(1),
117
- email: z.string().email()
118
- })
299
+ email: z.string().email(),
300
+ }),
301
+ response: userSchema,
302
+ },
303
+ handler: async (ctx) => {
304
+ return await ctx.services.db.users.create(ctx.request.body);
119
305
  },
120
- handler: async (ctx, params) => {
121
- const updatedUser = await db.users.update(params.userId, ctx.body);
122
- return updatedUser;
123
- }
124
306
  });
125
307
  ```
126
308
 
127
- ## 📖 Core Modules
309
+ ```typescript
310
+ // src/routes/users/[userId].ts
311
+ import { route } from '../../app';
312
+ import { z } from 'zod';
313
+ import { NotFoundError, ForbiddenError } from 'blaizejs';
128
314
 
129
- BlaizeJS Core consists of several integrated modules. Some are exported for direct use, while others work internally:
315
+ export const getUser = route.get({
316
+ schema: {
317
+ params: z.object({ userId: z.string().uuid() }),
318
+ response: userSchema,
319
+ },
320
+ handler: async (ctx, params) => {
321
+ const user = await ctx.services.db.users.findById(params.userId);
322
+ if (!user) throw new NotFoundError('User not found');
323
+ return user;
324
+ },
325
+ });
130
326
 
131
- ### 🌐 Server Module *(Exported)*
327
+ export const updateUser = route.put({
328
+ schema: {
329
+ params: z.object({ userId: z.string() }),
330
+ body: updateUserSchema,
331
+ response: userSchema,
332
+ },
333
+ handler: async (ctx, params) => {
334
+ // Check authorization using typed state
335
+ if (ctx.state.user?.id !== params.userId && ctx.state.user?.role !== 'admin') {
336
+ throw new ForbiddenError('Cannot update other users');
337
+ }
338
+ return await ctx.services.db.users.update(params.userId, ctx.request.body);
339
+ },
340
+ });
132
341
 
133
- Create HTTP/2 servers with automatic HTTPS, middleware, and plugins:
342
+ export const deleteUser = route.delete({
343
+ schema: {
344
+ params: z.object({ userId: z.string() }),
345
+ },
346
+ handler: async (ctx, params) => {
347
+ await ctx.services.db.users.delete(params.userId);
348
+ ctx.response.status(204);
349
+ },
350
+ });
351
+ ```
134
352
 
135
353
  ```typescript
136
- import { createServer, createMiddleware, createPlugin } from 'blaizejs';
354
+ // src/app-type.ts Export routes registry for the client
355
+ import { listUsers, createUser } from './routes/users';
356
+ import { getUser, updateUser, deleteUser } from './routes/users/[userId]';
357
+
358
+ export const routes = {
359
+ listUsers,
360
+ createUser,
361
+ getUser,
362
+ updateUser,
363
+ deleteUser,
364
+ } as const;
365
+ ```
137
366
 
138
- const server = createServer({
139
- port: 3000,
140
- routesDir: './routes',
141
- http2: { enabled: true }, // Auto-generates dev certificates
142
- middleware: [loggingMiddleware],
143
- plugins: [metricsPlugin()]
367
+ #### SSE Routes with the Factory
368
+
369
+ ```typescript
370
+ // src/routes/jobs/[jobId]/stream.ts
371
+ import { route } from '../../../app';
372
+ import { z } from 'zod';
373
+
374
+ export const getJobStream = route.sse({
375
+ schema: {
376
+ params: z.object({ jobId: z.string().uuid() }),
377
+ events: {
378
+ progress: z.object({
379
+ percent: z.number().min(0).max(100),
380
+ message: z.string().optional(),
381
+ }),
382
+ complete: z.object({ result: z.unknown() }),
383
+ error: z.object({ code: z.string(), message: z.string() }),
384
+ },
385
+ },
386
+ handler: async (stream, ctx, params, logger) => {
387
+ const unsubscribe = ctx.services.queue.subscribe(params.jobId, {
388
+ onProgress: (percent, message) => stream.send('progress', { percent, message }),
389
+ onComplete: (result) => stream.send('complete', { result }),
390
+ onError: (code, message) => stream.send('error', { code, message }),
391
+ });
392
+
393
+ stream.onClose(() => {
394
+ unsubscribe();
395
+ logger.info('Client disconnected');
396
+ });
397
+ },
144
398
  });
145
399
 
146
- // Add middleware after creation
147
- server.use(corsMiddleware);
400
+ // Client usage:
401
+ // const stream = await client.$sse.getJobStream({ params: { jobId: '123' } });
402
+ // stream.on('progress', (data) => console.log(data.percent));
403
+ ```
148
404
 
149
- // Register plugins dynamically
150
- await server.register(databasePlugin());
405
+ **SSE Handler Signature:**
151
406
 
152
- await server.listen();
407
+ ```typescript
408
+ handler: (
409
+ stream: TypedSSEStream, // Send typed events
410
+ ctx: Context, // Request context
411
+ params: TParams, // Validated parameters
412
+ logger: BlaizeLogger // Request-scoped logger
413
+ ) => Promise
153
414
  ```
154
415
 
155
- ### 🚀 Router Module *(Partially Exported)*
416
+ **TypedSSEStream Methods:**
417
+
418
+ | Method | Description |
419
+ |--------|-------------|
420
+ | `send(event, data)` | Send a typed event to the client |
421
+ | `close()` | Close the SSE connection |
156
422
 
157
- **⚠️ Note**: The router itself is internal. Only route creation functions are exported.
423
+ ---
158
424
 
159
- #### Available Exports:
160
- - ✅ `createGetRoute` - Create GET endpoints
161
- - ✅ `createPostRoute` - Create POST endpoints
162
- - ✅ `createPutRoute` - Create PUT endpoints
163
- - ✅ `createPatchRoute` - Create PATCH endpoints
164
- - ✅ `createDeleteRoute` - Create DELETE endpoints
165
- - ✅ `createHeadRoute` - Create HEAD endpoints
166
- - ✅ `createOptionsRoute` - Create OPTIONS endpoints
425
+ ### Individual Route Creators
167
426
 
168
- #### Internal (Not Exported):
169
- - ❌ `Router` interface - Used internally by server
170
- - `Matcher` - Internal route matching
171
- - ❌ `extractParams` - Internal parameter extraction
172
- - ❌ Route discovery utilities - Internal file system operations
427
+ For cases where you don't need shared context types, or when building custom abstractions, you can use the individual route creator functions directly.
428
+
429
+ > **Note:** These are higher-order functions (they return functions). The route factory pattern above is recommended for most applications.
173
430
 
174
431
  ```typescript
175
- // ✅ This is how you use routing:
176
- export const GET = createGetRoute({
177
- schema: { /* ... */ },
178
- handler: async (ctx) => { /* ... */ }
179
- });
432
+ import {
433
+ createGetRoute,
434
+ createPostRoute,
435
+ createPutRoute,
436
+ createPatchRoute,
437
+ createDeleteRoute,
438
+ createHeadRoute,
439
+ createOptionsRoute,
440
+ createSSERoute,
441
+ } from 'blaizejs';
442
+ ```
443
+
444
+ #### Example: Using Individual Route Creators
445
+
446
+ ```typescript
447
+ import { createGetRoute } from 'blaizejs';
448
+ import { z } from 'zod';
180
449
 
181
- // You cannot directly access the router:
182
- // import { Router } from 'blaizejs'; // NOT AVAILABLE
450
+ // Note: createGetRoute() returns a function
451
+ const getRoute = createGetRoute();
452
+
453
+ export const GET = getRoute({
454
+ schema: {
455
+ params: z.object({ userId: z.string().uuid() }),
456
+ response: z.object({
457
+ id: z.string(),
458
+ name: z.string(),
459
+ }),
460
+ },
461
+ handler: async (ctx, params) => {
462
+ return await db.users.findById(params.userId);
463
+ },
464
+ });
183
465
  ```
184
466
 
185
- ### 🔧 Middleware Module *(Exported)*
467
+ ---
468
+
469
+ ## 🔗 Middleware
470
+
471
+ ### createMiddleware
186
472
 
187
- Build composable request/response pipelines:
473
+ Create middleware with typed state and service additions.
188
474
 
189
475
  ```typescript
190
- import { createMiddleware, compose } from 'blaizejs';
476
+ import { createMiddleware } from 'blaizejs';
477
+
478
+ const middleware = createMiddleware({
479
+ name?: string;
480
+ handler: (ctx: Context, next: NextFunction) => Promise;
481
+ skip?: (ctx: Context) => boolean;
482
+ debug?: boolean;
483
+ });
484
+ ```
485
+
486
+ #### Options
487
+
488
+ | Option | Type | Description |
489
+ |--------|------|-------------|
490
+ | `name` | `string` | Middleware name (for debugging/logging) |
491
+ | `handler` | `function` | The middleware function |
492
+ | `skip` | `function` | Optional condition to skip this middleware |
493
+ | `debug` | `boolean` | Enable debug logging |
494
+
495
+ #### Type Parameters
496
+
497
+ | Parameter | Description |
498
+ |-----------|-------------|
499
+ | `TState` | Properties added to `ctx.state` |
500
+ | `TServices` | Properties added to `ctx.services` |
191
501
 
192
- // Simple middleware
193
- const logger = createMiddleware(async (ctx, next) => {
194
- console.log(`→ ${ctx.request.method} ${ctx.request.path}`);
195
- await next();
196
- console.log(`← ${ctx.response.statusCode}`);
502
+ #### Examples
503
+
504
+ **Logging Middleware:**
505
+
506
+ ```typescript
507
+ const loggingMiddleware = createMiddleware({
508
+ name: 'logger',
509
+ handler: async (ctx, next) => {
510
+ const start = Date.now();
511
+ console.log(`→ ${ctx.request.method} ${ctx.request.path}`);
512
+
513
+ await next();
514
+
515
+ const duration = Date.now() - start;
516
+ console.log(`← ${ctx.response.statusCode} (${duration}ms)`);
517
+ },
197
518
  });
519
+ ```
520
+
521
+ **Authentication Middleware:**
522
+
523
+ ```typescript
524
+ interface User {
525
+ id: string;
526
+ email: string;
527
+ role: 'admin' | 'user';
528
+ }
529
+
530
+ interface AuthService {
531
+ verify(token: string): Promise;
532
+ }
198
533
 
199
- // Middleware with options
200
- const auth = createMiddleware({
534
+ const authMiddleware = createMiddleware<
535
+ { user: User },
536
+ { auth: AuthService }
537
+ >({
201
538
  name: 'auth',
202
539
  handler: async (ctx, next) => {
203
- const token = ctx.request.header('authorization');
540
+ const token = ctx.request.header('authorization')?.replace('Bearer ', '');
541
+
204
542
  if (!token) {
205
- throw new UnauthorizedError('No token provided');
543
+ throw new UnauthorizedError('Missing authentication token');
544
+ }
545
+
546
+ try {
547
+ ctx.state.user = await authService.verify(token);
548
+ ctx.services.auth = authService;
549
+ } catch (error) {
550
+ throw new UnauthorizedError('Invalid token');
206
551
  }
207
- ctx.state.user = await verifyToken(token);
552
+
208
553
  await next();
209
554
  },
210
- skip: ctx => ctx.request.path.startsWith('/public')
555
+ skip: (ctx) => ctx.request.path === '/health',
211
556
  });
557
+ ```
558
+
559
+ **Timing Middleware:**
212
560
 
213
- // Compose multiple middleware
214
- const apiMiddleware = compose([cors, auth, rateLimit]);
561
+ ```typescript
562
+ const timingMiddleware = createMiddleware({
563
+ name: 'timing',
564
+ handler: async (ctx, next) => {
565
+ ctx.state.requestStart = Date.now();
566
+ await next();
567
+
568
+ const duration = Date.now() - ctx.state.requestStart;
569
+ ctx.response.header('X-Response-Time', `${duration}ms`);
570
+ },
571
+ });
215
572
  ```
216
573
 
217
- ### 🧩 Plugins Module *(Exported)*
574
+ ### createStateMiddleware
218
575
 
219
- Extend server functionality with lifecycle hooks:
576
+ Shorthand for middleware that only adds state.
577
+
578
+ ```typescript
579
+ import { createStateMiddleware } from 'blaizejs';
580
+
581
+ const timingMiddleware = createStateMiddleware(
582
+ async (ctx, next) => {
583
+ ctx.state.startTime = Date.now();
584
+ await next();
585
+ }
586
+ );
587
+ ```
588
+
589
+ ### createServiceMiddleware
590
+
591
+ Shorthand for middleware that only adds services.
592
+
593
+ ```typescript
594
+ import { createServiceMiddleware } from 'blaizejs';
595
+
596
+ const dbMiddleware = createServiceMiddleware(
597
+ async (ctx, next) => {
598
+ ctx.services.db = database;
599
+ await next();
600
+ }
601
+ );
602
+ ```
603
+
604
+ ### compose
605
+
606
+ Combine multiple middleware into a single middleware.
607
+
608
+ ```typescript
609
+ import { compose } from 'blaizejs';
610
+
611
+ const combined = compose([
612
+ loggingMiddleware,
613
+ authMiddleware,
614
+ timingMiddleware,
615
+ ]);
616
+
617
+ const server = createServer({
618
+ middleware: [combined],
619
+ routesDir: './routes',
620
+ });
621
+ ```
622
+
623
+ ---
624
+
625
+ ## 🔌 Plugins
626
+
627
+ ### createPlugin
628
+
629
+ Create a plugin with lifecycle hooks and service injection.
220
630
 
221
631
  ```typescript
222
632
  import { createPlugin } from 'blaizejs';
223
633
 
224
- const databasePlugin = createPlugin(
225
- 'database',
634
+ const plugin = createPlugin(
635
+ name: string,
636
+ version: string,
637
+ setup: (server: Server, options: TOptions) => void | PluginHooks | Promise,
638
+ defaultOptions?: Partial
639
+ );
640
+ ```
641
+
642
+ #### Parameters
643
+
644
+ | Parameter | Type | Description |
645
+ |-----------|------|-------------|
646
+ | `name` | `string` | Unique plugin identifier |
647
+ | `version` | `string` | Plugin version (SemVer) |
648
+ | `setup` | `function` | Setup function called during registration |
649
+ | `defaultOptions` | `object` | Default option values |
650
+
651
+ #### Plugin Hooks
652
+
653
+ | Hook | When | Use Case |
654
+ |------|------|----------|
655
+ | `register` | During `createServer()` | Add middleware, register routes |
656
+ | `initialize` | Before `server.listen()` | Connect to databases, warm caches |
657
+ | `onServerStart` | After server is listening | Start background workers |
658
+ | `onServerStop` | Before `server.close()` | Stop accepting work |
659
+ | `terminate` | During shutdown | Disconnect resources |
660
+
661
+ #### Examples
662
+
663
+ **Simple Plugin:**
664
+
665
+ ```typescript
666
+ const helloPlugin = createPlugin(
667
+ 'hello',
226
668
  '1.0.0',
227
669
  (server) => {
228
- let connection;
670
+ console.log('Hello plugin registered!');
229
671
 
230
672
  return {
231
- initialize: async () => {
232
- connection = await db.connect();
233
- console.log('Database connected');
234
- },
235
- onServerStart: async () => {
236
- await connection.migrate();
673
+ onServerStart: () => {
674
+ console.log('Server started!');
237
675
  },
238
- onServerStop: async () => {
239
- await connection.close();
240
- },
241
- terminate: async () => {
242
- console.log('Database plugin terminated');
243
- }
244
676
  };
245
677
  }
246
678
  );
247
679
 
248
- // Use in server
249
680
  const server = createServer({
250
- plugins: [databasePlugin()]
681
+ plugins: [helloPlugin()],
251
682
  });
252
683
  ```
253
684
 
254
- ### 🔗 Context Module *(Internal)*
255
-
256
- **⚠️ Note**: Context is automatically managed. You interact with it in handlers.
257
-
258
- Context is automatically provided to all route handlers and middleware:
685
+ **Plugin with Services:**
259
686
 
260
687
  ```typescript
261
- // Context is the first parameter in handlers
262
- export const GET = createGetRoute({
263
- handler: async (ctx) => {
264
- // Request information
265
- const userId = ctx.request.header('x-user-id');
266
- const query = ctx.request.query;
267
-
268
- // State management
269
- ctx.state.requestStart = Date.now();
688
+ interface DatabaseOptions {
689
+ connectionString: string;
690
+ poolSize?: number;
691
+ }
692
+
693
+ const databasePlugin = createPlugin(
694
+ 'database',
695
+ '1.0.0',
696
+ (server, options) => {
697
+ let db: Database;
270
698
 
271
- // Response methods (usually return instead)
272
- // ctx.response.json({ data });
273
- // ctx.response.redirect('/login');
699
+ // Inject database into context
700
+ server.use(createMiddleware({
701
+ name: 'database-injection',
702
+ handler: async (ctx, next) => {
703
+ ctx.services.db = db;
704
+ await next();
705
+ },
706
+ }));
274
707
 
275
- return { message: 'Hello' };
276
- }
708
+ return {
709
+ initialize: async () => {
710
+ db = await Database.connect(options.connectionString, {
711
+ poolSize: options.poolSize,
712
+ });
713
+ console.log('Database connected');
714
+ },
715
+ terminate: async () => {
716
+ await db.disconnect();
717
+ console.log('Database disconnected');
718
+ },
719
+ };
720
+ },
721
+ { poolSize: 10 } // Default options
722
+ );
723
+
724
+ // Usage
725
+ const server = createServer({
726
+ plugins: [
727
+ databasePlugin({ connectionString: 'postgres://localhost/mydb' }),
728
+ ],
277
729
  });
278
730
  ```
279
731
 
280
- ## 🛡️ Error Handling
732
+ ---
733
+
734
+ ## ⚠️ Error Classes
735
+
736
+ BlaizeJS provides 12 semantic error classes that automatically format to HTTP responses.
737
+
738
+ ### Error Response Format
739
+
740
+ All errors produce this response structure:
281
741
 
282
- ### Available Error Classes
742
+ ```json
743
+ {
744
+ "type": "ERROR_TYPE",
745
+ "title": "Error message",
746
+ "status": 400,
747
+ "correlationId": "req_k3x2m1_9z8y7w6v",
748
+ "timestamp": "2024-01-15T10:30:00.000Z",
749
+ "details": { }
750
+ }
751
+ ```
283
752
 
284
- BlaizeJS exports semantic error classes that automatically format responses:
753
+ ### Error Classes Reference
754
+
755
+ | Class | Status | Use Case |
756
+ |-------|--------|----------|
757
+ | `ValidationError` | 400 | Schema validation failures, invalid input |
758
+ | `UnauthorizedError` | 401 | Missing or invalid authentication |
759
+ | `ForbiddenError` | 403 | Authenticated but not authorized |
760
+ | `NotFoundError` | 404 | Resource doesn't exist |
761
+ | `ConflictError` | 409 | Resource state conflict (duplicate, version mismatch) |
762
+ | `RequestTimeoutError` | 408 | Request took too long |
763
+ | `PayloadTooLargeError` | 413 | Request body exceeds limit |
764
+ | `UnsupportedMediaTypeError` | 415 | Wrong content type |
765
+ | `UnprocessableEntityError` | 422 | Valid syntax but invalid semantics |
766
+ | `RateLimitError` | 429 | Too many requests |
767
+ | `InternalServerError` | 500 | Unexpected server error |
768
+ | `ServiceNotAvailableError` | 503 | Dependency unavailable |
769
+
770
+ ### Usage
285
771
 
286
772
  ```typescript
287
773
  import {
288
- ValidationError, // 400 - Bad Request
289
- UnauthorizedError, // 401 - Authentication Required
290
- ForbiddenError, // 403 - Access Denied
291
- NotFoundError, // 404 - Resource Not Found
292
- RequestTimeoutError, // 408 - Request Timeout
293
- ConflictError, // 409 - Resource Conflict
294
- PayloadTooLargeError, // 413 - Payload Too Large
295
- UnsupportedMediaTypeError, // 415 - Unsupported Media Type
296
- UnprocessableEntityError, // 422 - Unprocessable Entity
297
- RateLimitError, // 429 - Too Many Requests
298
- InternalServerError // 500 - Server Error
774
+ NotFoundError,
775
+ ValidationError,
776
+ UnauthorizedError,
777
+ ForbiddenError,
778
+ ConflictError,
779
+ RateLimitError,
299
780
  } from 'blaizejs';
300
781
 
301
- // Throw semantic errors
782
+ // Basic usage
783
+ throw new NotFoundError('User not found');
784
+
785
+ // With details
302
786
  throw new NotFoundError('User not found', {
303
787
  resourceType: 'user',
304
- resourceId: params.userId,
305
- suggestion: 'Check the user ID'
788
+ resourceId: userId,
789
+ suggestion: 'Verify the user ID exists',
306
790
  });
307
791
 
308
- // Automatic response format:
309
- // {
310
- // "type": "NOT_FOUND",
311
- // "title": "User not found",
312
- // "status": 404,
313
- // "correlationId": "req_abc123",
314
- // "timestamp": "2024-01-15T10:30:00.000Z",
315
- // "details": { ... }
316
- // }
792
+ // Validation error with field details
793
+ throw new ValidationError('Invalid input', {
794
+ fields: {
795
+ email: 'Must be a valid email address',
796
+ age: 'Must be at least 18',
797
+ },
798
+ });
799
+
800
+ // Rate limit with retry info
801
+ throw new RateLimitError('Too many requests', {
802
+ retryAfter: 60,
803
+ limit: 100,
804
+ remaining: 0,
805
+ });
806
+
807
+ // Conflict with version info
808
+ throw new ConflictError('Version mismatch', {
809
+ currentVersion: 5,
810
+ providedVersion: 3,
811
+ });
812
+ ```
813
+
814
+ ### Custom Correlation ID
815
+
816
+ ```typescript
817
+ // Use custom correlation ID (for distributed tracing)
818
+ throw new NotFoundError('User not found', {}, 'custom-trace-id-123');
317
819
  ```
318
820
 
319
- ### Additional Error Classes
821
+ ---
822
+
823
+ ## 📝 Logging
320
824
 
321
- The following error classes are also available for specific scenarios:
825
+ ### Global Logger
322
826
 
323
827
  ```typescript
324
- import {
325
- PayloadTooLargeError, // 413 - Request Entity Too Large
326
- UnsupportedMediaTypeError, // 415 - Unsupported Media Type
327
- RequestTimeoutError, // 408 - Request Timeout
328
- UnprocessableEntityError // 422 - Unprocessable Entity
329
- } from 'blaizejs';
828
+ import { logger } from 'blaizejs';
330
829
 
331
- // File size exceeded
332
- throw new PayloadTooLargeError('File too large', {
333
- fileCount: 11,
334
- maxFiles: 10,
335
- filename: 'huge-video.mp4',
336
- currentSize: 104857600, // 100MB
337
- maxSize: 52428800 // 50MB
338
- });
339
-
340
- // Wrong content type
341
- throw new UnsupportedMediaTypeError('File type not allowed', {
342
- receivedMimeType: 'application/x-executable',
343
- allowedMimeTypes: ['image/jpeg', 'image/png', 'application/pdf'],
344
- filename: 'virus.exe'
345
- });
346
-
347
- // Request timeout
348
- throw new RequestTimeoutError('Upload timeout', {
349
- timeoutMs: 30000,
350
- elapsedMs: 31000,
351
- operation: 'file-upload'
352
- });
353
-
354
- // Business rule violation
355
- throw new UnprocessableEntityError('Business rule violation', {
356
- rule: 'minimum_order_amount',
357
- currentValue: 5.00,
358
- requiredValue: 10.00,
359
- message: 'Order total must be at least $10.00'
360
- });
361
- ```
362
-
363
- ## 🎯 API Reference
364
-
365
- ### Exported Functions
366
-
367
- | Function | Description |
368
- |----------|-------------|
369
- | **Server** | |
370
- | `createServer(options?)` | Create HTTP/2 server instance |
371
- | **Routing** | |
372
- | `createGetRoute(config)` | Create GET endpoint |
373
- | `createPostRoute(config)` | Create POST endpoint |
374
- | `createPutRoute(config)` | Create PUT endpoint |
375
- | `createPatchRoute(config)` | Create PATCH endpoint |
376
- | `createDeleteRoute(config)` | Create DELETE endpoint |
377
- | `createHeadRoute(config)` | Create HEAD endpoint |
378
- | `createOptionsRoute(config)` | Create OPTIONS endpoint |
379
- | **Middleware** | |
380
- | `createMiddleware(handler)` | Create middleware instance |
381
- | `compose(middleware[])` | Compose multiple middleware |
382
- | **Plugins** | |
383
- | `createPlugin(name, version, factory)` | Create server plugin |
384
- | **Errors** | |
385
- | `ValidationError` | 400 Bad Request |
386
- | `UnauthorizedError` | 401 Unauthorized |
387
- | `ForbiddenError` | 403 Forbidden |
388
- | `NotFoundError` | 404 Not Found |
389
- | `RequestTimeoutError` | 408 Request Timeout |
390
- | `ConflictError` | 409 Conflict |
391
- | `PayloadTooLargeError` | 413 Payload Too Large |
392
- | `UnsupportedMediaTypeError` | 415 Unsupported Media Type |
393
- | `UnprocessableEntityError` | 422 Unprocessable Entity |
394
- | `RateLimitError` | 429 Too Many Requests |
395
- | `InternalServerError` | 500 Internal Server Error |
396
-
397
- ### Exported Types
398
-
399
- All types are re-exported from `@blaize-types`:
400
-
401
- ```typescript
402
- import type {
403
- // Server types
404
- Server,
405
- ServerOptionsInput,
406
-
407
- // Middleware types
408
- Middleware,
409
- MiddlewareFunction,
410
- MiddlewareOptions,
411
- NextFunction,
412
-
413
- // Plugin types
414
- Plugin,
415
- PluginFactory,
416
- PluginHooks,
417
-
418
- // Router types (limited export)
419
- HttpMethod,
420
- RouteHandler,
421
- RouteMethodOptions,
422
-
423
- // Context types
424
- Context,
425
-
426
- // Error types
427
- BlaizeError,
428
- ErrorType
429
- } from 'blaizejs';
830
+ logger.info('Server started', { port: 3000 });
831
+ logger.warn('Cache miss', { key: 'user:123' });
832
+ logger.error('Database error', { error: err.message });
833
+ logger.debug('Request received', { path: '/users' });
430
834
  ```
431
835
 
432
- ## 💡 Common Patterns
836
+ ### createLogger
433
837
 
434
- ### Protected Routes
838
+ Create a custom logger with specific transports.
435
839
 
436
840
  ```typescript
437
- const authMiddleware = createMiddleware({
438
- name: 'auth',
439
- handler: async (ctx, next) => {
440
- const token = ctx.request.header('authorization');
441
- if (!token) {
442
- throw new UnauthorizedError('Authentication required');
443
- }
444
- ctx.state.user = await verifyToken(token);
445
- await next();
446
- }
447
- });
448
-
449
- export const GET = createGetRoute({
450
- middleware: [authMiddleware],
451
- handler: async (ctx) => {
452
- return { user: ctx.state.user };
453
- }
841
+ import { createLogger, ConsoleTransport, JSONTransport } from 'blaizejs';
842
+
843
+ const customLogger = createLogger({
844
+ level: 'info',
845
+ transports: [
846
+ new ConsoleTransport({ colorize: true }),
847
+ new JSONTransport({ destination: './logs/app.log' }),
848
+ ],
454
849
  });
455
850
  ```
456
851
 
457
- ### Request Validation
852
+ ### Available Transports
853
+
854
+ | Transport | Description |
855
+ |-----------|-------------|
856
+ | `ConsoleTransport` | Logs to stdout with optional colors |
857
+ | `JSONTransport` | Logs as JSON to file or stream |
858
+ | `NullTransport` | Discards all logs (for testing) |
859
+
860
+ ### Route Handler Logger
861
+
862
+ Route handlers receive a request-scoped logger as the third parameter:
458
863
 
459
864
  ```typescript
460
- export const POST = createPostRoute({
865
+ export const getUser = route.get({
461
866
  schema: {
462
- body: z.object({
463
- email: z.string().email(),
464
- password: z.string().min(8),
465
- age: z.number().int().positive().optional()
466
- })
867
+ params: z.object({ userId: z.string() }),
467
868
  },
468
- handler: async (ctx) => {
469
- // Body is fully validated and typed
470
- const user = await createUser(ctx.body);
869
+ handler: async (ctx, params, logger) => {
870
+ logger.info('Fetching user', { userId: params.userId });
871
+ // Log includes correlation ID automatically
872
+
873
+ const user = await db.users.findById(params.userId);
874
+
875
+ logger.debug('User found', { user });
471
876
  return user;
472
- }
877
+ },
473
878
  });
474
879
  ```
475
880
 
476
- ### Error Handling
881
+ ### configureGlobalLogger
882
+
883
+ Configure the global logger instance.
477
884
 
478
885
  ```typescript
479
- export const GET = createGetRoute({
480
- handler: async (ctx, params) => {
481
- try {
482
- const resource = await findResource(params.id);
483
-
484
- if (!resource) {
485
- throw new NotFoundError('Resource not found', {
486
- resourceType: 'item',
487
- resourceId: params.id
488
- });
489
- }
490
-
491
- if (!hasPermission(ctx.state.user, resource)) {
492
- throw new ForbiddenError('Access denied', {
493
- resource: resource.id,
494
- requiredPermission: 'read'
495
- });
496
- }
497
-
498
- return resource;
499
- } catch (error) {
500
- // Framework automatically handles error responses
501
- throw error;
502
- }
503
- }
886
+ import { configureGlobalLogger, JSONTransport } from 'blaizejs';
887
+
888
+ configureGlobalLogger({
889
+ level: process.env.LOG_LEVEL || 'info',
890
+ transports: [
891
+ new JSONTransport({ pretty: process.env.NODE_ENV !== 'production' }),
892
+ ],
504
893
  });
505
894
  ```
506
895
 
507
- ## 🧪 Testing
896
+ ---
897
+
898
+ ## 🛠️ Utilities
508
899
 
509
- Use `@blaizejs/testing-utils` for testing:
900
+ ### getCorrelationId
901
+
902
+ Get the current request's correlation ID (from AsyncLocalStorage).
510
903
 
511
904
  ```typescript
512
- import { createTestContext } from '@blaizejs/testing-utils';
513
- import { describe, test, expect } from 'vitest';
905
+ import { getCorrelationId } from 'blaizejs';
514
906
 
515
- describe('User Routes', () => {
516
- test('GET /users/:id returns user', async () => {
517
- const ctx = createTestContext({
518
- method: 'GET',
519
- path: '/users/123'
520
- });
521
-
522
- const handler = createGetRoute({
523
- handler: async (ctx, params) => {
524
- return { id: params.userId, name: 'Test User' };
525
- }
526
- });
527
-
528
- const result = await handler.handler(ctx, { userId: '123' });
529
- expect(result.id).toBe('123');
530
- });
907
+ function someDeepFunction() {
908
+ const correlationId = getCorrelationId();
909
+ console.log(`Processing request ${correlationId}`);
910
+ }
911
+ ```
912
+
913
+ ### cors
914
+
915
+ CORS middleware for cross-origin requests.
916
+
917
+ ```typescript
918
+ import { createServer, cors } from 'blaizejs';
919
+
920
+ const server = createServer({
921
+ middleware: [
922
+ cors({
923
+ origin: 'https://example.com',
924
+ methods: ['GET', 'POST', 'PUT', 'DELETE'],
925
+ allowedHeaders: ['Content-Type', 'Authorization'],
926
+ credentials: true,
927
+ maxAge: 86400,
928
+ }),
929
+ ],
930
+ routesDir: './routes',
531
931
  });
532
932
  ```
533
933
 
534
- ## 📚 Type System
934
+ #### CORS Options
935
+
936
+ | Option | Type | Default | Description |
937
+ |--------|------|---------|-------------|
938
+ | `origin` | `string \| string[] \| function` | `'*'` | Allowed origins |
939
+ | `methods` | `string[]` | `['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE']` | Allowed methods |
940
+ | `allowedHeaders` | `string[]` | — | Allowed request headers |
941
+ | `exposedHeaders` | `string[]` | — | Headers to expose to client |
942
+ | `credentials` | `boolean` | `false` | Allow credentials |
943
+ | `maxAge` | `number` | — | Preflight cache duration (seconds) |
535
944
 
536
- BlaizeJS provides full type safety through TypeScript:
945
+ #### Examples
537
946
 
538
- ### Automatic Type Inference
947
+ **Multiple Origins:**
539
948
 
540
949
  ```typescript
541
- const route = createPostRoute({
542
- schema: {
543
- body: z.object({
544
- name: z.string(),
545
- age: z.number()
546
- }),
547
- response: z.object({
548
- id: z.string(),
549
- created: z.boolean()
550
- })
551
- },
552
- handler: async (ctx) => {
553
- // ctx.body is typed as { name: string; age: number }
554
- // Return type must match response schema
555
- return {
556
- id: '123',
557
- created: true
558
- };
559
- }
950
+ cors({
951
+ origin: ['https://app.example.com', 'https://admin.example.com'],
560
952
  });
561
953
  ```
562
954
 
563
- ### Custom Type Extensions
955
+ **Dynamic Origin:**
564
956
 
565
957
  ```typescript
566
- // Extend context state
567
- declare module 'blaizejs' {
568
- interface State {
569
- user?: {
570
- id: string;
571
- role: string;
572
- };
573
- }
574
- }
958
+ cors({
959
+ origin: (origin) => {
960
+ return origin?.endsWith('.example.com') ?? false;
961
+ },
962
+ });
575
963
  ```
576
964
 
577
- ## 🗺️ Roadmap
965
+ ---
578
966
 
579
- ### 🚀 Current Beta (v0.3.1)
967
+ ## 📦 Context Reference
968
+
969
+ The `Context` object is available in all route handlers and middleware.
970
+
971
+ ### ctx.request
972
+
973
+ | Property/Method | Type | Description |
974
+ |-----------------|------|-------------|
975
+ | `method` | `string` | HTTP method (GET, POST, etc.) |
976
+ | `path` | `string` | Request path |
977
+ | `url` | `string \| null` | Full URL |
978
+ | `query` | `Record<string, unknown>` | Parsed query parameters |
979
+ | `params` | `Record<string, string>` | Route parameters |
980
+ | `body` | `unknown` | Parsed request body |
981
+ | `protocol` | `'http' \| 'https'` | Request protocol |
982
+ | `isHttp2` | `boolean` | Whether using HTTP/2 |
983
+ | `header(name)` | `string \| undefined` | Get a header value |
984
+ | `headers(names?)` | `Record<string, string>` | Get multiple headers |
985
+ | `raw` | `IncomingMessage` | Raw Node.js request |
986
+
987
+ ### ctx.response
988
+
989
+ | Property/Method | Type | Description |
990
+ |-----------------|------|-------------|
991
+ | `sent` | `boolean` | Whether response was sent |
992
+ | `statusCode` | `number` | Current status code |
993
+ | `status(code)` | `ContextResponse` | Set status code (chainable) |
994
+ | `json(data)` | `ContextResponse` | Send JSON response |
995
+ | `html(content)` | `ContextResponse` | Send HTML response |
996
+ | `text(content)` | `ContextResponse` | Send text response |
997
+ | `redirect(url, code?)` | `ContextResponse` | Redirect response |
998
+ | `stream(readable)` | `ContextResponse` | Stream response |
999
+ | `header(name, value)` | `ContextResponse` | Set a header (chainable) |
1000
+ | `headers(headers)` | `ContextResponse` | Set multiple headers |
1001
+
1002
+ ### ctx.state
1003
+
1004
+ Request-scoped data added by middleware:
580
1005
 
581
- - ✅ Core server with HTTP/2 support
582
- - File-based routing (internal)
583
- - Middleware system
584
- - ✅ Plugin architecture
585
- - ✅ 11 semantic error classes (400-500 status codes)
586
- - ✅ Schema validation with Zod
587
- - ✅ Context management (internal)
588
- - ✅ Type-safe route creation
1006
+ ```typescript
1007
+ // Added by authMiddleware
1008
+ ctx.state.user // User object
589
1009
 
590
- ### 🎯 MVP/1.0 Release
1010
+ // Added by timingMiddleware
1011
+ ctx.state.startTime // number
1012
+ ```
591
1013
 
592
- #### Core Improvements
593
- - 🔄 **Export Router Utilities** - Parameter extraction, route matching for extensions
594
- - 🔄 **Custom Error Factory** - Allow user-defined error classes
595
- - 🔄 **Enhanced Testing Utils** - More comprehensive testing helpers
596
- - 🔄 **Performance Monitoring** - Built-in metrics and profiling
597
- - 🔄 **Additional HTTP Status Codes** - 405, 502, 503, 504 error classes
1014
+ ### ctx.services
598
1015
 
599
- #### New Features
600
- - 🔄 **WebSocket Support** - Real-time communication
601
- - 🔄 **Response Helpers** - Utility functions for common responses
602
- - 🔄 **Route Metadata** - Attach custom metadata to routes
603
- - 🔄 **Built-in Middleware** - CORS, compression, security headers
604
- - 🔄 **Request Streaming** - Handle large payloads efficiently
1016
+ Plugin-injected services:
605
1017
 
606
- ### 🔮 Post-MVP (v1.1+)
1018
+ ```typescript
1019
+ // Added by databasePlugin
1020
+ ctx.services.db // Database instance
607
1021
 
608
- - 🔄 **GraphQL Integration** - Built-in GraphQL support
609
- - 🔄 **gRPC Support** - Protocol buffer services
610
- - 🔄 **OpenAPI Generation** - Automatic API documentation
611
- - 🔄 **Distributed Tracing** - OpenTelemetry integration
612
- - 🔄 **Edge Runtime Support** - Cloudflare Workers, Deno Deploy
613
- - 🔄 **Bun Compatibility** - Native Bun.serve integration
1022
+ // Added by cachePlugin
1023
+ ctx.services.cache // CacheService
614
1024
 
615
- ## 🤝 Contributing
1025
+ // Added by queuePlugin
1026
+ ctx.services.queue // QueueService
1027
+ ```
616
1028
 
617
- We welcome contributions! Please see our [Contributing Guide](../../CONTRIBUTING.md) for details.
1029
+ > 🔒 **Note:** `createContext` is internal and not exported. For testing, use `createTestContext` from `@blaizejs/testing-utils`.
618
1030
 
619
- ### Development Setup
1031
+ ---
1032
+
1033
+ ## 🧪 Testing
1034
+
1035
+ BlaizeJS integrates with [Vitest](https://vitest.dev/) through the `@blaizejs/testing-utils` package.
1036
+
1037
+ ### Quick Setup
620
1038
 
621
1039
  ```bash
622
- # Clone the repository
623
- git clone https://github.com/jleajones/blaize.git
624
- cd blaize
1040
+ pnpm add -D vitest @blaizejs/testing-utils
1041
+ ```
1042
+
1043
+ ```typescript
1044
+ // vitest.config.ts
1045
+ import { defineConfig } from 'vitest/config';
1046
+
1047
+ export default defineConfig({
1048
+ test: {
1049
+ globals: true,
1050
+ },
1051
+ });
1052
+ ```
1053
+
1054
+ ### Testing Routes
1055
+
1056
+ ```typescript
1057
+ import { describe, it, expect } from 'vitest';
1058
+ import { createTestContext, createMockLogger } from '@blaizejs/testing-utils';
1059
+ import { GET } from './routes/users/[userId]';
625
1060
 
626
- # Install dependencies
627
- pnpm install
1061
+ describe('GET /users/:userId', () => {
1062
+ it('returns user by id', async () => {
1063
+ const ctx = createTestContext({
1064
+ method: 'GET',
1065
+ path: '/users/123',
1066
+ params: { userId: '123' },
1067
+ });
1068
+ const logger = createMockLogger();
1069
+
1070
+ const result = await GET.handler(ctx, { userId: '123' }, logger);
1071
+
1072
+ expect(result.id).toBe('123');
1073
+ expect(result.name).toBeDefined();
1074
+ });
1075
+
1076
+ it('throws NotFoundError for missing user', async () => {
1077
+ const ctx = createTestContext({
1078
+ params: { userId: 'nonexistent' },
1079
+ });
1080
+ const logger = createMockLogger();
1081
+
1082
+ await expect(
1083
+ GET.handler(ctx, { userId: 'nonexistent' }, logger)
1084
+ ).rejects.toThrow(NotFoundError);
1085
+ });
1086
+ });
1087
+ ```
628
1088
 
629
- # Run tests
630
- pnpm test
1089
+ ### Testing Middleware
631
1090
 
632
- # Build packages
633
- pnpm build
1091
+ ```typescript
1092
+ import { describe, it, expect, vi } from 'vitest';
1093
+ import { createTestContext } from '@blaizejs/testing-utils';
1094
+ import { authMiddleware } from './middleware/auth';
634
1095
 
635
- # Run examples
636
- pnpm --filter blaizejs dev
1096
+ describe('authMiddleware', () => {
1097
+ it('adds user to state when token is valid', async () => {
1098
+ const ctx = createTestContext({
1099
+ headers: { authorization: 'Bearer valid-token' },
1100
+ });
1101
+ const next = vi.fn();
1102
+
1103
+ await authMiddleware.handler(ctx, next);
1104
+
1105
+ expect(ctx.state.user).toBeDefined();
1106
+ expect(ctx.state.user.id).toBe('user-123');
1107
+ expect(next).toHaveBeenCalled();
1108
+ });
1109
+
1110
+ it('throws UnauthorizedError when token is missing', async () => {
1111
+ const ctx = createTestContext();
1112
+ const next = vi.fn();
1113
+
1114
+ await expect(
1115
+ authMiddleware.handler(ctx, next)
1116
+ ).rejects.toThrow(UnauthorizedError);
1117
+
1118
+ expect(next).not.toHaveBeenCalled();
1119
+ });
1120
+ });
637
1121
  ```
638
1122
 
639
- ### Package Structure
1123
+ ### Mocking Services
640
1124
 
1125
+ ```typescript
1126
+ import { describe, it, expect, vi } from 'vitest';
1127
+ import { createTestContext } from '@blaizejs/testing-utils';
1128
+ import { POST } from './routes/users';
1129
+
1130
+ describe('POST /users', () => {
1131
+ it('creates user with mocked database', async () => {
1132
+ const mockDb = {
1133
+ users: {
1134
+ create: vi.fn().mockResolvedValue({
1135
+ id: 'new-user-123',
1136
+ name: 'John',
1137
+ email: 'john@example.com',
1138
+ }),
1139
+ },
1140
+ };
1141
+
1142
+ const ctx = createTestContext({
1143
+ method: 'POST',
1144
+ body: { name: 'John', email: 'john@example.com' },
1145
+ services: { db: mockDb },
1146
+ });
1147
+
1148
+ const result = await POST.handler(ctx, {}, createMockLogger());
1149
+
1150
+ expect(result.id).toBe('new-user-123');
1151
+ expect(mockDb.users.create).toHaveBeenCalledWith({
1152
+ name: 'John',
1153
+ email: 'john@example.com',
1154
+ });
1155
+ });
1156
+ });
641
1157
  ```
642
- packages/
643
- ├── blaize-core/ # Main framework (this package)
644
- │ ├── src/
645
- │ │ ├── server/ # Server implementation
646
- │ │ ├── router/ # Router (mostly internal)
647
- │ │ ├── middleware/ # Middleware system
648
- │ │ ├── plugins/ # Plugin system
649
- │ │ ├── context/ # Context (internal)
650
- │ │ ├── errors/ # Error classes
651
- │ │ └── index.ts # Main exports
652
- │ └── package.json
653
- ├── blaize-types/ # Shared TypeScript types
654
- ├── blaize-client/ # Client SDK
655
- └── blaize-testing-utils/ # Testing utilities
1158
+
1159
+ ### Mock Logger
1160
+
1161
+ ```typescript
1162
+ import { createMockLogger } from '@blaizejs/testing-utils';
1163
+
1164
+ const logger = createMockLogger();
1165
+
1166
+ // Use in tests
1167
+ await handler(ctx, params, logger);
1168
+
1169
+ // Assert logs
1170
+ expect(logger.logs).toContainEqual({
1171
+ level: 'info',
1172
+ message: 'User created',
1173
+ meta: expect.objectContaining({ userId: '123' }),
1174
+ });
656
1175
  ```
657
1176
 
658
- ### Important Notes
1177
+ See [`@blaizejs/testing-utils`](../blaize-testing-utils/README.md) for the full testing API.
1178
+
1179
+ ---
1180
+
1181
+ ## 🗺️ Roadmap
1182
+
1183
+ ### 🎯 v1.0 (Stable)
1184
+
1185
+ - [ ] Redis adapter for queue plugin
1186
+ - [ ] Rate limiting plugin (`@blaizejs/plugin-rate-limit`)
1187
+ - [ ] Compression middleware (`@blaizejs/middleware-compression`)
1188
+ - [ ] Database plugin with migrations (`@blaizejs/plugin-db`)
1189
+ - [ ] Storage plugin (`@blaizejs/plugin-storage`)
1190
+ - [ ] OpenAPI/Swagger generation
659
1191
 
660
- When contributing to BlaizeJS Core:
1192
+ ### 🔮 Future
661
1193
 
662
- 1. **Check Exports**: Ensure new features are exported in `src/index.ts`
663
- 2. **Update Types**: Add types to `@blaize-types` package
664
- 3. **Document Internal APIs**: Mark internal-only features clearly
665
- 4. **Add Tests**: Use `@blaizejs/testing-utils` for testing
666
- 5. **Follow Patterns**: Match existing code style and patterns
1194
+ - [ ] Authentication plugin (`@blaizejs/plugin-auth`)
1195
+ - [ ] Edge runtime support
1196
+ - [ ] External queue workers
1197
+ - [ ] HTTP/2 hosting solutions
1198
+ - [ ] Deeper AI integrations
667
1199
 
668
1200
  ---
669
1201
 
670
- **Built with ❤️ by the BlaizeJS team**
1202
+ ## 📚 Related
1203
+
1204
+ - [`@blaizejs/client`](../blaize-client/README.md) — Type-safe RPC client
1205
+ - [`@blaizejs/testing-utils`](../blaize-testing-utils/README.md) — Testing utilities
1206
+ - [Architecture Guide](../../docs/ARCHITECTURE.md) — How BlaizeJS works
1207
+ - [Getting Started](../../docs/GETTING-STARTED.md) — Build your first project
1208
+
1209
+ ---
1210
+
1211
+ ## 📄 License
1212
+
1213
+ MIT © [BlaizeJS Contributors](https://github.com/jleajones/blaize/graphs/contributors)
1214
+
1215
+ ---
671
1216
 
672
- _For questions or issues, please [open an issue](https://github.com/jleajones/blaize/issues) on GitHub._
1217
+ **Built with ❤️ by the BlaizeJS team**