blaizejs 0.9.2 → 0.10.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 (27) hide show
  1. package/README.md +302 -1031
  2. package/dist/{chunk-I5FNWSJS.js → chunk-4UKXEMYU.js} +3 -3
  3. package/dist/chunk-4UKXEMYU.js.map +1 -0
  4. package/dist/{chunk-3ICDFF57.js → chunk-IPU6LCPL.js} +3 -3
  5. package/dist/{chunk-XIPQPFN5.js → chunk-JY6NZIG2.js} +3 -3
  6. package/dist/{chunk-GIZW5W7C.js → chunk-PGUWVOHK.js} +3 -3
  7. package/dist/{chunk-ULIQB554.js → chunk-VEQ5CZZF.js} +3 -3
  8. package/dist/index.cjs +14 -14
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +5 -1
  11. package/dist/index.d.ts +5 -1
  12. package/dist/index.js +15 -15
  13. package/dist/index.js.map +1 -1
  14. package/dist/{internal-server-error-DVVTTWHD.js → internal-server-error-VJDQ4MEY.js} +3 -3
  15. package/dist/{payload-too-large-error-QGRSATV5.js → payload-too-large-error-GVRSZNQ3.js} +3 -3
  16. package/dist/{unsupported-media-type-error-VAT4HTL4.js → unsupported-media-type-error-XA32IKKD.js} +3 -3
  17. package/dist/{validation-error-N57OM7AM.js → validation-error-FY37VOK2.js} +3 -3
  18. package/package.json +2 -2
  19. package/dist/chunk-I5FNWSJS.js.map +0 -1
  20. /package/dist/{chunk-3ICDFF57.js.map → chunk-IPU6LCPL.js.map} +0 -0
  21. /package/dist/{chunk-XIPQPFN5.js.map → chunk-JY6NZIG2.js.map} +0 -0
  22. /package/dist/{chunk-GIZW5W7C.js.map → chunk-PGUWVOHK.js.map} +0 -0
  23. /package/dist/{chunk-ULIQB554.js.map → chunk-VEQ5CZZF.js.map} +0 -0
  24. /package/dist/{internal-server-error-DVVTTWHD.js.map → internal-server-error-VJDQ4MEY.js.map} +0 -0
  25. /package/dist/{payload-too-large-error-QGRSATV5.js.map → payload-too-large-error-GVRSZNQ3.js.map} +0 -0
  26. /package/dist/{unsupported-media-type-error-VAT4HTL4.js.map → unsupported-media-type-error-XA32IKKD.js.map} +0 -0
  27. /package/dist/{validation-error-N57OM7AM.js.map → validation-error-FY37VOK2.js.map} +0 -0
package/README.md CHANGED
@@ -1,1217 +1,488 @@
1
- # 🔥 BlaizeJS Core
1
+ # 🔥 BlaizeJS
2
2
 
