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