vector-framework 1.1.1 → 1.2.1
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 +99 -628
- package/dist/auth/protected.d.ts +1 -0
- package/dist/auth/protected.d.ts.map +1 -1
- package/dist/auth/protected.js +3 -0
- package/dist/auth/protected.js.map +1 -1
- package/dist/cache/manager.d.ts +1 -0
- package/dist/cache/manager.d.ts.map +1 -1
- package/dist/cache/manager.js +5 -7
- package/dist/cache/manager.js.map +1 -1
- package/dist/cli/graceful-shutdown.d.ts +15 -0
- package/dist/cli/graceful-shutdown.d.ts.map +1 -0
- package/dist/cli/graceful-shutdown.js +42 -0
- package/dist/cli/graceful-shutdown.js.map +1 -0
- package/dist/cli/index.js +46 -97
- 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 +3423 -660
- 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.map +1 -1
- package/dist/core/config-loader.js +7 -2
- package/dist/core/config-loader.js.map +1 -1
- package/dist/core/router.d.ts +41 -17
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/router.js +432 -153
- package/dist/core/router.js.map +1 -1
- package/dist/core/server.d.ts +17 -1
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +471 -31
- package/dist/core/server.js.map +1 -1
- package/dist/core/vector.d.ts +8 -5
- package/dist/core/vector.d.ts.map +1 -1
- package/dist/core/vector.js +53 -14
- 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.map +1 -1
- package/dist/dev/route-scanner.js +1 -5
- 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 +34 -41
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1420 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1420 -8
- 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 +1425 -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 +502 -0
- package/dist/openapi/generator.js.map +1 -0
- package/dist/start-vector.d.ts +3 -0
- package/dist/start-vector.d.ts.map +1 -0
- package/dist/start-vector.js +38 -0
- package/dist/start-vector.js.map +1 -0
- package/dist/types/index.d.ts +95 -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/logger.js +1 -1
- package/dist/utils/path.d.ts +6 -0
- package/dist/utils/path.d.ts.map +1 -1
- package/dist/utils/path.js +5 -0
- 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 +3 -0
- package/dist/utils/validation.js.map +1 -1
- package/package.json +15 -12
- package/src/auth/protected.ts +7 -13
- package/src/cache/manager.ts +8 -18
- package/src/cli/graceful-shutdown.ts +60 -0
- package/src/cli/index.ts +52 -115
- package/src/cli/option-resolution.ts +40 -0
- package/src/constants/index.ts +7 -0
- package/src/core/config-loader.ts +7 -4
- package/src/core/router.ts +502 -156
- package/src/core/server.ts +610 -33
- package/src/core/vector.ts +87 -33
- package/src/dev/route-generator.ts +1 -3
- package/src/dev/route-scanner.ts +2 -9
- package/src/http.ts +85 -125
- package/src/index.ts +4 -3
- package/src/middleware/manager.ts +4 -0
- package/src/openapi/assets/favicon/android-chrome-192x192.png +0 -0
- package/src/openapi/assets/favicon/android-chrome-512x512.png +0 -0
- package/src/openapi/assets/favicon/apple-touch-icon.png +0 -0
- package/src/openapi/assets/favicon/favicon-16x16.png +0 -0
- package/src/openapi/assets/favicon/favicon-32x32.png +0 -0
- package/src/openapi/assets/favicon/favicon.ico +0 -0
- package/src/openapi/assets/favicon/site.webmanifest +11 -0
- package/src/openapi/assets/logo.svg +12 -0
- package/src/openapi/assets/logo_dark.svg +6 -0
- package/src/openapi/assets/logo_icon.png +0 -0
- package/src/openapi/assets/logo_white.svg +6 -0
- package/src/openapi/assets/tailwindcdn.js +83 -0
- package/src/openapi/docs-ui.ts +1435 -0
- package/src/openapi/generator.ts +586 -0
- package/src/start-vector.ts +50 -0
- package/src/types/index.ts +138 -17
- package/src/types/standard-schema.ts +147 -0
- package/src/utils/cors.ts +101 -0
- package/src/utils/logger.ts +1 -1
- package/src/utils/path.ts +6 -0
- package/src/utils/schema-validation.ts +123 -0
- package/src/utils/validation.ts +3 -0
package/README.md
CHANGED
|
@@ -1,706 +1,177 @@
|
|
|
1
|
-
#
|
|
1
|
+
# vector
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img width="1640" height="664" alt="vector-bw-banner" src="https://github.com/user-attachments/assets/afeb8c6e-0729-4fea-9054-7972f8af9365" />
|
|
4
4
|
|
|
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
|
|
5
|
+
Blazing fast, secure, and developer-friendly API framework for Bun.
|
|
9
6
|
|
|
10
|
-
|
|
7
|
+
Vector is a Bun-first framework for building HTTP APIs with declarative route files, strong TypeScript inference, and zero runtime dependencies.
|
|
8
|
+
|
|
9
|
+
- **Zero Dependencies**: No runtime routing dependency
|
|
10
|
+
- **Bun Native**: TypeScript-first workflow with the Bun runtime
|
|
11
|
+
- **Type Safe**: Typed request/auth/context with schema-driven validation
|
|
12
|
+
- **Built In**: Middleware, auth hooks, caching, CORS, OpenAPI generation
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
## Installation
|
|
13
15
|
|
|
14
16
|
```bash
|
|
15
17
|
bun add vector-framework
|
|
16
18
|
```
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
Requirements:
|
|
21
|
+
|
|
22
|
+
- Bun `>= 1.0.0`
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
19
25
|
|
|
20
|
-
Create `vector.config.ts`
|
|
26
|
+
### 1. Create `vector.config.ts`
|
|
21
27
|
|
|
22
|
-
```
|
|
23
|
-
// vector.config.ts
|
|
28
|
+
```ts
|
|
24
29
|
import type { VectorConfigSchema } from "vector-framework";
|
|
25
30
|
|
|
26
31
|
const config: VectorConfigSchema = {
|
|
27
|
-
// Server configuration
|
|
28
32
|
port: 3000,
|
|
29
33
|
hostname: "localhost",
|
|
30
34
|
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;
|
|
35
|
+
routesDir: "./routes",
|
|
36
|
+
defaults: {
|
|
37
|
+
route: {
|
|
38
|
+
expose: true,
|
|
39
|
+
auth: false,
|
|
40
|
+
},
|
|
59
41
|
},
|
|
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
42
|
};
|
|
76
43
|
|
|
77
44
|
export default config;
|
|
78
45
|
```
|
|
79
46
|
|
|
80
|
-
### 2. Create
|
|
47
|
+
### 2. Create a route file
|
|
81
48
|
|
|
82
|
-
```
|
|
49
|
+
```ts
|
|
83
50
|
// routes/hello.ts
|
|
84
51
|
import { route } from "vector-framework";
|
|
85
52
|
|
|
86
|
-
// Simple public endpoint
|
|
87
53
|
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
|
-
},
|
|
54
|
+
{ method: "GET", path: "/hello/:name", expose: true },
|
|
106
55
|
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,
|
|
56
|
+
return { message: `Hello ${req.params.name}` };
|
|
121
57
|
},
|
|
122
|
-
async () => {
|
|
123
|
-
// Expensive operation, will be cached
|
|
124
|
-
const users = await db.users.findMany();
|
|
125
|
-
return { users };
|
|
126
|
-
}
|
|
127
58
|
);
|
|
128
59
|
```
|
|
129
60
|
|
|
130
|
-
### 3.
|
|
61
|
+
### 3. Run the server
|
|
131
62
|
|
|
132
63
|
```bash
|
|
133
|
-
# Development mode with hot reload
|
|
134
64
|
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
65
|
```
|
|
142
66
|
|
|
143
|
-
|
|
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";
|
|
67
|
+
Your API will be available at `http://localhost:3000`.
|
|
152
68
|
|
|
153
|
-
|
|
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,
|
|
69
|
+
## Production Start
|
|
169
70
|
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
}
|
|
71
|
+
```bash
|
|
72
|
+
bun vector start --config ./vector.config.ts --routes ./routes --port 8080 --host 0.0.0.0
|
|
232
73
|
```
|
|
233
74
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
Every route handler receives a typed request object:
|
|
75
|
+
Notes:
|
|
237
76
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
```
|
|
77
|
+
- `start` uses the same config and route options as `dev`.
|
|
78
|
+
- `start` sets `NODE_ENV=production`.
|
|
79
|
+
- File watching is only enabled for `dev`.
|
|
254
80
|
|
|
255
|
-
|
|
81
|
+
## Programmatic Start
|
|
256
82
|
|
|
257
|
-
|
|
83
|
+
```ts
|
|
84
|
+
import { startVector } from "vector-framework";
|
|
258
85
|
|
|
259
|
-
|
|
260
|
-
|
|
86
|
+
const app = await startVector({
|
|
87
|
+
configPath: "./vector.config.ts",
|
|
88
|
+
});
|
|
261
89
|
|
|
262
|
-
|
|
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
|
-
}
|
|
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
|
-
);
|
|
90
|
+
console.log(`Listening on ${app.server.hostname}:${app.server.port}`);
|
|
292
91
|
```
|
|
293
92
|
|
|
294
|
-
|
|
93
|
+
Notes:
|
|
295
94
|
|
|
296
|
-
|
|
95
|
+
- `startVector()` does not enable file watching.
|
|
96
|
+
- `app.stop()` stops immediately (used for dev reload flows).
|
|
97
|
+
- `await app.shutdown()` performs graceful shutdown and runs config `shutdown` hook.
|
|
297
98
|
|
|
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
|
|
99
|
+
## Optional: Validation + OpenAPI
|
|
308
100
|
|
|
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
|
-
}
|
|
101
|
+
```bash
|
|
102
|
+
bun add -d zod
|
|
320
103
|
```
|
|
321
104
|
|
|
322
|
-
|
|
105
|
+
Vector is not tied to Zod. It supports any validation library that implements the
|
|
106
|
+
`StandardSchemaV1` interface (`~standard` v1).
|
|
323
107
|
|
|
324
|
-
|
|
325
|
-
// vector.config.ts
|
|
326
|
-
import type { VectorConfigSchema } from "vector-framework";
|
|
327
|
-
import { verifyJWT } from "./lib/auth";
|
|
328
|
-
import { redis } from "./lib/redis";
|
|
108
|
+
Common compatible choices include:
|
|
329
109
|
|
|
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
|
-
},
|
|
110
|
+
- Zod (v4+)
|
|
111
|
+
- Valibot
|
|
112
|
+
- ArkType
|
|
345
113
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
114
|
+
For OpenAPI schema conversion, your library also needs `StandardJSONSchemaV1`
|
|
115
|
+
(`~standard.jsonSchema.input/output`). If those converters are missing, runtime
|
|
116
|
+
validation still works, but schema conversion is skipped.
|
|
349
117
|
|
|
350
|
-
|
|
351
|
-
|
|
118
|
+
```ts
|
|
119
|
+
import { route } from "vector-framework";
|
|
120
|
+
import { z } from "zod";
|
|
352
121
|
|
|
353
|
-
|
|
354
|
-
|
|
122
|
+
const CreateUserInput = z.object({
|
|
123
|
+
body: z.object({
|
|
124
|
+
email: z.string().email(),
|
|
125
|
+
name: z.string().min(1),
|
|
126
|
+
}),
|
|
127
|
+
});
|
|
355
128
|
|
|
356
|
-
|
|
357
|
-
const cached = await redis.get(key);
|
|
358
|
-
if (cached) return JSON.parse(cached);
|
|
129
|
+
const CreateUserSchema = { input: CreateUserInput };
|
|
359
130
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
131
|
+
export const createUser = route(
|
|
132
|
+
{ method: "POST", path: "/users", expose: true, schema: CreateUserSchema },
|
|
133
|
+
async (req) => {
|
|
134
|
+
return { created: true, email: req.content.email };
|
|
363
135
|
},
|
|
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. Both single-segment (`*`) and multi-segment (`**`) glob patterns are supported:
|
|
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
|
-
"**/__tests__/**", // Exclude any nested __tests__ directory
|
|
460
|
-
"_*.ts", // Exclude files starting with underscore
|
|
461
|
-
],
|
|
462
|
-
};
|
|
136
|
+
);
|
|
463
137
|
```
|
|
464
138
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
Vector achieves exceptional performance through:
|
|
468
|
-
|
|
469
|
-
- **Bun Runtime**: Native TypeScript execution without transpilation
|
|
470
|
-
- **Minimal Dependencies**: Only itty-router (3KB) as dependency
|
|
471
|
-
- **Optimized Routing**: Efficient regex-based route matching
|
|
472
|
-
- **Smart Caching**: Built-in response caching with configurable TTL
|
|
473
|
-
|
|
474
|
-
Benchmarks show Vector handling **100,000+ requests/second** on standard hardware.
|
|
475
|
-
|
|
476
|
-
## Why Vector?
|
|
477
|
-
|
|
478
|
-
### For Encore Users
|
|
479
|
-
Love Encore's declarative API design but need more flexibility? Vector provides the same developer experience with the freedom to deploy anywhere Bun runs.
|
|
139
|
+
Enable OpenAPI in `vector.config.ts`:
|
|
480
140
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
## Error Reference
|
|
488
|
-
|
|
489
|
-
Vector provides comprehensive error responses for all HTTP status codes. All errors return a consistent format:
|
|
490
|
-
|
|
491
|
-
```json
|
|
492
|
-
{
|
|
493
|
-
"error": true,
|
|
494
|
-
"message": "Error message",
|
|
495
|
-
"statusCode": 400,
|
|
496
|
-
"timestamp": "2025-01-01T00:00:00.000Z"
|
|
141
|
+
```ts
|
|
142
|
+
openapi: {
|
|
143
|
+
enabled: true,
|
|
144
|
+
path: '/openapi.json',
|
|
145
|
+
docs: false,
|
|
497
146
|
}
|
|
498
147
|
```
|
|
499
148
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
```typescript
|
|
503
|
-
import { APIError } from "vector-framework";
|
|
504
|
-
|
|
505
|
-
// 400 Bad Request
|
|
506
|
-
APIError.badRequest("Invalid input data");
|
|
507
|
-
|
|
508
|
-
// 401 Unauthorized
|
|
509
|
-
APIError.unauthorized("Authentication required");
|
|
510
|
-
|
|
511
|
-
// 402 Payment Required
|
|
512
|
-
APIError.paymentRequired("Subscription expired");
|
|
513
|
-
|
|
514
|
-
// 403 Forbidden
|
|
515
|
-
APIError.forbidden("Access denied");
|
|
516
|
-
|
|
517
|
-
// 404 Not Found
|
|
518
|
-
APIError.notFound("Resource not found");
|
|
519
|
-
|
|
520
|
-
// 405 Method Not Allowed
|
|
521
|
-
APIError.methodNotAllowed("POST not allowed on this endpoint");
|
|
522
|
-
|
|
523
|
-
// 406 Not Acceptable
|
|
524
|
-
APIError.notAcceptable("Cannot produce requested content type");
|
|
525
|
-
|
|
526
|
-
// 408 Request Timeout
|
|
527
|
-
APIError.requestTimeout("Request took too long");
|
|
528
|
-
|
|
529
|
-
// 409 Conflict
|
|
530
|
-
APIError.conflict("Resource already exists");
|
|
531
|
-
|
|
532
|
-
// 410 Gone
|
|
533
|
-
APIError.gone("Resource permanently deleted");
|
|
534
|
-
|
|
535
|
-
// 411 Length Required
|
|
536
|
-
APIError.lengthRequired("Content-Length header required");
|
|
537
|
-
|
|
538
|
-
// 412 Precondition Failed
|
|
539
|
-
APIError.preconditionFailed("ETag mismatch");
|
|
540
|
-
|
|
541
|
-
// 413 Payload Too Large
|
|
542
|
-
APIError.payloadTooLarge("Request body exceeds limit");
|
|
543
|
-
|
|
544
|
-
// 414 URI Too Long
|
|
545
|
-
APIError.uriTooLong("URL exceeds maximum length");
|
|
149
|
+
## Documentation
|
|
546
150
|
|
|
547
|
-
|
|
548
|
-
APIError.unsupportedMediaType("Content-Type not supported");
|
|
151
|
+
Start here for deeper guides:
|
|
549
152
|
|
|
550
|
-
|
|
551
|
-
|
|
153
|
+
- [Docs Index](docs/README.md)
|
|
154
|
+
- [Configuration](docs/configuration.md)
|
|
155
|
+
- [Routing and Request API](docs/routing.md)
|
|
156
|
+
- [TypeScript Types](docs/typescript.md)
|
|
157
|
+
- [Schema Validation](docs/schema-validation.md)
|
|
158
|
+
- [OpenAPI and Docs UI](docs/openapi.md)
|
|
159
|
+
- [CLI and Route Discovery](docs/cli-and-discovery.md)
|
|
160
|
+
- [Error Reference](docs/errors.md)
|
|
161
|
+
- [Migration Notes](docs/migration.md)
|
|
162
|
+
- [Performance Notes](docs/performance.md)
|
|
552
163
|
|
|
553
|
-
|
|
554
|
-
APIError.expectationFailed("Expect header requirements not met");
|
|
164
|
+
## Examples
|
|
555
165
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
// 422 Unprocessable Entity
|
|
563
|
-
APIError.unprocessableEntity("Validation failed");
|
|
564
|
-
|
|
565
|
-
// 423 Locked
|
|
566
|
-
APIError.locked("Resource is locked");
|
|
567
|
-
|
|
568
|
-
// 424 Failed Dependency
|
|
569
|
-
APIError.failedDependency("Dependent request failed");
|
|
570
|
-
|
|
571
|
-
// 425 Too Early
|
|
572
|
-
APIError.tooEarly("Request is too early");
|
|
573
|
-
|
|
574
|
-
// 426 Upgrade Required
|
|
575
|
-
APIError.upgradeRequired("Protocol upgrade required");
|
|
576
|
-
|
|
577
|
-
// 428 Precondition Required
|
|
578
|
-
APIError.preconditionRequired("Precondition headers required");
|
|
579
|
-
|
|
580
|
-
// 429 Too Many Requests
|
|
581
|
-
APIError.tooManyRequests("Rate limit exceeded");
|
|
582
|
-
|
|
583
|
-
// 431 Request Header Fields Too Large
|
|
584
|
-
APIError.requestHeaderFieldsTooLarge("Headers too large");
|
|
585
|
-
|
|
586
|
-
// 451 Unavailable For Legal Reasons
|
|
587
|
-
APIError.unavailableForLegalReasons("Content blocked for legal reasons");
|
|
588
|
-
```
|
|
589
|
-
|
|
590
|
-
### Server Errors (5xx)
|
|
591
|
-
|
|
592
|
-
```typescript
|
|
593
|
-
// 500 Internal Server Error
|
|
594
|
-
APIError.internalServerError("Something went wrong");
|
|
595
|
-
|
|
596
|
-
// 501 Not Implemented
|
|
597
|
-
APIError.notImplemented("Feature not yet available");
|
|
598
|
-
|
|
599
|
-
// 502 Bad Gateway
|
|
600
|
-
APIError.badGateway("Upstream server error");
|
|
601
|
-
|
|
602
|
-
// 503 Service Unavailable
|
|
603
|
-
APIError.serviceUnavailable("Service temporarily down");
|
|
604
|
-
|
|
605
|
-
// 504 Gateway Timeout
|
|
606
|
-
APIError.gatewayTimeout("Upstream server timeout");
|
|
607
|
-
|
|
608
|
-
// 505 HTTP Version Not Supported
|
|
609
|
-
APIError.httpVersionNotSupported("HTTP/3 not supported");
|
|
610
|
-
|
|
611
|
-
// 506 Variant Also Negotiates
|
|
612
|
-
APIError.variantAlsoNegotiates("Content negotiation error");
|
|
613
|
-
|
|
614
|
-
// 507 Insufficient Storage
|
|
615
|
-
APIError.insufficientStorage("Server storage full");
|
|
616
|
-
|
|
617
|
-
// 508 Loop Detected
|
|
618
|
-
APIError.loopDetected("Infinite loop detected");
|
|
619
|
-
|
|
620
|
-
// 510 Not Extended
|
|
621
|
-
APIError.notExtended("Extension required");
|
|
622
|
-
|
|
623
|
-
// 511 Network Authentication Required
|
|
624
|
-
APIError.networkAuthenticationRequired("Network login required");
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
### Convenience Aliases
|
|
628
|
-
|
|
629
|
-
```typescript
|
|
630
|
-
// Alias for 422 Unprocessable Entity
|
|
631
|
-
APIError.invalidArgument("Field 'email' is required");
|
|
632
|
-
|
|
633
|
-
// Alias for 429 Too Many Requests
|
|
634
|
-
APIError.rateLimitExceeded("Try again in 60 seconds");
|
|
635
|
-
|
|
636
|
-
// Alias for 503 Service Unavailable
|
|
637
|
-
APIError.maintenance("Scheduled maintenance in progress");
|
|
638
|
-
```
|
|
639
|
-
|
|
640
|
-
### Custom Errors
|
|
641
|
-
|
|
642
|
-
```typescript
|
|
643
|
-
// Create error with any status code
|
|
644
|
-
APIError.custom(456, "Custom error message");
|
|
645
|
-
|
|
646
|
-
// With custom content type
|
|
647
|
-
APIError.custom(400, "Invalid XML", "application/xml");
|
|
648
|
-
```
|
|
649
|
-
|
|
650
|
-
### Usage in Routes
|
|
651
|
-
|
|
652
|
-
```typescript
|
|
653
|
-
export const example = route(
|
|
654
|
-
{ method: "POST", path: "/api/users" },
|
|
655
|
-
async (req) => {
|
|
656
|
-
// Validation errors
|
|
657
|
-
if (!req.content?.email) {
|
|
658
|
-
throw APIError.badRequest("Email is required");
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
if (!isValidEmail(req.content.email)) {
|
|
662
|
-
throw APIError.unprocessableEntity("Invalid email format");
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
// Authentication errors
|
|
666
|
-
if (!req.authUser) {
|
|
667
|
-
throw APIError.unauthorized("Please login first");
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
if (req.authUser.role !== "admin") {
|
|
671
|
-
throw APIError.forbidden("Admin access required");
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// Resource errors
|
|
675
|
-
const existingUser = await findUserByEmail(req.content.email);
|
|
676
|
-
if (existingUser) {
|
|
677
|
-
throw APIError.conflict("Email already registered");
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
// Rate limiting
|
|
681
|
-
if (await checkRateLimit(req.authUser.id)) {
|
|
682
|
-
throw APIError.tooManyRequests("Maximum 5 users per hour");
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
try {
|
|
686
|
-
const user = await createUser(req.content);
|
|
687
|
-
return { user };
|
|
688
|
-
} catch (error) {
|
|
689
|
-
// Database errors
|
|
690
|
-
if (error.code === "STORAGE_FULL") {
|
|
691
|
-
throw APIError.insufficientStorage("Database full");
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// Generic server error
|
|
695
|
-
throw APIError.internalServerError("Failed to create user");
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
);
|
|
699
|
-
```
|
|
166
|
+
- [examples/routes/health.ts](examples/routes/health.ts)
|
|
167
|
+
- [examples/routes/events.ts](examples/routes/events.ts)
|
|
168
|
+
- [examples/routes/commerce.ts](examples/routes/commerce.ts)
|
|
169
|
+
- [tests/e2e/test-routes.ts](tests/e2e/test-routes.ts) (broader endpoint patterns)
|
|
170
|
+
- [tests/e2e/test-zod-routes.ts](tests/e2e/test-zod-routes.ts) (Zod + I/O validation flows)
|
|
700
171
|
|
|
701
172
|
## Contributing
|
|
702
173
|
|
|
703
|
-
Contributions are welcome
|
|
174
|
+
Contributions are welcome. Open an issue or pull request.
|
|
704
175
|
|
|
705
176
|
## License
|
|
706
177
|
|