3
- > The core BlaizeJS framework type-safe APIs with file-based routing
3
+ > Call server functions like local functions fully typed
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
- [![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/)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.3+-blue.svg)](https://www.typescriptlang.org/)
8
+ [![Node.js](https://img.shields.io/badge/Node.js-20.0+-green.svg)](https://nodejs.org/)
9
9
  [![Build Status](https://github.com/jleajones/blaize/workflows/Test/badge.svg)](https://github.com/jleajones/blaize/actions)
10
10
 
11
- The `blaizejs` package is the core framework providing servers, routing, middleware, plugins, and error handling with end-to-end type safety.
11
+ BlaizeJS is a TypeScript-first backend framework that brings end-to-end type safety to Node.js APIs. Define your routes once, and get full autocomplete and type checking on both server and client — no code generation, no manual type syncing, no runtime overhead.
12
12
 
13
13
  ---
14
14
 
15
- ## 📦 Installation
16
-
17
- ### Recommended: Create a New Project
18
-
19
- ```bash
20
- # Using pnpm (recommended)
21
- pnpm dlx create-blaize-app my-app
22
-
23
- # Using npm
24
- npx create-blaize-app my-app
25
-
26
- # Using yarn
27
- yarn dlx create-blaize-app my-app
28
- ```
29
-
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
- ---
48
-
49
- ## 🚀 Quick Start
15
+ ## The Magic
50
16
 
51
17
  ```typescript
52
- // src/app.ts
18
+ // src/app.ts — Create your server and typed route factory
53
19
  import { Blaize, type InferContext } from 'blaizejs';
54
- import { fileURLToPath } from 'node:url';
55
- import path from 'node:path';
56
-
57
- const __filename = fileURLToPath(import.meta.url);
58
- const __dirname = path.dirname(__filename);
59
20
 
60
21
  const app = Blaize.createServer({
61
22
  port: 3000,
62
- routesDir: path.resolve(__dirname, './routes'),
23
+ routesDir: './src/routes',
63
24
  });
64
25
 
65
- // Create a typed route factory for use in route files
66
- type AppContext = InferContext;
26
+ // Create a typed route factory shares types across all routes
27
+ type AppContext = InferContext<typeof app>;
67
28
  export const route = Blaize.Router.createRouteFactory<
68
29
  AppContext['state'],
69
30
  AppContext['services']
70
31
  >();
71
32
 
72
33
  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
34
  ```
89
35
 
90
36
  ```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
-
167
- const server = createServer({
168
- port: 3000,
169
- routesDir: './src/routes',
170
- });
171
-
172
- await server.listen();
173
- ```
174
-
175
- **With Middleware:**
176
-
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:**
211
-
212
- ```typescript
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';
278
- import { z } from 'zod';
279
-
280
- // Named exports — these names become the client method names
281
- export const listUsers = route.get({
282
- schema: {
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
- });
292
- },
293
- });
294
-
295
- export const createUser = route.post({
296
- schema: {
297
- body: z.object({
298
- name: z.string().min(1),
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);
305
- },
306
- });
307
- ```
308
-
309
- ```typescript
310
- // src/routes/users/[userId].ts
37
+ // src/routes/users/[userId].ts — Routes get full type inference
311
38
  import { route } from '../../app';
312
39
  import { z } from 'zod';
313
- import { NotFoundError, ForbiddenError } from 'blaizejs';
314
40
 
315
41
  export const getUser = route.get({
316
42
  schema: {
317
43
  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
- });
326
-
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
- });
341
-
342
- export const deleteUser = route.delete({
343
- schema: {
344
- params: z.object({ userId: z.string() }),
44
+ response: z.object({
45
+ id: z.string(),
46
+ name: z.string(),
47
+ email: z.string().email(),
48
+ }),
345
49
  },
346
- handler: async (ctx, params) => {
347
- await ctx.services.db.users.delete(params.userId);
348
- ctx.response.status(204);
50
+ handler: async ({ ctx, params }) => {
51
+ // ctx.state and ctx.services are fully typed from middleware/plugins!
52
+ return await db.users.findById(params.userId);
349
53
  },
350
54
  });
351
55
  ```
352
56
 
353
57
  ```typescript
354
- // src/app-type.ts — Export routes registry for the client
58
+ // src/app-type.ts — Export your route registry for the client
59
+ import { getUser } from './routes/users/[userId]';
355
60
  import { listUsers, createUser } from './routes/users';
356
- import { getUser, updateUser, deleteUser } from './routes/users/[userId]';
357
61
 
358
62
  export const routes = {
63
+ getUser,
359
64
  listUsers,
360
65
  createUser,
361
- getUser,
362
- updateUser,
363
- deleteUser,
364
66
  } as const;
365
67
  ```
366
68
 
367
- #### SSE Routes with the Factory
368
-
369
69
  ```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
- },
398
- });
399
-
400
- // Client usage:
401
- // const stream = await client.$sse.getJobStream({ params: { jobId: '123' } });
402
- // stream.on('progress', (data) => console.log(data.percent));
403
- ```
70
+ // client.ts — Full autocomplete, zero configuration
71
+ import { createClient } from '@blaizejs/client';
72
+ import { routes } from './server/app-type';
404
73
 
405
- **SSE Handler Signature:**
74
+ // Create client with URL and routes registry
75
+ const client = createClient('https://api.example.com', routes);
406
76
 
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
77
+ // Methods use the EXPORT NAME — not the path!
78
+ const user = await client.$get.getUser({
79
+ params: { userId: '550e8400-e29b-41d4-a716-446655440000' },
80
+ });
81
+ // ^ user is typed as { id: string; name: string; email: string }
414
82
  ```
415
83
 
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 |
84
+ **Define once. Infer everywhere.** Your IDE knows every route, every parameter, every response shape — automatically.
422
85
 
423
86
  ---
424
87
 
425
- ### Individual Route Creators
88
+ ## 🎯 Why BlaizeJS?
426
89
 
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.
430
-
431
- ```typescript
432
- import {
433
- createGetRoute,
434
- createPostRoute,
435
- createPutRoute,
436
- createPatchRoute,
437
- createDeleteRoute,
438
- createHeadRoute,
439
- createOptionsRoute,
440
- createSSERoute,
441
- } from 'blaizejs';
442
- ```
90
+ ### 🔒 End-to-End Type Safety
443
91
 
444
- #### Example: Using Individual Route Creators
92
+ Types flow from your Zod schemas through your handlers to your client calls. Change a response field and TypeScript catches it everywhere — no manual syncing required.
445
93
 
446
94
  ```typescript
447
- import { createGetRoute } from 'blaizejs';
448
- import { z } from 'zod';
449
-
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
- },
95
+ // Define your schema once
96
+ const userSchema = z.object({
97
+ id: z.string(),
98
+ name: z.string(),
99
+ role: z.enum(['admin', 'user']), // Add a field here...
464
100
  });
465
- ```
466
-
467
- ---
468
-
469
- ## 🔗 Middleware
470
-
471
- ### createMiddleware
472
-
473
- Create middleware with typed state and service additions.
474
101
 
475
- ```typescript
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;
102
+ export const listUsers = route.get({
103
+ schema: { response: z.array(userSchema) },
104
+ handler: async () => getUsers(),
483
105
  });
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
106
 
495
- #### Type Parameters
496
-
497
- | Parameter | Description |
498
- |-----------|-------------|
499
- | `TState` | Properties added to `ctx.state` |
500
- | `TServices` | Properties added to `ctx.services` |
107
+ // Export to routes registry, client automatically knows about `role`
108
+ const users = await client.$get.listUsers();
109
+ users[0].role; // Autocomplete: 'admin' | 'user'
110
+ ```
501
111
 
502
- #### Examples
112
+ ### 📡 Real-Time Built In
503
113
 
504
- **Logging Middleware:**
114
+ Server-Sent Events with typed event schemas. Stream data to clients with the same type safety as your REST endpoints.
505
115
 
506
116
  ```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)`);
117
+ // Server: Stream job progress
118
+ export const getJobStatus = route.sse({
119
+ schema: {
120
+ query: z.object({ jobId: z.string() }),
121
+ events: {
122
+ progress: z.object({ percent: z.number(), message: z.string() }),
123
+ complete: z.object({ result: z.string() }),
124
+ error: z.object({ code: z.string(), message: z.string() }),
125
+ },
517
126
  },
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
- }
533
-
534
- const authMiddleware = createMiddleware<
535
- { user: User },
536
- { auth: AuthService }
537
- >({
538
- name: 'auth',
539
- handler: async (ctx, next) => {
540
- const token = ctx.request.header('authorization')?.replace('Bearer ', '');
541
-
542
- if (!token) {
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');
551
- }
552
-
553
- await next();
127
+ handler: async ({ stream, ctx }) => {
128
+ stream.send('progress', { percent: 0, message: 'Starting...' });
129
+ // ... do work ...
130
+ stream.send('complete', { result: 'Done!' });
554
131
  },
555
- skip: (ctx) => ctx.request.path === '/health',
556
132
  });
557
133
  ```
558
134
 
559
- **Timing Middleware:**
560
-
561
135
  ```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
- },
136
+ // Client: Typed event listeners (browser only)
137
+ const events = await client.$sse.getJobStatus({ query: { jobId: '123' } });
138
+ events.on('progress', data => {
139
+ console.log(`${data.percent}%: ${data.message}`);
571
140
  });
572
141
  ```
573
142
 
574
- ### createStateMiddleware
575
-
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
143
+ ### ⚙️ Background Jobs with Progress Tracking
590
144
 
591
- Shorthand for middleware that only adds services.
145
+ Built-in job queues with priority scheduling, retries, and real-time progress streaming via SSE.
592
146
 
593
147
  ```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
148
+ // Define a job handler
149
+ const processVideo = async (ctx: JobContext<{ videoId: string }>) => {
150
+ ctx.progress(10, 'Downloading...');
151
+ const video = await download(ctx.data.videoId);
605
152
 
606
- Combine multiple middleware into a single middleware.
153
+ ctx.progress(50, 'Transcoding...');
154
+ const output = await transcode(video);
607
155
 
608
- ```typescript
609
- import { compose } from 'blaizejs';
156
+ ctx.progress(90, 'Uploading...');
157
+ await upload(output);
610
158
 
611
- const combined = compose([
612
- loggingMiddleware,
613
- authMiddleware,
614
- timingMiddleware,
615
- ]);
159
+ return { url: output.url };
160
+ };
616
161
 
617
- const server = createServer({
618
- middleware: [combined],
619
- routesDir: './routes',
162
+ // Queue a job from any route
163
+ const jobId = await ctx.services.queue.add('media', 'process-video', {
164
+ videoId: '123',
620
165
  });
621
166
  ```
622
167
 
623
- ---
624
-
625
- ## 🔌 Plugins
168
+ ### 🛡️ Errors That Make Sense
626
169
 
627
- ### createPlugin
628
-
629
- Create a plugin with lifecycle hooks and service injection.
170
+ 12 semantic error classes that automatically format to proper HTTP responses with correlation IDs for distributed tracing.
630
171
 
631
172
  ```typescript
632
- import { createPlugin } from 'blaizejs';
633
-
634
- const plugin = createPlugin(
635
- name: string,
636
- version: string,
637
- setup: (server: Server, options: TOptions) => void | PluginHooks | Promise,
638
- defaultOptions?: Partial
639
- );
173
+ // Throw semantic errors
174
+ if (!user) {
175
+ throw new NotFoundError('User not found', {
176
+ resourceType: 'user',
177
+ resourceId: userId,
178
+ suggestion: 'Verify the user ID exists',
179
+ });
180
+ }
181
+
182
+ // Automatic HTTP response:
183
+ // {
184
+ // "type": "NOT_FOUND",
185
+ // "title": "User not found",
186
+ // "status": 404,
187
+ // "correlationId": "req_k3x2m1_9z8y7w6v",
188
+ // "timestamp": "2024-01-15T10:30:00.000Z",
189
+ // "details": {
190
+ // "resourceType": "user",
191
+ // "resourceId": "123",
192
+ // "suggestion": "Verify the user ID exists"
193
+ // }
194
+ // }
640
195
  ```
641
196
 
642
- #### Parameters
197
+ ---
643
198
 
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 |
199
+ ## 🚀 Quick Start
650
200
 
651
- #### Plugin Hooks
201
+ ### Create a New Project
652
202
 
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 |
203
+ The fastest way to get started is with `create-blaize-app`:
660
204
 
661
- #### Examples
205
+ ```bash
206
+ # Using pnpm (recommended)
207
+ pnpm dlx create-blaize-app my-app
662
208
 
663
- **Simple Plugin:**
209
+ # Using npm
210
+ npx create-blaize-app my-app
664
211
 
665
- ```typescript
666
- const helloPlugin = createPlugin(
667
- 'hello',
668
- '1.0.0',
669
- (server) => {
670
- console.log('Hello plugin registered!');
671
-
672
- return {
673
- onServerStart: () => {
674
- console.log('Server started!');
675
- },
676
- };
677
- }
678
- );
679
-
680
- const server = createServer({
681
- plugins: [helloPlugin()],
682
- });
212
+ # Using yarn
213
+ yarn dlx create-blaize-app my-app
683
214
  ```
684
215
 
685
- **Plugin with Services:**
216
+ ```bash
217
+ cd my-app
218
+ pnpm dev
219
+ # 🔥 Server running at https://localhost:7485
220
+ ```
686
221
 
687
- ```typescript
688
- interface DatabaseOptions {
689
- connectionString: string;
690
- poolSize?: number;
691
- }
222
+ ### Verify It Works
692
223
 
693
- const databasePlugin = createPlugin(
694
- 'database',
695
- '1.0.0',
696
- (server, options) => {
697
- let db: Database;
698
-
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
- }));
707
-
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
- ],
729
- });
224
+ ```bash
225
+ curl -k https://localhost:7485/health
226
+ # {"status":"ok","timestamp":1703001234567}
730
227
  ```
731
228
 
732
- ---
229
+ That's it! You have a fully configured BlaizeJS project with TypeScript, file-based routing, and example routes.
733
230
 
734
- ## ⚠️ Error Classes
231
+ <details>
232
+ <summary><strong>📦 Manual Installation</strong></summary>
735
233
 
736
- BlaizeJS provides 12 semantic error classes that automatically format to HTTP responses.
234
+ If you prefer to add BlaizeJS to an existing project:
737
235
 
738
- ### Error Response Format
236
+ ```bash
237
+ # Using pnpm
238
+ pnpm add blaizejs zod
739
239
 
740
- All errors produce this response structure:
240
+ # Using npm
241
+ npm install blaizejs zod
741
242
 
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
- }
243
+ # Using yarn
244
+ yarn add blaizejs zod
751
245
  ```
752
246
 
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
771
-
772
247
  ```typescript
773
- import {
774
- NotFoundError,
775
- ValidationError,
776
- UnauthorizedError,
777
- ForbiddenError,
778
- ConflictError,
779
- RateLimitError,
780
- } from 'blaizejs';
781
-
782
- // Basic usage
783
- throw new NotFoundError('User not found');
784
-
785
- // With details
786
- throw new NotFoundError('User not found', {
787
- resourceType: 'user',
788
- resourceId: userId,
789
- suggestion: 'Verify the user ID exists',
790
- });
791
-
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
- });
248
+ // src/app.ts
249
+ import { Blaize, type InferContext } from 'blaizejs';
250
+ import { fileURLToPath } from 'node:url';
251
+ import path from 'node:path';
799
252
 
800
- // Rate limit with retry info
801
- throw new RateLimitError('Too many requests', {
802
- retryAfter: 60,
803
- limit: 100,
804
- remaining: 0,
805
- });
253
+ const __filename = fileURLToPath(import.meta.url);
254
+ const __dirname = path.dirname(__filename);
806
255
 
807
- // Conflict with version info
808
- throw new ConflictError('Version mismatch', {
809
- currentVersion: 5,
810
- providedVersion: 3,
256
+ const app = Blaize.createServer({
257
+ port: 3000,
258
+ routesDir: path.resolve(__dirname, './routes'),
811
259
  });
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');
819
- ```
820
260
 
821
- ---
822
-
823
- ## 📝 Logging
824
-
825
- ### Global Logger
826
-
827
- ```typescript
828
- import { logger } from 'blaizejs';
261
+ // Create typed route factory
262
+ type AppContext = InferContext<typeof app>;
263
+ export const route = Blaize.Router.createRouteFactory<
264
+ AppContext['state'],
265
+ AppContext['services']
266
+ >();
829
267
 
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' });
268
+ await app.listen();
269
+ console.log('🔥 Server running at https://localhost:3000');
834
270
  ```
835
271
 
836
- ### createLogger
837
-
838
- Create a custom logger with specific transports.
839
-
840
272
  ```typescript
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
- ],
849
- });
850
- ```
851
-
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:
273
+ // src/routes/health.ts
274
+ import { route } from '../app';
275
+ import { z } from 'zod';
863
276
 
864
- ```typescript
865
- export const getUser = route.get({
277
+ export const getHealth = route.get({
866
278
  schema: {
867
- params: z.object({ userId: z.string() }),
868
- },
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 });
876
- return user;
279
+ response: z.object({ status: z.literal('ok'), timestamp: z.number() }),
877
280
  },
281
+ handler: async () => ({
282
+ status: 'ok' as const,
283
+ timestamp: Date.now(),
284
+ }),
878
285
  });
