vector-framework 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -634
- package/dist/auth/protected.d.ts.map +1 -1
- package/dist/auth/protected.js.map +1 -1
- package/dist/cache/manager.d.ts +5 -2
- package/dist/cache/manager.d.ts.map +1 -1
- package/dist/cache/manager.js +21 -12
- package/dist/cache/manager.js.map +1 -1
- package/dist/cli/index.js +60 -126
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/option-resolution.d.ts +4 -0
- package/dist/cli/option-resolution.d.ts.map +1 -0
- package/dist/cli/option-resolution.js +28 -0
- package/dist/cli/option-resolution.js.map +1 -0
- package/dist/cli.js +2774 -599
- package/dist/constants/index.d.ts +3 -0
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +6 -0
- package/dist/constants/index.js.map +1 -1
- package/dist/core/config-loader.d.ts +2 -2
- package/dist/core/config-loader.d.ts.map +1 -1
- package/dist/core/config-loader.js +18 -18
- package/dist/core/config-loader.js.map +1 -1
- package/dist/core/router.d.ts +41 -15
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/router.js +465 -150
- package/dist/core/router.js.map +1 -1
- package/dist/core/server.d.ts +17 -3
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +274 -33
- package/dist/core/server.js.map +1 -1
- package/dist/core/vector.d.ts +9 -8
- package/dist/core/vector.d.ts.map +1 -1
- package/dist/core/vector.js +40 -32
- package/dist/core/vector.js.map +1 -1
- package/dist/dev/route-generator.d.ts.map +1 -1
- package/dist/dev/route-generator.js.map +1 -1
- package/dist/dev/route-scanner.d.ts +1 -1
- package/dist/dev/route-scanner.d.ts.map +1 -1
- package/dist/dev/route-scanner.js +37 -43
- package/dist/dev/route-scanner.js.map +1 -1
- package/dist/http.d.ts +14 -14
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +84 -84
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1314 -8
- package/dist/index.mjs +1314 -8
- package/dist/middleware/manager.d.ts +1 -1
- package/dist/middleware/manager.d.ts.map +1 -1
- package/dist/middleware/manager.js +4 -0
- package/dist/middleware/manager.js.map +1 -1
- package/dist/openapi/docs-ui.d.ts +2 -0
- package/dist/openapi/docs-ui.d.ts.map +1 -0
- package/dist/openapi/docs-ui.js +1313 -0
- package/dist/openapi/docs-ui.js.map +1 -0
- package/dist/openapi/generator.d.ts +12 -0
- package/dist/openapi/generator.d.ts.map +1 -0
- package/dist/openapi/generator.js +273 -0
- package/dist/openapi/generator.js.map +1 -0
- package/dist/types/index.d.ts +70 -11
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/standard-schema.d.ts +118 -0
- package/dist/types/standard-schema.d.ts.map +1 -0
- package/dist/types/standard-schema.js +2 -0
- package/dist/types/standard-schema.js.map +1 -0
- package/dist/utils/cors.d.ts +13 -0
- package/dist/utils/cors.d.ts.map +1 -0
- package/dist/utils/cors.js +89 -0
- package/dist/utils/cors.js.map +1 -0
- package/dist/utils/path.d.ts +7 -0
- package/dist/utils/path.d.ts.map +1 -1
- package/dist/utils/path.js +14 -3
- package/dist/utils/path.js.map +1 -1
- package/dist/utils/schema-validation.d.ts +31 -0
- package/dist/utils/schema-validation.d.ts.map +1 -0
- package/dist/utils/schema-validation.js +77 -0
- package/dist/utils/schema-validation.js.map +1 -0
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +1 -0
- package/dist/utils/validation.js.map +1 -1
- package/package.json +24 -19
- package/src/auth/protected.ts +3 -13
- package/src/cache/manager.ts +25 -30
- package/src/cli/index.ts +62 -141
- package/src/cli/option-resolution.ts +40 -0
- package/src/constants/index.ts +7 -0
- package/src/core/config-loader.ts +20 -22
- package/src/core/router.ts +535 -155
- package/src/core/server.ts +354 -45
- package/src/core/vector.ts +71 -61
- package/src/dev/route-generator.ts +1 -3
- package/src/dev/route-scanner.ts +38 -51
- package/src/http.ts +117 -187
- package/src/index.ts +3 -3
- package/src/middleware/manager.ts +8 -11
- package/src/openapi/assets/tailwindcdn.js +83 -0
- package/src/openapi/docs-ui.ts +1317 -0
- package/src/openapi/generator.ts +359 -0
- package/src/types/index.ts +104 -17
- package/src/types/standard-schema.ts +147 -0
- package/src/utils/cors.ts +101 -0
- package/src/utils/path.ts +19 -4
- package/src/utils/schema-validation.ts +123 -0
- package/src/utils/validation.ts +1 -0
package/README.md
CHANGED
|
@@ -1,705 +1,158 @@
|
|
|
1
|
-
#
|
|
1
|
+
# vector
|
|
2
|
+
<img width="1640" height="664" alt="vector-bw-banner" src="https://github.com/user-attachments/assets/afeb8c6e-0729-4fea-9054-7972f8af9365" />
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
Blazing fast, secure, and developer-friendly API framework for Bun.
|
|
4
5
|
|
|
5
|
-
|
|
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
|
+
Vector is a Bun-first framework for building HTTP APIs with declarative route files, strong TypeScript inference, and zero runtime dependencies.
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
- **Zero Dependencies**: No runtime routing dependency
|
|
9
|
+
- **Bun Native**: TypeScript-first workflow with the Bun runtime
|
|
10
|
+
- **Type Safe**: Typed request/auth/context with schema-driven validation
|
|
11
|
+
- **Built In**: Middleware, auth hooks, caching, CORS, OpenAPI generation
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
## Installation
|
|
13
14
|
|
|
14
15
|
```bash
|
|
15
16
|
bun add vector-framework
|
|
16
17
|
```
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
Requirements:
|
|
20
|
+
|
|
21
|
+
- Bun `>= 1.0.0`
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
## Quick Start
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
### 1. Create `vector.config.ts`
|
|
26
|
+
|
|
27
|
+
```ts
|
|
24
28
|
import type { VectorConfigSchema } from "vector-framework";
|
|
25
29
|
|
|
26
30
|
const config: VectorConfigSchema = {
|
|
27
|
-
// Server configuration
|
|
28
31
|
port: 3000,
|
|
29
32
|
hostname: "localhost",
|
|
30
33
|
development: process.env.NODE_ENV !== "production",
|
|
31
|
-
routesDir: "./routes",
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
allowHeaders: ["Content-Type", "Authorization"],
|
|
38
|
-
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
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;
|
|
34
|
+
routesDir: "./routes",
|
|
35
|
+
defaults: {
|
|
36
|
+
route: {
|
|
37
|
+
expose: true,
|
|
38
|
+
auth: false,
|
|
39
|
+
},
|
|
59
40
|
},
|
|
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
|
-
],
|
|
75
41
|
};
|
|
76
42
|
|
|
77
43
|
export default config;
|
|
78
44
|
```
|
|
79
45
|
|
|
80
|
-
### 2. Create
|
|
46
|
+
### 2. Create a route file
|
|
81
47
|
|
|
82
|
-
```
|
|
48
|
+
```ts
|
|
83
49
|
// routes/hello.ts
|
|
84
50
|
import { route } from "vector-framework";
|
|
85
51
|
|
|
86
|
-
// Simple public endpoint
|
|
87
52
|
export const hello = route(
|
|
88
|
-
{
|
|
89
|
-
method: "GET",
|
|
90
|
-
path: "/hello/:name",
|
|
91
|
-
expose: true, // Public endpoint (default: true)
|
|
92
|
-
},
|
|
93
|
-
async (req) => {
|
|
94
|
-
return { message: `Hello ${req.params.name}!` };
|
|
95
|
-
}
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
// Protected endpoint - uses auth from config
|
|
99
|
-
export const getProfile = route(
|
|
100
|
-
{
|
|
101
|
-
method: "GET",
|
|
102
|
-
path: "/profile",
|
|
103
|
-
auth: true, // Requires authentication
|
|
104
|
-
expose: true,
|
|
105
|
-
},
|
|
53
|
+
{ method: "GET", path: "/hello/:name", expose: true },
|
|
106
54
|
async (req) => {
|
|
107
|
-
return {
|
|
108
|
-
user: req.authUser, // Typed from your auth handler
|
|
109
|
-
timestamp: new Date(),
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
);
|
|
113
|
-
|
|
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,
|
|
55
|
+
return { message: `Hello ${req.params.name}` };
|
|
121
56
|
},
|
|
122
|
-
async () => {
|
|
123
|
-
// Expensive operation, will be cached
|
|
124
|
-
const users = await db.users.findMany();
|
|
125
|
-
return { users };
|
|
126
|
-
}
|
|
127
57
|
);
|
|
128
58
|
```
|
|
129
59
|
|
|
130
|
-
### 3.
|
|
60
|
+
### 3. Run the server
|
|
131
61
|
|
|
132
62
|
```bash
|
|
133
|
-
# Development mode with hot reload
|
|
134
63
|
bun vector dev
|
|
135
|
-
|
|
136
|
-
# Production mode
|
|
137
|
-
bun vector start
|
|
138
|
-
|
|
139
|
-
# With custom options
|
|
140
|
-
bun vector dev --port 4000 --routes ./api
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
That's it! Your API is running at `http://localhost:3000` 🎉
|
|
144
|
-
|
|
145
|
-
## TypeScript Type Safety
|
|
146
|
-
|
|
147
|
-
Vector provides full type safety with customizable types. Define your types in the config and use them in routes:
|
|
148
|
-
|
|
149
|
-
```typescript
|
|
150
|
-
// vector.config.ts
|
|
151
|
-
import type { VectorConfigSchema, VectorTypes } from "vector-framework";
|
|
152
|
-
|
|
153
|
-
// Define your custom user type
|
|
154
|
-
interface MyUser {
|
|
155
|
-
id: string;
|
|
156
|
-
email: string;
|
|
157
|
-
role: "admin" | "user";
|
|
158
|
-
permissions: string[];
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Extend Vector types
|
|
162
|
-
interface MyAppTypes extends VectorTypes {
|
|
163
|
-
auth: MyUser;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Use in config with type parameter
|
|
167
|
-
const config: VectorConfigSchema<MyAppTypes> = {
|
|
168
|
-
port: 3000,
|
|
169
|
-
|
|
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
|
-
};
|
|
179
|
-
},
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
export default config;
|
|
183
|
-
export type { MyAppTypes }; // Export for use in routes
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
// routes/admin.ts
|
|
188
|
-
import { route, APIError } from "vector-framework";
|
|
189
|
-
import type { MyAppTypes } from "../vector.config";
|
|
190
|
-
|
|
191
|
-
// Use type parameter to get fully typed request
|
|
192
|
-
export const adminOnly = route<MyAppTypes>(
|
|
193
|
-
{
|
|
194
|
-
method: "GET",
|
|
195
|
-
path: "/admin/data",
|
|
196
|
-
auth: true,
|
|
197
|
-
expose: true,
|
|
198
|
-
},
|
|
199
|
-
async (req) => {
|
|
200
|
-
// req.authUser is now typed as MyUser
|
|
201
|
-
if (req.authUser?.role !== "admin") {
|
|
202
|
-
throw APIError.forbidden("Admin access required");
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// TypeScript knows these properties exist
|
|
206
|
-
return {
|
|
207
|
-
user: req.authUser.email,
|
|
208
|
-
permissions: req.authUser.permissions,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
);
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
## Core Features
|
|
215
|
-
|
|
216
|
-
### Route Options
|
|
217
|
-
|
|
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
|
-
```
|
|
233
|
-
|
|
234
|
-
### Request Object
|
|
235
|
-
|
|
236
|
-
Every route handler receives a typed request object:
|
|
237
|
-
|
|
238
|
-
```typescript
|
|
239
|
-
export const example = route(
|
|
240
|
-
{ method: "POST", path: "/example/:id" },
|
|
241
|
-
async (req) => {
|
|
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
|
|
251
|
-
}
|
|
252
|
-
);
|
|
253
64
|
```
|
|
254
65
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
Vector provides comprehensive error responses:
|
|
66
|
+
Your API will be available at `http://localhost:3000`.
|
|
258
67
|
|
|
259
|
-
|
|
260
|
-
import { APIError } from "vector-framework";
|
|
68
|
+
## Production Start
|
|
261
69
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
async (req) => {
|
|
265
|
-
// Client errors (4xx)
|
|
266
|
-
if (!req.params.id) {
|
|
267
|
-
throw APIError.badRequest("ID is required");
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const data = await findData(req.params.id);
|
|
271
|
-
if (!data) {
|
|
272
|
-
throw APIError.notFound("Data not found");
|
|
273
|
-
}
|
|
274
|
-
|
|
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
|
-
}
|
|
283
|
-
|
|
284
|
-
// Server errors (5xx)
|
|
285
|
-
try {
|
|
286
|
-
return await processData(data);
|
|
287
|
-
} catch (error) {
|
|
288
|
-
throw APIError.internalServerError("Processing failed");
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
);
|
|
70
|
+
```bash
|
|
71
|
+
bun vector start --config ./vector.config.ts --routes ./routes --port 8080 --host 0.0.0.0
|
|
292
72
|
```
|
|
293
73
|
|
|
294
|
-
|
|
74
|
+
Notes:
|
|
295
75
|
|
|
296
|
-
|
|
76
|
+
- `start` uses the same config and route options as `dev`.
|
|
77
|
+
- `start` sets `NODE_ENV=production`.
|
|
78
|
+
- File watching is only enabled for `dev`.
|
|
297
79
|
|
|
298
|
-
|
|
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
|
|
80
|
+
## Optional: Validation + OpenAPI
|
|
308
81
|
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
}
|
|
82
|
+
```bash
|
|
83
|
+
bun add -d zod
|
|
320
84
|
```
|
|
321
85
|
|
|
322
|
-
|
|
86
|
+
Vector is not tied to Zod. It supports any validation library that implements the
|
|
87
|
+
`StandardSchemaV1` interface (`~standard` v1).
|
|
323
88
|
|
|
324
|
-
|
|
325
|
-
// vector.config.ts
|
|
326
|
-
import type { VectorConfigSchema } from "vector-framework";
|
|
327
|
-
import { verifyJWT } from "./lib/auth";
|
|
328
|
-
import { redis } from "./lib/redis";
|
|
89
|
+
Common compatible choices include:
|
|
329
90
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
development: process.env.NODE_ENV !== "production",
|
|
334
|
-
routesDir: "./api/routes",
|
|
335
|
-
routeExcludePatterns: ["*.test.ts", "*.spec.ts"], // Optional: custom exclusions
|
|
336
|
-
idleTimeout: 60,
|
|
337
|
-
|
|
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
|
-
},
|
|
91
|
+
- Zod (v4+)
|
|
92
|
+
- Valibot
|
|
93
|
+
- ArkType
|
|
345
94
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
95
|
+
For OpenAPI schema conversion, your library also needs `StandardJSONSchemaV1`
|
|
96
|
+
(`~standard.jsonSchema.input/output`). If those converters are missing, runtime
|
|
97
|
+
validation still works, but schema conversion is skipped.
|
|
349
98
|
|
|
350
|
-
|
|
351
|
-
|
|
99
|
+
```ts
|
|
100
|
+
import { route } from "vector-framework";
|
|
101
|
+
import { z } from "zod";
|
|
352
102
|
|
|
353
|
-
|
|
354
|
-
|
|
103
|
+
const CreateUserInput = z.object({
|
|
104
|
+
body: z.object({
|
|
105
|
+
email: z.string().email(),
|
|
106
|
+
name: z.string().min(1),
|
|
107
|
+
}),
|
|
108
|
+
});
|
|
355
109
|
|
|
356
|
-
|
|
357
|
-
const cached = await redis.get(key);
|
|
358
|
-
if (cached) return JSON.parse(cached);
|
|
110
|
+
const CreateUserSchema = { input: CreateUserInput };
|
|
359
111
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
112
|
+
export const createUser = route(
|
|
113
|
+
{ method: "POST", path: "/users", expose: true, schema: CreateUserSchema },
|
|
114
|
+
async (req) => {
|
|
115
|
+
return { created: true, email: req.content.email };
|
|
363
116
|
},
|
|
364
|
-
|
|
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
|
-
],
|
|
395
|
-
};
|
|
396
|
-
|
|
397
|
-
export default config;
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
## CLI Commands
|
|
401
|
-
|
|
402
|
-
```bash
|
|
403
|
-
# Development server with hot reload
|
|
404
|
-
bun vector dev
|
|
405
|
-
|
|
406
|
-
# Production server
|
|
407
|
-
bun vector start
|
|
408
|
-
|
|
409
|
-
# Build for production
|
|
410
|
-
bun vector build
|
|
411
|
-
|
|
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
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
## Project Structure
|
|
420
|
-
|
|
421
|
-
```
|
|
422
|
-
my-app/
|
|
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
|
|
431
|
-
│ ├── auth.ts
|
|
432
|
-
│ ├── db.ts
|
|
433
|
-
│ └── redis.ts
|
|
434
|
-
└── package.json
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
## Route Discovery
|
|
438
|
-
|
|
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:
|
|
449
|
-
|
|
450
|
-
```typescript
|
|
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
|
-
};
|
|
117
|
+
);
|
|
462
118
|
```
|
|
463
119
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
Vector achieves exceptional performance through:
|
|
467
|
-
|
|
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
|
|
120
|
+
Enable OpenAPI in `vector.config.ts`:
|
|
472
121
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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.
|
|
479
|
-
|
|
480
|
-
### For Express/Fastify Users
|
|
481
|
-
Tired of middleware chains and verbose configurations? Vector's declarative approach makes APIs cleaner and more maintainable.
|
|
482
|
-
|
|
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"
|
|
122
|
+
```ts
|
|
123
|
+
openapi: {
|
|
124
|
+
enabled: true,
|
|
125
|
+
path: '/openapi.json',
|
|
126
|
+
docs: false,
|
|
496
127
|
}
|
|
497
128
|
```
|
|
498
129
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
```typescript
|
|
502
|
-
import { APIError } from "vector-framework";
|
|
503
|
-
|
|
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");
|
|
587
|
-
```
|
|
588
|
-
|
|
589
|
-
### Server Errors (5xx)
|
|
590
|
-
|
|
591
|
-
```typescript
|
|
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");
|
|
637
|
-
```
|
|
638
|
-
|
|
639
|
-
### Custom Errors
|
|
130
|
+
## Documentation
|
|
640
131
|
|
|
641
|
-
|
|
642
|
-
// Create error with any status code
|
|
643
|
-
APIError.custom(456, "Custom error message");
|
|
132
|
+
Start here for deeper guides:
|
|
644
133
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
134
|
+
- [Docs Index](docs/README.md)
|
|
135
|
+
- [Configuration](docs/configuration.md)
|
|
136
|
+
- [Routing and Request API](docs/routing.md)
|
|
137
|
+
- [TypeScript Types](docs/typescript.md)
|
|
138
|
+
- [Schema Validation](docs/schema-validation.md)
|
|
139
|
+
- [OpenAPI and Docs UI](docs/openapi.md)
|
|
140
|
+
- [CLI and Route Discovery](docs/cli-and-discovery.md)
|
|
141
|
+
- [Error Reference](docs/errors.md)
|
|
142
|
+
- [Migration Notes](docs/migration.md)
|
|
143
|
+
- [Performance Notes](docs/performance.md)
|
|
648
144
|
|
|
649
|
-
|
|
145
|
+
## Examples
|
|
650
146
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
if (!req.content?.email) {
|
|
657
|
-
throw APIError.badRequest("Email is required");
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
if (!isValidEmail(req.content.email)) {
|
|
661
|
-
throw APIError.unprocessableEntity("Invalid email format");
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
// Authentication errors
|
|
665
|
-
if (!req.authUser) {
|
|
666
|
-
throw APIError.unauthorized("Please login first");
|
|
667
|
-
}
|
|
668
|
-
|
|
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");
|
|
677
|
-
}
|
|
678
|
-
|
|
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
|
-
}
|
|
696
|
-
}
|
|
697
|
-
);
|
|
698
|
-
```
|
|
147
|
+
- [examples/routes/health.ts](examples/routes/health.ts)
|
|
148
|
+
- [examples/routes/events.ts](examples/routes/events.ts)
|
|
149
|
+
- [examples/routes/commerce.ts](examples/routes/commerce.ts)
|
|
150
|
+
- [tests/e2e/test-routes.ts](tests/e2e/test-routes.ts) (broader endpoint patterns)
|
|
151
|
+
- [tests/e2e/test-zod-routes.ts](tests/e2e/test-zod-routes.ts) (Zod + I/O validation flows)
|
|
699
152
|
|
|
700
153
|
## Contributing
|
|
701
154
|
|
|
702
|
-
Contributions are welcome
|
|
155
|
+
Contributions are welcome. Open an issue or pull request.
|
|
703
156
|
|
|
704
157
|
## License
|
|
705
158
|
|