vector-framework 0.9.7 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +516 -346
- package/dist/cli/index.js +63 -64
- package/dist/cli/index.js.map +1 -1
- package/dist/cli.js +480 -424
- package/dist/core/router.d.ts +1 -0
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/router.js +73 -25
- package/dist/core/router.js.map +1 -1
- package/dist/core/vector.d.ts.map +1 -1
- package/dist/core/vector.js +8 -4
- package/dist/core/vector.js.map +1 -1
- package/dist/dev/route-scanner.d.ts +5 -2
- package/dist/dev/route-scanner.d.ts.map +1 -1
- package/dist/dev/route-scanner.js +63 -16
- package/dist/dev/route-scanner.js.map +1 -1
- package/dist/http.d.ts +2 -2
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +3 -4
- package/dist/http.js.map +1 -1
- package/dist/index.js +7 -7
- package/dist/index.mjs +7 -7
- package/dist/middleware/manager.d.ts.map +1 -1
- package/dist/middleware/manager.js +8 -1
- package/dist/middleware/manager.js.map +1 -1
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -2
- package/src/cli/index.ts +78 -78
- package/src/core/router.ts +85 -27
- package/src/core/vector.ts +9 -4
- package/src/dev/route-scanner.ts +76 -18
- package/src/http.ts +8 -13
- package/src/middleware/manager.ts +7 -1
- package/src/types/index.ts +5 -0
package/README.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# Vector Framework
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Blazing fast, secure, and developer-friendly API framework for Bun**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- **70,000+ requests/second** - Optimized for extreme performance
|
|
6
|
+
- **Single dependency** - Only itty-router, minimizing security risks
|
|
7
|
+
- **Zero build step** - Native TypeScript execution with Bun
|
|
8
|
+
- **Encore-like DX** - Declarative, type-safe APIs you'll love
|
|
6
9
|
|
|
7
10
|
## Quick Start
|
|
8
11
|
|
|
@@ -12,9 +15,9 @@ Vector brings the blazing performance of Bun to developers who appreciate the si
|
|
|
12
15
|
bun add vector-framework
|
|
13
16
|
```
|
|
14
17
|
|
|
15
|
-
###
|
|
18
|
+
### 1. Configure Your App
|
|
16
19
|
|
|
17
|
-
Create
|
|
20
|
+
Create `vector.config.ts` in your project root:
|
|
18
21
|
|
|
19
22
|
```typescript
|
|
20
23
|
// vector.config.ts
|
|
@@ -22,507 +25,674 @@ import type { VectorConfigSchema } from "vector-framework";
|
|
|
22
25
|
|
|
23
26
|
const config: VectorConfigSchema = {
|
|
24
27
|
// Server configuration
|
|
25
|
-
port: 3000,
|
|
26
|
-
hostname: "localhost",
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
reusePort: true, // Reuse port (default: true)
|
|
30
|
-
idleTimeout: 60, // Idle timeout in seconds (default: 60)
|
|
28
|
+
port: 3000,
|
|
29
|
+
hostname: "localhost",
|
|
30
|
+
development: process.env.NODE_ENV !== "production",
|
|
31
|
+
routesDir: "./routes", // Auto-discovers routes here
|
|
31
32
|
|
|
32
33
|
// CORS configuration
|
|
33
34
|
cors: {
|
|
34
|
-
origin: "*",
|
|
35
|
-
credentials: true,
|
|
35
|
+
origin: "*",
|
|
36
|
+
credentials: true,
|
|
36
37
|
allowHeaders: ["Content-Type", "Authorization"],
|
|
37
38
|
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
38
|
-
exposeHeaders: ["X-Total-Count"],
|
|
39
|
-
maxAge: 86400, // Preflight cache duration in seconds
|
|
40
39
|
},
|
|
40
|
+
|
|
41
|
+
// Authentication handler
|
|
42
|
+
auth: async (request) => {
|
|
43
|
+
const token = request.headers.get("Authorization")?.replace("Bearer ", "");
|
|
44
|
+
if (token === "valid-token") {
|
|
45
|
+
return { id: "user-123", email: "user@example.com" };
|
|
46
|
+
}
|
|
47
|
+
throw new Error("Invalid token");
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
// Optional: Cache handler (Redis example)
|
|
51
|
+
cache: async (key, factory, ttl) => {
|
|
52
|
+
// Your caching logic here
|
|
53
|
+
const cached = await redis.get(key);
|
|
54
|
+
if (cached) return JSON.parse(cached);
|
|
55
|
+
|
|
56
|
+
const value = await factory();
|
|
57
|
+
await redis.setex(key, ttl, JSON.stringify(value));
|
|
58
|
+
return value;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
// Optional: Middleware
|
|
62
|
+
before: [
|
|
63
|
+
async (request) => {
|
|
64
|
+
console.log(`${request.method} ${request.url}`);
|
|
65
|
+
return request;
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
after: [
|
|
69
|
+
async (response, request) => {
|
|
70
|
+
const duration = Date.now() - (request.startTime || Date.now());
|
|
71
|
+
response.headers.set("X-Response-Time", `${duration}ms`);
|
|
72
|
+
return response;
|
|
73
|
+
}
|
|
74
|
+
],
|
|
41
75
|
};
|
|
42
76
|
|
|
43
77
|
export default config;
|
|
44
78
|
```
|
|
45
79
|
|
|
46
|
-
### Your First
|
|
80
|
+
### 2. Create Your First Route
|
|
47
81
|
|
|
48
82
|
```typescript
|
|
49
83
|
// routes/hello.ts
|
|
50
84
|
import { route } from "vector-framework";
|
|
51
85
|
|
|
52
|
-
//
|
|
86
|
+
// Simple public endpoint
|
|
53
87
|
export const hello = route(
|
|
54
88
|
{
|
|
55
89
|
method: "GET",
|
|
56
90
|
path: "/hello/:name",
|
|
91
|
+
expose: true, // Public endpoint (default: true)
|
|
57
92
|
},
|
|
58
93
|
async (req) => {
|
|
59
|
-
|
|
60
|
-
return { message: `Hello ${name}!` };
|
|
94
|
+
return { message: `Hello ${req.params.name}!` };
|
|
61
95
|
}
|
|
62
96
|
);
|
|
63
97
|
|
|
64
|
-
// Protected endpoint - auth
|
|
98
|
+
// Protected endpoint - uses auth from config
|
|
65
99
|
export const getProfile = route(
|
|
66
100
|
{
|
|
67
101
|
method: "GET",
|
|
68
102
|
path: "/profile",
|
|
69
|
-
auth: true, //
|
|
103
|
+
auth: true, // Requires authentication
|
|
104
|
+
expose: true,
|
|
70
105
|
},
|
|
71
106
|
async (req) => {
|
|
72
107
|
return {
|
|
73
|
-
user: req.authUser,
|
|
74
|
-
|
|
108
|
+
user: req.authUser, // Typed from your auth handler
|
|
109
|
+
timestamp: new Date(),
|
|
75
110
|
};
|
|
76
111
|
}
|
|
77
112
|
);
|
|
78
|
-
```
|
|
79
113
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return {
|
|
114
|
+
// Cached endpoint
|
|
115
|
+
export const getUsers = route(
|
|
116
|
+
{
|
|
117
|
+
method: "GET",
|
|
118
|
+
path: "/users",
|
|
119
|
+
cache: 300, // Cache for 5 minutes
|
|
120
|
+
expose: true,
|
|
121
|
+
},
|
|
122
|
+
async () => {
|
|
123
|
+
// Expensive operation, will be cached
|
|
124
|
+
const users = await db.users.findMany();
|
|
125
|
+
return { users };
|
|
92
126
|
}
|
|
93
|
-
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
// Start server - routes auto-discovered from ./routes
|
|
97
|
-
vector.serve({ port: 3000 });
|
|
127
|
+
);
|
|
98
128
|
```
|
|
99
129
|
|
|
100
|
-
###
|
|
130
|
+
### 3. Start Your Server
|
|
101
131
|
|
|
102
132
|
```bash
|
|
103
|
-
#
|
|
133
|
+
# Development mode with hot reload
|
|
104
134
|
bun vector dev
|
|
105
135
|
|
|
106
|
-
#
|
|
136
|
+
# Production mode
|
|
107
137
|
bun vector start
|
|
108
138
|
|
|
109
|
-
#
|
|
110
|
-
bun vector
|
|
111
|
-
|
|
112
|
-
# Run with custom options
|
|
113
|
-
bun vector dev --port 3000 --routes ./api
|
|
139
|
+
# With custom options
|
|
140
|
+
bun vector dev --port 4000 --routes ./api
|
|
114
141
|
```
|
|
115
142
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
If you've been looking for Encore-like developer experience with Bun's performance, Vector is your answer. Define your routes declaratively, enjoy automatic type safety, and ship faster than ever.
|
|
143
|
+
That's it! Your API is running at `http://localhost:3000` 🎉
|
|
119
144
|
|
|
120
|
-
##
|
|
145
|
+
## TypeScript Type Safety
|
|
121
146
|
|
|
122
|
-
|
|
123
|
-
- **Type-Safe** - Full TypeScript support with excellent type inference
|
|
124
|
-
- **Auto Route Discovery** - Automatically discovers and loads routes from your filesystem
|
|
125
|
-
- **Middleware System** - Flexible pre/post request middleware pipeline
|
|
126
|
-
- **Built-in Authentication** - Simple but powerful authentication system
|
|
127
|
-
- **Response Caching** - Automatic response caching with configurable TTL
|
|
128
|
-
- **CORS Support** - Configurable CORS with sensible defaults
|
|
129
|
-
- **Developer Experience** - Auto route discovery and CLI tools
|
|
147
|
+
Vector provides full type safety with customizable types. Define your types in the config and use them in routes:
|
|
130
148
|
|
|
131
|
-
|
|
149
|
+
```typescript
|
|
150
|
+
// vector.config.ts
|
|
151
|
+
import type { VectorConfigSchema, VectorTypes } from "vector-framework";
|
|
132
152
|
|
|
133
|
-
|
|
153
|
+
// Define your custom user type
|
|
154
|
+
interface MyUser {
|
|
155
|
+
id: string;
|
|
156
|
+
email: string;
|
|
157
|
+
role: "admin" | "user";
|
|
158
|
+
permissions: string[];
|
|
159
|
+
}
|
|
134
160
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
161
|
+
// Extend Vector types
|
|
162
|
+
interface MyAppTypes extends VectorTypes {
|
|
163
|
+
auth: MyUser;
|
|
164
|
+
}
|
|
139
165
|
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
method: "GET",
|
|
144
|
-
path: "/users",
|
|
145
|
-
cache: 300, // Cache for 5 minutes
|
|
146
|
-
},
|
|
147
|
-
async () => {
|
|
148
|
-
const users = await db.user.findMany();
|
|
149
|
-
return { users };
|
|
150
|
-
}
|
|
151
|
-
);
|
|
166
|
+
// Use in config with type parameter
|
|
167
|
+
const config: VectorConfigSchema<MyAppTypes> = {
|
|
168
|
+
port: 3000,
|
|
152
169
|
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
170
|
+
// Auth handler returns your custom type
|
|
171
|
+
auth: async (request): Promise<MyUser> => {
|
|
172
|
+
// Your auth logic
|
|
173
|
+
return {
|
|
174
|
+
id: "user-123",
|
|
175
|
+
email: "user@example.com",
|
|
176
|
+
role: "admin",
|
|
177
|
+
permissions: ["read", "write"],
|
|
178
|
+
};
|
|
159
179
|
},
|
|
160
|
-
|
|
161
|
-
const { name, email } = req.content; // Type-safe, auto-parsed
|
|
180
|
+
};
|
|
162
181
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
182
|
+
export default config;
|
|
183
|
+
export type { MyAppTypes }; // Export for use in routes
|
|
184
|
+
```
|
|
166
185
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
186
|
+
```typescript
|
|
187
|
+
// routes/admin.ts
|
|
188
|
+
import { route, APIError } from "vector-framework";
|
|
189
|
+
import type { MyAppTypes } from "../vector.config";
|
|
170
190
|
|
|
171
|
-
//
|
|
172
|
-
export const
|
|
191
|
+
// Use type parameter to get fully typed request
|
|
192
|
+
export const adminOnly = route<MyAppTypes>(
|
|
173
193
|
{
|
|
174
194
|
method: "GET",
|
|
175
|
-
path: "/
|
|
195
|
+
path: "/admin/data",
|
|
196
|
+
auth: true,
|
|
197
|
+
expose: true,
|
|
176
198
|
},
|
|
177
199
|
async (req) => {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (!user) {
|
|
182
|
-
throw new APIError("User not found", 404);
|
|
200
|
+
// req.authUser is now typed as MyUser
|
|
201
|
+
if (req.authUser?.role !== "admin") {
|
|
202
|
+
throw APIError.forbidden("Admin access required");
|
|
183
203
|
}
|
|
184
204
|
|
|
185
|
-
|
|
205
|
+
// TypeScript knows these properties exist
|
|
206
|
+
return {
|
|
207
|
+
user: req.authUser.email,
|
|
208
|
+
permissions: req.authUser.permissions,
|
|
209
|
+
};
|
|
186
210
|
}
|
|
187
211
|
);
|
|
188
212
|
```
|
|
189
213
|
|
|
190
|
-
##
|
|
191
|
-
|
|
192
|
-
### 🚀 Bun-Powered Performance
|
|
214
|
+
## Core Features
|
|
193
215
|
|
|
194
|
-
|
|
195
|
-
- **Significantly faster** startup times and request handling
|
|
196
|
-
- **Minimal memory footprint** thanks to Bun's efficient runtime
|
|
216
|
+
### Route Options
|
|
197
217
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
218
|
+
```typescript
|
|
219
|
+
interface RouteOptions {
|
|
220
|
+
method: string; // HTTP method (GET, POST, etc.)
|
|
221
|
+
path: string; // Route path with params (/users/:id)
|
|
222
|
+
expose?: boolean; // Make route accessible (default: true)
|
|
223
|
+
auth?: boolean; // Require authentication
|
|
224
|
+
cache?: number | { // Cache configuration
|
|
225
|
+
ttl: number; // Time to live in seconds
|
|
226
|
+
key?: string; // Custom cache key
|
|
227
|
+
};
|
|
228
|
+
rawRequest?: boolean; // Skip body parsing
|
|
229
|
+
rawResponse?: boolean; // Return raw response
|
|
230
|
+
responseContentType?: string; // Response content type
|
|
231
|
+
}
|
|
232
|
+
```
|
|
204
233
|
|
|
205
|
-
###
|
|
234
|
+
### Request Object
|
|
206
235
|
|
|
207
|
-
|
|
236
|
+
Every route handler receives a typed request object:
|
|
208
237
|
|
|
209
238
|
```typescript
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
{ expose: true, method: "GET", path: "/hello/:name" },
|
|
213
|
-
async ({ name }: { name: string }): Promise<Response> => {
|
|
214
|
-
const msg = `Hello ${name}!`;
|
|
215
|
-
return { message: msg };
|
|
216
|
-
}
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
// Vector-style (what you write)
|
|
220
|
-
export const hello = route(
|
|
221
|
-
{ expose: true, method: "GET", path: "/hello/:name" },
|
|
239
|
+
export const example = route(
|
|
240
|
+
{ method: "POST", path: "/example/:id" },
|
|
222
241
|
async (req) => {
|
|
223
|
-
|
|
242
|
+
// All available request properties:
|
|
243
|
+
req.params.id; // URL parameters
|
|
244
|
+
req.query.search; // Query parameters
|
|
245
|
+
req.headers; // Request headers
|
|
246
|
+
req.cookies; // Parsed cookies
|
|
247
|
+
req.content; // Parsed body (JSON/form data)
|
|
248
|
+
req.authUser; // Authenticated user (when auth: true)
|
|
249
|
+
req.context; // Request context
|
|
250
|
+
req.metadata; // Route metadata
|
|
224
251
|
}
|
|
225
252
|
);
|
|
226
253
|
```
|
|
227
254
|
|
|
228
|
-
|
|
255
|
+
### Error Handling
|
|
229
256
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
| Encore | Vector |
|
|
233
|
-
| ---------------------- | -------------------------------- |
|
|
234
|
-
| `api.requireAuth()` | `{ auth: true }` in route config |
|
|
235
|
-
| Auto-generated clients | Not yet available |
|
|
236
|
-
| Built-in tracing | Middleware support |
|
|
237
|
-
| Cloud deployment | Deploy anywhere Bun runs |
|
|
238
|
-
|
|
239
|
-
**The key difference:** Vector runs on Bun, giving you significantly better performance and lower resource usage while maintaining the developer experience you love.
|
|
240
|
-
|
|
241
|
-
## Route Options
|
|
257
|
+
Vector provides comprehensive error responses:
|
|
242
258
|
|
|
243
259
|
```typescript
|
|
244
|
-
|
|
245
|
-
method: string; // HTTP method (GET, POST, etc.)
|
|
246
|
-
path: string; // Route path with params (/users/:id)
|
|
247
|
-
expose?: boolean; // Make route accessible (default: true)
|
|
248
|
-
auth?: boolean; // Require authentication (default: false)
|
|
249
|
-
cache?:
|
|
250
|
-
| number
|
|
251
|
-
| {
|
|
252
|
-
// Cache configuration
|
|
253
|
-
ttl: number; // Time to live in seconds
|
|
254
|
-
key?: string; // Custom cache key
|
|
255
|
-
};
|
|
256
|
-
rawRequest?: boolean; // Skip body parsing (default: false)
|
|
257
|
-
rawResponse?: boolean; // Return raw response (default: false)
|
|
258
|
-
responseContentType?: string; // Response content type
|
|
259
|
-
}
|
|
260
|
-
```
|
|
260
|
+
import { APIError } from "vector-framework";
|
|
261
261
|
|
|
262
|
-
|
|
262
|
+
export const example = route(
|
|
263
|
+
{ method: "GET", path: "/data/:id" },
|
|
264
|
+
async (req) => {
|
|
265
|
+
// Client errors (4xx)
|
|
266
|
+
if (!req.params.id) {
|
|
267
|
+
throw APIError.badRequest("ID is required");
|
|
268
|
+
}
|
|
263
269
|
|
|
264
|
-
|
|
270
|
+
const data = await findData(req.params.id);
|
|
271
|
+
if (!data) {
|
|
272
|
+
throw APIError.notFound("Data not found");
|
|
273
|
+
}
|
|
265
274
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
275
|
+
if (!canAccess(req.authUser, data)) {
|
|
276
|
+
throw APIError.forbidden("Access denied");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Rate limiting
|
|
280
|
+
if (await isRateLimited(req)) {
|
|
281
|
+
throw APIError.tooManyRequests("Please wait before trying again");
|
|
282
|
+
}
|
|
272
283
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
284
|
+
// Server errors (5xx)
|
|
285
|
+
try {
|
|
286
|
+
return await processData(data);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
throw APIError.internalServerError("Processing failed");
|
|
289
|
+
}
|
|
277
290
|
}
|
|
278
|
-
|
|
279
|
-
};
|
|
291
|
+
);
|
|
280
292
|
```
|
|
281
293
|
|
|
282
|
-
|
|
294
|
+
## Configuration Reference
|
|
295
|
+
|
|
296
|
+
### VectorConfigSchema
|
|
283
297
|
|
|
284
298
|
```typescript
|
|
285
|
-
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
299
|
+
interface VectorConfigSchema {
|
|
300
|
+
// Server
|
|
301
|
+
port?: number; // Server port (default: 3000)
|
|
302
|
+
hostname?: string; // Server hostname (default: localhost)
|
|
303
|
+
reusePort?: boolean; // Reuse port (default: true)
|
|
304
|
+
development?: boolean; // Development mode
|
|
305
|
+
routesDir?: string; // Routes directory (default: ./routes)
|
|
306
|
+
routeExcludePatterns?: string[]; // Patterns to exclude from route scanning
|
|
307
|
+
idleTimeout?: number; // Idle timeout in seconds
|
|
308
|
+
|
|
309
|
+
// CORS
|
|
310
|
+
cors?: CorsOptions | boolean;
|
|
311
|
+
|
|
312
|
+
// Handlers
|
|
313
|
+
auth?: ProtectedHandler; // Authentication handler
|
|
314
|
+
cache?: CacheHandler; // Cache handler
|
|
315
|
+
|
|
316
|
+
// Middleware
|
|
317
|
+
before?: BeforeMiddleware[]; // Pre-request middleware
|
|
318
|
+
after?: AfterMiddleware[]; // Post-response middleware
|
|
319
|
+
}
|
|
290
320
|
```
|
|
291
321
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
Implement your authentication logic:
|
|
322
|
+
### Example: Full Configuration
|
|
295
323
|
|
|
296
324
|
```typescript
|
|
297
|
-
vector.
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
throw new Error("Invalid authorization header");
|
|
302
|
-
}
|
|
325
|
+
// vector.config.ts
|
|
326
|
+
import type { VectorConfigSchema } from "vector-framework";
|
|
327
|
+
import { verifyJWT } from "./lib/auth";
|
|
328
|
+
import { redis } from "./lib/redis";
|
|
303
329
|
|
|
304
|
-
|
|
305
|
-
|
|
330
|
+
const config: VectorConfigSchema = {
|
|
331
|
+
port: process.env.PORT || 3000,
|
|
332
|
+
hostname: "0.0.0.0",
|
|
333
|
+
development: process.env.NODE_ENV !== "production",
|
|
334
|
+
routesDir: "./api/routes",
|
|
335
|
+
routeExcludePatterns: ["*.test.ts", "*.spec.ts"], // Optional: custom exclusions
|
|
336
|
+
idleTimeout: 60,
|
|
306
337
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
338
|
+
cors: {
|
|
339
|
+
origin: ["https://example.com", "https://app.example.com"],
|
|
340
|
+
credentials: true,
|
|
341
|
+
allowHeaders: ["Content-Type", "Authorization", "X-Request-ID"],
|
|
342
|
+
allowMethods: ["GET", "POST", "PUT", "DELETE"],
|
|
343
|
+
maxAge: 86400,
|
|
344
|
+
},
|
|
310
345
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
346
|
+
auth: async (request) => {
|
|
347
|
+
const token = request.headers.get("Authorization")?.replace("Bearer ", "");
|
|
348
|
+
if (!token) throw new Error("No token provided");
|
|
314
349
|
|
|
315
|
-
|
|
350
|
+
const user = await verifyJWT(token);
|
|
351
|
+
if (!user) throw new Error("Invalid token");
|
|
316
352
|
|
|
317
|
-
|
|
353
|
+
return user;
|
|
354
|
+
},
|
|
318
355
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const cached = await redis.get(key);
|
|
323
|
-
if (cached) return JSON.parse(cached);
|
|
356
|
+
cache: async (key, factory, ttl) => {
|
|
357
|
+
const cached = await redis.get(key);
|
|
358
|
+
if (cached) return JSON.parse(cached);
|
|
324
359
|
|
|
325
|
-
|
|
326
|
-
|
|
360
|
+
const value = await factory();
|
|
361
|
+
await redis.setex(key, ttl, JSON.stringify(value));
|
|
362
|
+
return value;
|
|
363
|
+
},
|
|
327
364
|
|
|
328
|
-
|
|
365
|
+
before: [
|
|
366
|
+
// Logging middleware
|
|
367
|
+
async (request) => {
|
|
368
|
+
request.startTime = Date.now();
|
|
369
|
+
console.log(`[${new Date().toISOString()}] ${request.method} ${request.url}`);
|
|
370
|
+
return request;
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
// Request ID middleware
|
|
374
|
+
async (request) => {
|
|
375
|
+
request.id = crypto.randomUUID();
|
|
376
|
+
return request;
|
|
377
|
+
},
|
|
378
|
+
],
|
|
379
|
+
|
|
380
|
+
after: [
|
|
381
|
+
// Response time header
|
|
382
|
+
async (response, request) => {
|
|
383
|
+
const duration = Date.now() - (request.startTime || Date.now());
|
|
384
|
+
response.headers.set("X-Response-Time", `${duration}ms`);
|
|
385
|
+
return response;
|
|
386
|
+
},
|
|
387
|
+
|
|
388
|
+
// Security headers
|
|
389
|
+
async (response) => {
|
|
390
|
+
response.headers.set("X-Content-Type-Options", "nosniff");
|
|
391
|
+
response.headers.set("X-Frame-Options", "DENY");
|
|
392
|
+
return response;
|
|
393
|
+
},
|
|
394
|
+
],
|
|
329
395
|
};
|
|
396
|
+
|
|
397
|
+
export default config;
|
|
330
398
|
```
|
|
331
399
|
|
|
332
|
-
##
|
|
400
|
+
## CLI Commands
|
|
333
401
|
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
hostname?: string; // Server hostname (default: localhost)
|
|
338
|
-
reusePort?: boolean; // Reuse port (default: true)
|
|
339
|
-
development?: boolean; // Development mode
|
|
340
|
-
routesDir?: string; // Routes directory (default: ./routes)
|
|
341
|
-
autoDiscover?: boolean; // Auto-discover routes (default: true)
|
|
342
|
-
idleTimeout?: number; // Idle timeout in seconds (default: 60)
|
|
343
|
-
cors?: CorsOptions; // CORS configuration
|
|
344
|
-
before?: BeforeMiddlewareHandler[]; // Pre-request middleware
|
|
345
|
-
finally?: AfterMiddlewareHandler[]; // Post-response middleware
|
|
346
|
-
}
|
|
402
|
+
```bash
|
|
403
|
+
# Development server with hot reload
|
|
404
|
+
bun vector dev
|
|
347
405
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
credentials?: boolean;
|
|
351
|
-
allowHeaders?: string | string[];
|
|
352
|
-
allowMethods?: string | string[];
|
|
353
|
-
exposeHeaders?: string | string[];
|
|
354
|
-
maxAge?: number;
|
|
355
|
-
}
|
|
356
|
-
```
|
|
406
|
+
# Production server
|
|
407
|
+
bun vector start
|
|
357
408
|
|
|
358
|
-
|
|
409
|
+
# Build for production
|
|
410
|
+
bun vector build
|
|
359
411
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
vector.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
// Add finally middleware (runs after routes)
|
|
368
|
-
vector.finally(async (response, request) => {
|
|
369
|
-
response.headers.set("X-Response-Time", Date.now() - request.startTime);
|
|
370
|
-
return response;
|
|
371
|
-
});
|
|
412
|
+
# Command options
|
|
413
|
+
bun vector dev --port 4000 # Custom port
|
|
414
|
+
bun vector dev --host 0.0.0.0 # Custom host
|
|
415
|
+
bun vector dev --routes ./api # Custom routes directory
|
|
416
|
+
bun vector dev --config ./custom.config.ts # Custom config file
|
|
372
417
|
```
|
|
373
418
|
|
|
374
419
|
## Project Structure
|
|
375
420
|
|
|
376
421
|
```
|
|
377
422
|
my-app/
|
|
378
|
-
├──
|
|
379
|
-
|
|
380
|
-
│ ├──
|
|
381
|
-
│
|
|
382
|
-
├──
|
|
423
|
+
├── vector.config.ts # Framework configuration
|
|
424
|
+
├── routes/ # Auto-discovered routes
|
|
425
|
+
│ ├── users.ts # /users endpoints
|
|
426
|
+
│ ├── posts.ts # /posts endpoints
|
|
427
|
+
│ ├── users.test.ts # Test file (automatically excluded)
|
|
428
|
+
│ └── admin/ # Nested routes
|
|
429
|
+
│ └── stats.ts # /admin/stats endpoints
|
|
430
|
+
├── lib/ # Your libraries
|
|
383
431
|
│ ├── auth.ts
|
|
384
|
-
│
|
|
385
|
-
|
|
432
|
+
│ ├── db.ts
|
|
433
|
+
│ └── redis.ts
|
|
386
434
|
└── package.json
|
|
387
435
|
```
|
|
388
436
|
|
|
389
|
-
##
|
|
437
|
+
## Route Discovery
|
|
390
438
|
|
|
391
|
-
Vector
|
|
439
|
+
Vector automatically discovers and loads route files from your `routesDir` (default: `./routes`). By default, the following file patterns are excluded from route scanning:
|
|
440
|
+
|
|
441
|
+
- `*.test.ts`, `*.test.js`, `*.test.tsx`, `*.test.jsx` - Test files
|
|
442
|
+
- `*.spec.ts`, `*.spec.js`, `*.spec.tsx`, `*.spec.jsx` - Spec files
|
|
443
|
+
- `*.tests.ts`, `*.tests.js` - Test suite files
|
|
444
|
+
- `**/__tests__/**` - Test directories
|
|
445
|
+
- `*.interface.ts`, `*.type.ts` - Type definition files
|
|
446
|
+
- `*.d.ts` - TypeScript declaration files
|
|
447
|
+
|
|
448
|
+
You can customize the exclusion patterns using the `routeExcludePatterns` configuration option:
|
|
392
449
|
|
|
393
450
|
```typescript
|
|
394
|
-
|
|
395
|
-
|
|
451
|
+
// vector.config.ts
|
|
452
|
+
const config: VectorConfigSchema = {
|
|
453
|
+
routesDir: "./routes",
|
|
454
|
+
// Custom exclusion patterns (overrides defaults)
|
|
455
|
+
routeExcludePatterns: [
|
|
456
|
+
"*.test.ts",
|
|
457
|
+
"*.spec.ts",
|
|
458
|
+
"*.mock.ts",
|
|
459
|
+
"_*.ts", // Exclude files starting with underscore
|
|
460
|
+
],
|
|
461
|
+
};
|
|
462
|
+
```
|
|
396
463
|
|
|
397
|
-
|
|
398
|
-
interface MyUser {
|
|
399
|
-
id: string;
|
|
400
|
-
email: string;
|
|
401
|
-
role: "admin" | "user";
|
|
402
|
-
permissions: string[];
|
|
403
|
-
}
|
|
464
|
+
## Performance
|
|
404
465
|
|
|
405
|
-
|
|
406
|
-
interface MyAppTypes extends VectorTypes {
|
|
407
|
-
auth: MyUser;
|
|
408
|
-
}
|
|
466
|
+
Vector achieves exceptional performance through:
|
|
409
467
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
vector.protected = async (request): Promise<MyUser> => {
|
|
415
|
-
// Your auth logic here
|
|
416
|
-
return {
|
|
417
|
-
id: "user-123",
|
|
418
|
-
email: "user@example.com",
|
|
419
|
-
role: "admin",
|
|
420
|
-
permissions: ["read", "write"],
|
|
421
|
-
};
|
|
422
|
-
};
|
|
468
|
+
- **Bun Runtime**: Native TypeScript execution without transpilation
|
|
469
|
+
- **Minimal Dependencies**: Only itty-router (3KB) as dependency
|
|
470
|
+
- **Optimized Routing**: Efficient regex-based route matching
|
|
471
|
+
- **Smart Caching**: Built-in response caching with configurable TTL
|
|
423
472
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
async (request) => {
|
|
428
|
-
// request.authUser is typed as MyUser
|
|
429
|
-
if (request.authUser?.role !== "admin") {
|
|
430
|
-
throw APIError.forbidden("Admin access required");
|
|
431
|
-
}
|
|
432
|
-
return { adminData: "..." };
|
|
433
|
-
}
|
|
434
|
-
);
|
|
435
|
-
```
|
|
473
|
+
Benchmarks show Vector handling **70,000+ requests/second** on standard hardware.
|
|
474
|
+
|
|
475
|
+
## Why Vector?
|
|
436
476
|
|
|
437
|
-
|
|
477
|
+
### For Encore Users
|
|
478
|
+
Love Encore's declarative API design but need more flexibility? Vector provides the same developer experience with the freedom to deploy anywhere Bun runs.
|
|
438
479
|
|
|
439
|
-
|
|
480
|
+
### For Express/Fastify Users
|
|
481
|
+
Tired of middleware chains and verbose configurations? Vector's declarative approach makes APIs cleaner and more maintainable.
|
|
440
482
|
|
|
441
|
-
###
|
|
483
|
+
### For New Projects
|
|
484
|
+
Starting fresh? Vector gives you production-ready features from day one with minimal configuration.
|
|
485
|
+
|
|
486
|
+
## Error Reference
|
|
487
|
+
|
|
488
|
+
Vector provides comprehensive error responses for all HTTP status codes. All errors return a consistent format:
|
|
489
|
+
|
|
490
|
+
```json
|
|
491
|
+
{
|
|
492
|
+
"error": true,
|
|
493
|
+
"message": "Error message",
|
|
494
|
+
"statusCode": 400,
|
|
495
|
+
"timestamp": "2025-01-01T00:00:00.000Z"
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Client Errors (4xx)
|
|
442
500
|
|
|
443
501
|
```typescript
|
|
444
502
|
import { APIError } from "vector-framework";
|
|
445
503
|
|
|
446
|
-
//
|
|
447
|
-
APIError.badRequest("Invalid input");
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
APIError.
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
APIError.
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
APIError.
|
|
463
|
-
|
|
464
|
-
//
|
|
465
|
-
APIError.
|
|
466
|
-
|
|
467
|
-
|
|
504
|
+
// 400 Bad Request
|
|
505
|
+
APIError.badRequest("Invalid input data");
|
|
506
|
+
|
|
507
|
+
// 401 Unauthorized
|
|
508
|
+
APIError.unauthorized("Authentication required");
|
|
509
|
+
|
|
510
|
+
// 402 Payment Required
|
|
511
|
+
APIError.paymentRequired("Subscription expired");
|
|
512
|
+
|
|
513
|
+
// 403 Forbidden
|
|
514
|
+
APIError.forbidden("Access denied");
|
|
515
|
+
|
|
516
|
+
// 404 Not Found
|
|
517
|
+
APIError.notFound("Resource not found");
|
|
518
|
+
|
|
519
|
+
// 405 Method Not Allowed
|
|
520
|
+
APIError.methodNotAllowed("POST not allowed on this endpoint");
|
|
521
|
+
|
|
522
|
+
// 406 Not Acceptable
|
|
523
|
+
APIError.notAcceptable("Cannot produce requested content type");
|
|
524
|
+
|
|
525
|
+
// 408 Request Timeout
|
|
526
|
+
APIError.requestTimeout("Request took too long");
|
|
527
|
+
|
|
528
|
+
// 409 Conflict
|
|
529
|
+
APIError.conflict("Resource already exists");
|
|
530
|
+
|
|
531
|
+
// 410 Gone
|
|
532
|
+
APIError.gone("Resource permanently deleted");
|
|
533
|
+
|
|
534
|
+
// 411 Length Required
|
|
535
|
+
APIError.lengthRequired("Content-Length header required");
|
|
536
|
+
|
|
537
|
+
// 412 Precondition Failed
|
|
538
|
+
APIError.preconditionFailed("ETag mismatch");
|
|
539
|
+
|
|
540
|
+
// 413 Payload Too Large
|
|
541
|
+
APIError.payloadTooLarge("Request body exceeds limit");
|
|
542
|
+
|
|
543
|
+
// 414 URI Too Long
|
|
544
|
+
APIError.uriTooLong("URL exceeds maximum length");
|
|
545
|
+
|
|
546
|
+
// 415 Unsupported Media Type
|
|
547
|
+
APIError.unsupportedMediaType("Content-Type not supported");
|
|
548
|
+
|
|
549
|
+
// 416 Range Not Satisfiable
|
|
550
|
+
APIError.rangeNotSatisfiable("Requested range cannot be satisfied");
|
|
551
|
+
|
|
552
|
+
// 417 Expectation Failed
|
|
553
|
+
APIError.expectationFailed("Expect header requirements not met");
|
|
554
|
+
|
|
555
|
+
// 418 I'm a Teapot
|
|
556
|
+
APIError.imATeapot("I refuse to brew coffee");
|
|
557
|
+
|
|
558
|
+
// 421 Misdirected Request
|
|
559
|
+
APIError.misdirectedRequest("Request sent to wrong server");
|
|
560
|
+
|
|
561
|
+
// 422 Unprocessable Entity
|
|
562
|
+
APIError.unprocessableEntity("Validation failed");
|
|
563
|
+
|
|
564
|
+
// 423 Locked
|
|
565
|
+
APIError.locked("Resource is locked");
|
|
566
|
+
|
|
567
|
+
// 424 Failed Dependency
|
|
568
|
+
APIError.failedDependency("Dependent request failed");
|
|
569
|
+
|
|
570
|
+
// 425 Too Early
|
|
571
|
+
APIError.tooEarly("Request is too early");
|
|
572
|
+
|
|
573
|
+
// 426 Upgrade Required
|
|
574
|
+
APIError.upgradeRequired("Protocol upgrade required");
|
|
575
|
+
|
|
576
|
+
// 428 Precondition Required
|
|
577
|
+
APIError.preconditionRequired("Precondition headers required");
|
|
578
|
+
|
|
579
|
+
// 429 Too Many Requests
|
|
580
|
+
APIError.tooManyRequests("Rate limit exceeded");
|
|
581
|
+
|
|
582
|
+
// 431 Request Header Fields Too Large
|
|
583
|
+
APIError.requestHeaderFieldsTooLarge("Headers too large");
|
|
584
|
+
|
|
585
|
+
// 451 Unavailable For Legal Reasons
|
|
586
|
+
APIError.unavailableForLegalReasons("Content blocked for legal reasons");
|
|
468
587
|
```
|
|
469
588
|
|
|
470
589
|
### Server Errors (5xx)
|
|
471
590
|
|
|
472
591
|
```typescript
|
|
473
|
-
// Server
|
|
474
|
-
APIError.internalServerError("Something went wrong");
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
APIError.
|
|
478
|
-
|
|
479
|
-
|
|
592
|
+
// 500 Internal Server Error
|
|
593
|
+
APIError.internalServerError("Something went wrong");
|
|
594
|
+
|
|
595
|
+
// 501 Not Implemented
|
|
596
|
+
APIError.notImplemented("Feature not yet available");
|
|
597
|
+
|
|
598
|
+
// 502 Bad Gateway
|
|
599
|
+
APIError.badGateway("Upstream server error");
|
|
600
|
+
|
|
601
|
+
// 503 Service Unavailable
|
|
602
|
+
APIError.serviceUnavailable("Service temporarily down");
|
|
603
|
+
|
|
604
|
+
// 504 Gateway Timeout
|
|
605
|
+
APIError.gatewayTimeout("Upstream server timeout");
|
|
606
|
+
|
|
607
|
+
// 505 HTTP Version Not Supported
|
|
608
|
+
APIError.httpVersionNotSupported("HTTP/3 not supported");
|
|
609
|
+
|
|
610
|
+
// 506 Variant Also Negotiates
|
|
611
|
+
APIError.variantAlsoNegotiates("Content negotiation error");
|
|
612
|
+
|
|
613
|
+
// 507 Insufficient Storage
|
|
614
|
+
APIError.insufficientStorage("Server storage full");
|
|
615
|
+
|
|
616
|
+
// 508 Loop Detected
|
|
617
|
+
APIError.loopDetected("Infinite loop detected");
|
|
618
|
+
|
|
619
|
+
// 510 Not Extended
|
|
620
|
+
APIError.notExtended("Extension required");
|
|
621
|
+
|
|
622
|
+
// 511 Network Authentication Required
|
|
623
|
+
APIError.networkAuthenticationRequired("Network login required");
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
### Convenience Aliases
|
|
627
|
+
|
|
628
|
+
```typescript
|
|
629
|
+
// Alias for 422 Unprocessable Entity
|
|
630
|
+
APIError.invalidArgument("Field 'email' is required");
|
|
631
|
+
|
|
632
|
+
// Alias for 429 Too Many Requests
|
|
633
|
+
APIError.rateLimitExceeded("Try again in 60 seconds");
|
|
634
|
+
|
|
635
|
+
// Alias for 503 Service Unavailable
|
|
636
|
+
APIError.maintenance("Scheduled maintenance in progress");
|
|
480
637
|
```
|
|
481
638
|
|
|
482
639
|
### Custom Errors
|
|
483
640
|
|
|
484
641
|
```typescript
|
|
485
|
-
// Create
|
|
486
|
-
APIError.custom(456,
|
|
642
|
+
// Create error with any status code
|
|
643
|
+
APIError.custom(456, "Custom error message");
|
|
487
644
|
|
|
488
|
-
//
|
|
489
|
-
|
|
490
|
-
{
|
|
491
|
-
"error": true,
|
|
492
|
-
"message": "Error message",
|
|
493
|
-
"statusCode": 400,
|
|
494
|
-
"timestamp": "2025-01-01T00:00:00.000Z"
|
|
495
|
-
}
|
|
645
|
+
// With custom content type
|
|
646
|
+
APIError.custom(400, "Invalid XML", "application/xml");
|
|
496
647
|
```
|
|
497
648
|
|
|
498
649
|
### Usage in Routes
|
|
499
650
|
|
|
500
651
|
```typescript
|
|
501
|
-
|
|
502
|
-
{ method: "
|
|
652
|
+
export const example = route(
|
|
653
|
+
{ method: "POST", path: "/api/users" },
|
|
503
654
|
async (req) => {
|
|
504
|
-
// Validation
|
|
505
|
-
if (!req.
|
|
506
|
-
throw APIError.badRequest("
|
|
655
|
+
// Validation errors
|
|
656
|
+
if (!req.content?.email) {
|
|
657
|
+
throw APIError.badRequest("Email is required");
|
|
507
658
|
}
|
|
508
659
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
throw APIError.tooManyRequests("Please wait before trying again");
|
|
660
|
+
if (!isValidEmail(req.content.email)) {
|
|
661
|
+
throw APIError.unprocessableEntity("Invalid email format");
|
|
512
662
|
}
|
|
513
663
|
|
|
514
|
-
//
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
throw APIError.notFound("Data not found");
|
|
664
|
+
// Authentication errors
|
|
665
|
+
if (!req.authUser) {
|
|
666
|
+
throw APIError.unauthorized("Please login first");
|
|
518
667
|
}
|
|
519
668
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
669
|
+
if (req.authUser.role !== "admin") {
|
|
670
|
+
throw APIError.forbidden("Admin access required");
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Resource errors
|
|
674
|
+
const existingUser = await findUserByEmail(req.content.email);
|
|
675
|
+
if (existingUser) {
|
|
676
|
+
throw APIError.conflict("Email already registered");
|
|
523
677
|
}
|
|
524
678
|
|
|
525
|
-
|
|
679
|
+
// Rate limiting
|
|
680
|
+
if (await checkRateLimit(req.authUser.id)) {
|
|
681
|
+
throw APIError.tooManyRequests("Maximum 5 users per hour");
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
try {
|
|
685
|
+
const user = await createUser(req.content);
|
|
686
|
+
return { user };
|
|
687
|
+
} catch (error) {
|
|
688
|
+
// Database errors
|
|
689
|
+
if (error.code === "STORAGE_FULL") {
|
|
690
|
+
throw APIError.insufficientStorage("Database full");
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Generic server error
|
|
694
|
+
throw APIError.internalServerError("Failed to create user");
|
|
695
|
+
}
|
|
526
696
|
}
|
|
527
697
|
);
|
|
528
698
|
```
|