879
286
  ```
880
287
 
881
- ### configureGlobalLogger
882
-
883
- Configure the global logger instance.
884
-
885
288
  ```typescript
886
- import { configureGlobalLogger, JSONTransport } from 'blaizejs';
289
+ // src/app-type.ts Export routes registry for the client
290
+ import { getHealth } from './routes/health';
887
291
 
888
- configureGlobalLogger({
889
- level: process.env.LOG_LEVEL || 'info',
890
- transports: [
891
- new JSONTransport({ pretty: process.env.NODE_ENV !== 'production' }),
892
- ],
893
- });
292
+ export const routes = {
293
+ getHealth,
294
+ } as const;
894
295
  ```
895
296
 
896
- ---
897
-
898
- ## 🛠️ Utilities
899
-
900
- ### getCorrelationId
901
-
902
- Get the current request's correlation ID (from AsyncLocalStorage).
903
-
904
- ```typescript
905
- import { getCorrelationId } from 'blaizejs';
906
-
907
- function someDeepFunction() {
908
- const correlationId = getCorrelationId();
909
- console.log(`Processing request ${correlationId}`);
910
- }
911
- ```
297
+ </details>
912
298
 
913
- ### cors
299
+ ### Add a Type-Safe Client
914
300
 
915
- CORS middleware for cross-origin requests.
301
+ Connect to your API with full type inference:
916
302
 
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',
931
- });
303
+ ```bash
304
+ pnpm add @blaizejs/client
932
305
  ```
933
306
 
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) |
944
-
945
- #### Examples
946
-
947
- **Multiple Origins:**
948
-
949
307
  ```typescript
950
- cors({
951
- origin: ['https://app.example.com', 'https://admin.example.com'],
952
- });
953
- ```
308
+ // client.ts
309
+ import { createClient } from '@blaizejs/client';
310
+ import { routes } from './server/app-type';
954
311
 
955
- **Dynamic Origin:**
312
+ // Create client with URL and routes registry
313
+ const client = createClient('https://localhost:3000', routes);
956
314
 
957
- ```typescript
958
- cors({
959
- origin: (origin) => {
960
- return origin?.endsWith('.example.com') ?? false;
961
- },
962
- });
315
+ // Methods use the EXPORT NAME from your routes
316
+ const health = await client.$get.getHealth();
317
+ console.log(health.status); // ✅ Typed as 'ok'
318
+ console.log(health.timestamp); // ✅ Typed as number
963
319
  ```
964
320
 
965
321
  ---
966
322
 
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:
323
+ ## 📦 Ecosystem
324
+
325
+ | Package | Version | Description | Status |
326
+ |---------|---------|-------------|--------|
327
+ | [`blaizejs`](./packages/blaize-core) | 0.9.2 | Core framework | 🟡 Beta |
328
+ | [`@blaizejs/client`](./packages/blaize-client) | 0.5.1 | Type-safe RPC client | 🟡 Beta |
329
+ | [`@blaizejs/plugin-queue`](./plugins/queue) | 2.0.0 | Background job processing with Redis | 🟢 Stable |
330
+ | [`@blaizejs/plugin-cache`](./plugins/cache) | 2.0.0 | Caching with memory/Redis adapters | 🟢 Stable |
331
+ | [`@blaizejs/plugin-metrics`](./plugins/metrics) | 4.0.0 | Prometheus metrics & dashboard | 🟡 Beta |
332
+ | [`@blaizejs/middleware-security`](./middleware/security) | 4.0.0 | Security headers (CSP, HSTS) | 🟡 Beta |
333
+ | [`@blaizejs/testing-utils`](./packages/blaize-testing-utils) | 0.2.0 | Test helpers & mocks | 🟡 Beta |
334
+ | [`@blaizejs/adapter-redis`](./adapters/redis) | 2.0.0 | Redis adapter for queue & cache | 🟢 Stable |
335
+ | `create-blaize-app` | 0.1.21 | Project scaffolding CLI | 🟡 Beta |
336
+
337
+ ### 🔮 Coming Soon
338
+
339
+ | Package | Description |
340
+ |---------|-------------|
341
+ | `@blaizejs/plugin-storage` | File storage abstraction (S3, local, etc.) |
342
+ | `@blaizejs/plugin-db` | Database integration with migrations |
343
+ | `@blaizejs/plugin-rate-limit` | Flexible rate limiting |
344
+ | `@blaizejs/middleware-compression` | Response compression |
345
+ | `@blaizejs/plugin-auth` | Authentication strategies |
1005
346
 
1006
- ```typescript
1007
- // Added by authMiddleware
1008
- ctx.state.user // User object
347
+ ---
1009
348
 
1010
- // Added by timingMiddleware
1011
- ctx.state.startTime // number
1012
- ```
349
+ ## 📚 Documentation
1013
350
 
1014
- ### ctx.services
351
+ ### Getting Started
1015
352
 
1016
- Plugin-injected services:
353
+ - [Quick Start](#-quick-start) — Zero to API in 5 minutes
354
+ - [30-Minute Tutorial](./docs/getting-started/tutorial.md) — Build a complete task API
355
+ - [Architecture Overview](./ARCHITECTURE.md) — How BlaizeJS works under the hood
1017
356
 
1018
- ```typescript
1019
- // Added by databasePlugin
1020
- ctx.services.db // Database instance
357
+ ### Core Guides
1021
358
 
1022
- // Added by cachePlugin
1023
- ctx.services.cache // CacheService
359
+ - [Routing Guide](./docs/guides/routing.md) — File-based routing patterns
360
+ - [Middleware Guide](./docs/guides/middleware.md) — Request processing pipeline
361
+ - [Plugins Guide](./docs/guides/plugins.md) — Resource management & lifecycle
362
+ - [Error Handling Guide](./docs/guides/error-handling.md) — Semantic errors & debugging
363
+ - [Real-Time Guide](./docs/guides/real-time.md) — SSE and Event Bus
364
+ - [Client Guide](./docs/guides/client.md) — Type-safe RPC & SSE client
365
+ - [Testing Guide](./docs/guides/testing.md) — Test your BlaizeJS apps
1024
366
 
1025
- // Added by queuePlugin
1026
- ctx.services.queue // QueueService
1027
- ```
367
+ ### API Reference
1028
368
 
1029
- > 🔒 **Note:** `createContext` is internal and not exported. For testing, use `createTestContext` from `@blaizejs/testing-utils`.
369
+ - [`blaizejs`](./packages/blaize-core/README.md) Core framework API
370
+ - [`@blaizejs/client`](./packages/blaize-client/README.md) — Client SDK API
371
+ - [`@blaizejs/plugin-queue`](./plugins/queue/README.md) — Queue plugin API
372
+ - [`@blaizejs/plugin-cache`](./plugins/cache/README.md) — Cache plugin API
373
+ - [`@blaizejs/testing-utils`](./packages/blaize-testing-utils/README.md) — Testing utilities API
1030
374
 
1031
375
  ---
1032
376
 
1033
- ## 🧪 Testing
377
+ ## 🗺️ Roadmap
1034
378
 
1035
- BlaizeJS integrates with [Vitest](https://vitest.dev/) through the `@blaizejs/testing-utils` package.
379
+ ### v0.9.2 (Current)
1036
380
 
1037
- ### Quick Setup
381
+ - Event Bus for distributed coordination
382
+ - ✅ Redis adapter for queue and cache
383
+ - ✅ Type-safe file handling
384
+ - ✅ Enhanced SSE streaming
385
+ - ✅ BlaizeLogger as first-class citizen
386
+ - ✅ Correlation IDs for distributed tracing
1038
387
 
1039
- ```bash
1040
- pnpm add -D vitest @blaizejs/testing-utils
1041
- ```
388
+ ### 🎯 v1.0 (Stable Release)
1042
389
 
1043
- ```typescript
1044
- // vitest.config.ts
1045
- import { defineConfig } from 'vitest/config';
390
+ - [ ] Rate limiting plugin
391
+ - [ ] Compression middleware
392
+ - [ ] Database plugin with migrations
393
+ - [ ] Storage plugin (S3, local)
394
+ - [ ] OpenAPI/Swagger generation
395
+ - [ ] Authentication plugin
396
+ - [ ] Production deployment guides
1046
397
 
1047
- export default defineConfig({
1048
- test: {
1049
- globals: true,
1050
- },
1051
- });
1052
- ```
398
+ ### 🔮 Future (Post-1.0)
1053
399
 
1054
- ### Testing Routes
400
+ - [ ] Edge runtime support
401
+ - [ ] WebSocket support (bidirectional real-time)
402
+ - [ ] External queue workers
403
+ - [ ] GraphQL integration
404
+ - [ ] gRPC-Web support
405
+ - [ ] Distributed tracing (OpenTelemetry)
406
+ - [ ] AI-powered route generation
1055
407
 
1056
- ```typescript
1057
- import { describe, it, expect } from 'vitest';
1058
- import { createTestContext, createMockLogger } from '@blaizejs/testing-utils';
1059
- import { GET } from './routes/users/[userId]';
1060
-
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
- ```
408
+ [View full roadmap →](https://github.com/jleajones/blaize/projects)
1088
409
 
1089
- ### Testing Middleware
410
+ ---
1090
411
 
1091
- ```typescript
1092
- import { describe, it, expect, vi } from 'vitest';
1093
- import { createTestContext } from '@blaizejs/testing-utils';
1094
- import { authMiddleware } from './middleware/auth';
1095
-
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
- });
1121
- ```
412
+ ## 🌟 Key Features
1122
413
 
1123
- ### Mocking Services
414
+ ### Type Safety Everywhere
1124
415
 
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
- });
1157
- ```
416
+ - **Zero runtime overhead** — Types are compile-time only
417
+ - **No code generation** Pure TypeScript inference
418
+ - **Zod integration** Runtime validation + type inference
419
+ - **End-to-end types** Server schemas → Client calls
1158
420
 
1159
- ### Mock Logger
421
+ ### Developer Experience
1160
422
 
1161
- ```typescript
1162
- import { createMockLogger } from '@blaizejs/testing-utils';
423
+ - **File-based routing** — URL structure mirrors your files
424
+ - **Hot reload** Changes reflect instantly
425
+ - **Full autocomplete** — Your IDE knows everything
426
+ - **Clear errors** — Helpful error messages with suggestions
1163
427
 
1164
- const logger = createMockLogger();
428
+ ### Production Ready
1165
429
 
1166
- // Use in tests
1167
- await handler(ctx, params, logger);
430
+ - **HTTP/2 native** — Better performance by default
431
+ - **Correlation IDs** — Track requests across services
432
+ - **Structured logging** — BlaizeLogger with context
433
+ - **Semantic errors** — 12 error types with automatic HTTP formatting
434
+ - **Redis adapters** — Production-ready queue & cache
1168
435
 
1169
- // Assert logs
1170
- expect(logger.logs).toContainEqual({
1171
- level: 'info',
1172
- message: 'User created',
1173
- meta: expect.objectContaining({ userId: '123' }),
1174
- });
1175
- ```
436
+ ### Real-Time & Background Jobs
1176
437
 
1177
- See [`@blaizejs/testing-utils`](../blaize-testing-utils/README.md) for the full testing API.
438
+ - **Server-Sent Events** Type-safe streaming
439
+ - **Event Bus** — Distributed coordination
440
+ - **Job queues** — Priority scheduling with retries
441
+ - **Progress tracking** — Stream job updates via SSE
1178
442
 
1179
443
  ---
1180
444
 
1181
- ## 🗺️ Roadmap
445
+ ## 🤝 Contributing
1182
446
 
1183
- ### 🎯 v1.0 (Stable)
447
+ We welcome contributions! BlaizeJS is built by developers, for developers.
1184
448
 
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
449
+ - 🐛 **Found a bug?** [Open an issue](https://github.com/jleajones/blaize/issues)
450
+ - 💡 **Have an idea?** [Start a discussion](https://github.com/jleajones/blaize/discussions)
451
+ - 🔧 **Want to contribute?** See our [Contributing Guide](./CONTRIBUTING.md)
1191
452
 
1192
- ### 🔮 Future
453
+ ### Development Setup
1193
454
 
1194
- - [ ] Authentication plugin (`@blaizejs/plugin-auth`)
1195
- - [ ] Edge runtime support
1196
- - [ ] External queue workers
1197
- - [ ] HTTP/2 hosting solutions
1198
- - [ ] Deeper AI integrations
455
+ ```bash
456
+ git clone https://github.com/jleajones/blaize.git
457
+ cd blaize
458
+ pnpm install
459
+ pnpm test
460
+ pnpm build
461
+ ```
1199
462
 
1200
463
  ---
1201
464
 
1202
- ## 📚 Related
465
+ ## 📄 License
1203
466
 
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
467
+ MIT © [BlaizeJS Contributors](https://github.com/jleajones/blaize/graphs/contributors)
1208
468
 
1209
469
  ---
1210
470
 
1211
- ## 📄 License
471
+ ## 🙏 Acknowledgments
1212
472
 
1213
- MIT © [BlaizeJS Contributors](https://github.com/jleajones/blaize/graphs/contributors)
473
+ BlaizeJS stands on the shoulders of giants:
474
+
475
+ - **[tRPC](https://trpc.io/)** — Inspiration for type-safe RPC
476
+ - **[Hono](https://hono.dev/)** — Influence on middleware patterns
477
+ - **[Fastify](https://fastify.io/)** — Performance benchmarks
478
+ - **[Zod](https://zod.dev/)** — Schema validation excellence
1214
479
 
1215
480
  ---
1216
481
 
1217
- **Built with ❤️ by the BlaizeJS team**
482
+ <p align="center">
483
+ <strong>Built with ❤️ by the BlaizeJS team</strong>
484
+ <br>
485
+ <a href="https://github.com/jleajones/blaize">GitHub</a> •
486
+ <a href="https://discord.gg/blaizejs">Discord</a> •
487
+ <a href="https://twitter.com/blaizejs">Twitter</a>
488
+ </p>