vector-framework 0.8.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/LICENSE +21 -0
- package/README.md +508 -0
- package/dist/auth/protected.d.ts +9 -0
- package/dist/auth/protected.d.ts.map +1 -0
- package/dist/auth/protected.js +26 -0
- package/dist/auth/protected.js.map +1 -0
- package/dist/cache/manager.d.ts +21 -0
- package/dist/cache/manager.d.ts.map +1 -0
- package/dist/cache/manager.js +92 -0
- package/dist/cache/manager.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +142 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/constants/index.d.ts +84 -0
- package/dist/constants/index.d.ts.map +1 -0
- package/dist/constants/index.js +88 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/core/router.d.ts +26 -0
- package/dist/core/router.d.ts.map +1 -0
- package/dist/core/router.js +208 -0
- package/dist/core/router.js.map +1 -0
- package/dist/core/server.d.ts +18 -0
- package/dist/core/server.d.ts.map +1 -0
- package/dist/core/server.js +89 -0
- package/dist/core/server.js.map +1 -0
- package/dist/core/vector.d.ts +43 -0
- package/dist/core/vector.d.ts.map +1 -0
- package/dist/core/vector.js +179 -0
- package/dist/core/vector.js.map +1 -0
- package/dist/dev/route-generator.d.ts +8 -0
- package/dist/dev/route-generator.d.ts.map +1 -0
- package/dist/dev/route-generator.js +77 -0
- package/dist/dev/route-generator.js.map +1 -0
- package/dist/dev/route-scanner.d.ts +9 -0
- package/dist/dev/route-scanner.d.ts.map +1 -0
- package/dist/dev/route-scanner.js +85 -0
- package/dist/dev/route-scanner.js.map +1 -0
- package/dist/errors/index.d.ts +24 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +73 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/http.d.ts +73 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +143 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +21 -0
- package/dist/middleware/manager.d.ts +11 -0
- package/dist/middleware/manager.d.ts.map +1 -0
- package/dist/middleware/manager.js +35 -0
- package/dist/middleware/manager.js.map +1 -0
- package/dist/types/index.d.ts +85 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/logger.d.ts +25 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +68 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/validation.d.ts +5 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +48 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +110 -0
- package/src/auth/protected.ts +41 -0
- package/src/cache/manager.ts +133 -0
- package/src/cli/index.ts +157 -0
- package/src/constants/index.ts +93 -0
- package/src/core/router.ts +258 -0
- package/src/core/server.ts +107 -0
- package/src/core/vector.ts +228 -0
- package/src/dev/route-generator.ts +93 -0
- package/src/dev/route-scanner.ts +97 -0
- package/src/errors/index.ts +91 -0
- package/src/http.ts +331 -0
- package/src/index.ts +19 -0
- package/src/middleware/manager.ts +53 -0
- package/src/types/index.ts +126 -0
- package/src/utils/logger.ts +87 -0
- package/src/utils/validation.ts +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 webhie-com
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
# Vector Framework
|
|
2
|
+
|
|
3
|
+
**The speed of Bun. The developer experience you love.**
|
|
4
|
+
|
|
5
|
+
Vector brings the blazing performance of Bun to developers who appreciate the simplicity and elegance of frameworks like Encore. Build production-ready APIs with a familiar, declarative syntax while leveraging Bun's incredible speed.
|
|
6
|
+
|
|
7
|
+
## Why Vector?
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Fast & Lightweight** - Built on Bun and itty-router for maximum performance
|
|
14
|
+
- **Type-Safe** - Full TypeScript support with excellent type inference
|
|
15
|
+
- **Auto Route Discovery** - Automatically discovers and loads routes from your filesystem
|
|
16
|
+
- **Middleware System** - Flexible pre/post request middleware pipeline
|
|
17
|
+
- **Built-in Authentication** - Simple but powerful authentication system
|
|
18
|
+
- **Response Caching** - Automatic response caching with configurable TTL
|
|
19
|
+
- **CORS Support** - Configurable CORS with sensible defaults
|
|
20
|
+
- **Developer Experience** - Auto route discovery and CLI tools
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bun add vector
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Your First API (Encore-style)
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
// routes/hello.ts
|
|
34
|
+
import { route } from "vector";
|
|
35
|
+
|
|
36
|
+
// Public endpoint - clean and declarative
|
|
37
|
+
export const hello = route(
|
|
38
|
+
{
|
|
39
|
+
method: "GET",
|
|
40
|
+
path: "/hello/:name",
|
|
41
|
+
expose: true,
|
|
42
|
+
},
|
|
43
|
+
async (req) => {
|
|
44
|
+
const { name } = req.params!;
|
|
45
|
+
return { message: `Hello ${name}!` };
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Protected endpoint - auth built-in
|
|
50
|
+
export const getProfile = route(
|
|
51
|
+
{
|
|
52
|
+
method: "GET",
|
|
53
|
+
path: "/profile",
|
|
54
|
+
expose: true,
|
|
55
|
+
auth: true, // That's it! Auth handled.
|
|
56
|
+
},
|
|
57
|
+
async (req) => {
|
|
58
|
+
return {
|
|
59
|
+
user: req.authUser,
|
|
60
|
+
lastLogin: new Date(),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Start Your Server
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// server.ts
|
|
70
|
+
import vector from "vector";
|
|
71
|
+
|
|
72
|
+
// Set up auth (once, globally)
|
|
73
|
+
vector.protected = async (request) => {
|
|
74
|
+
const token = request.headers.get("Authorization")?.replace("Bearer ", "");
|
|
75
|
+
// Your auth logic here
|
|
76
|
+
if (token === "valid-token") {
|
|
77
|
+
return { id: "user-123", email: "user@example.com" };
|
|
78
|
+
}
|
|
79
|
+
throw new Error("Invalid token");
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Start server - routes auto-discovered from ./routes
|
|
83
|
+
vector.serve({ port: 3000 });
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Familiar Patterns, Modern Performance
|
|
87
|
+
|
|
88
|
+
### Real-World Example: User Service
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// routes/users.ts
|
|
92
|
+
import { route } from "vector";
|
|
93
|
+
import { db } from "../db";
|
|
94
|
+
|
|
95
|
+
// Public endpoint with caching
|
|
96
|
+
export const listUsers = route(
|
|
97
|
+
{
|
|
98
|
+
method: "GET",
|
|
99
|
+
path: "/users",
|
|
100
|
+
expose: true,
|
|
101
|
+
cache: 300, // Cache for 5 minutes
|
|
102
|
+
},
|
|
103
|
+
async () => {
|
|
104
|
+
const users = await db.user.findMany();
|
|
105
|
+
return { users };
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Protected endpoint with automatic body parsing
|
|
110
|
+
export const createUser = route(
|
|
111
|
+
{
|
|
112
|
+
method: "POST",
|
|
113
|
+
path: "/users",
|
|
114
|
+
expose: true,
|
|
115
|
+
auth: true, // Auth required
|
|
116
|
+
},
|
|
117
|
+
async (req) => {
|
|
118
|
+
const { name, email } = req.content; // Type-safe, auto-parsed
|
|
119
|
+
|
|
120
|
+
const user = await db.user.create({
|
|
121
|
+
data: { name, email },
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return { user };
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// Parameter extraction made simple
|
|
129
|
+
export const getUser = route(
|
|
130
|
+
{
|
|
131
|
+
method: "GET",
|
|
132
|
+
path: "/users/:id",
|
|
133
|
+
expose: true,
|
|
134
|
+
},
|
|
135
|
+
async (req) => {
|
|
136
|
+
const { id } = req.params!;
|
|
137
|
+
const user = await db.user.findUnique({ where: { id } });
|
|
138
|
+
|
|
139
|
+
if (!user) {
|
|
140
|
+
throw new APIError("User not found", 404);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return { user };
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Why Choose Vector?
|
|
149
|
+
|
|
150
|
+
### 🚀 Bun-Powered Performance
|
|
151
|
+
|
|
152
|
+
- **Native TypeScript** execution without transpilation overhead
|
|
153
|
+
- **Significantly faster** startup times and request handling
|
|
154
|
+
- **Minimal memory footprint** thanks to Bun's efficient runtime
|
|
155
|
+
|
|
156
|
+
### 💡 Developer Experience
|
|
157
|
+
|
|
158
|
+
- **Encore-inspired API** - If you know Encore, you already know Vector
|
|
159
|
+
- **Auto route discovery** - Just write your routes, we'll find them
|
|
160
|
+
- **Type safety everywhere** - Full TypeScript support
|
|
161
|
+
- **Built-in essentials** - Auth, caching, CORS, middleware - all included
|
|
162
|
+
|
|
163
|
+
### 🔄 Easy Migration
|
|
164
|
+
|
|
165
|
+
Moving from Express, Fastify, or Encore? Vector makes it simple:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// Encore-style (what you know)
|
|
169
|
+
export const get = api(
|
|
170
|
+
{ expose: true, method: "GET", path: "/hello/:name" },
|
|
171
|
+
async ({ name }: { name: string }): Promise<Response> => {
|
|
172
|
+
const msg = `Hello ${name}!`;
|
|
173
|
+
return { message: msg };
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Vector-style (what you write)
|
|
178
|
+
export const hello = route(
|
|
179
|
+
{ expose: true, method: "GET", path: "/hello/:name" },
|
|
180
|
+
async (req) => {
|
|
181
|
+
return { message: `Hello ${req.params.name}!` };
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## For Encore Users
|
|
187
|
+
|
|
188
|
+
Switching from Encore? You'll feel right at home. Vector provides the same declarative, type-safe API design with the performance benefits of Bun:
|
|
189
|
+
|
|
190
|
+
| Encore | Vector |
|
|
191
|
+
| ---------------------- | -------------------------------- |
|
|
192
|
+
| `api.requireAuth()` | `{ auth: true }` in route config |
|
|
193
|
+
| Auto-generated clients | Not yet available |
|
|
194
|
+
| Built-in tracing | Middleware support |
|
|
195
|
+
| Cloud deployment | Deploy anywhere Bun runs |
|
|
196
|
+
|
|
197
|
+
**The key difference:** Vector runs on Bun, giving you significantly better performance and lower resource usage while maintaining the developer experience you love.
|
|
198
|
+
|
|
199
|
+
## CLI Commands
|
|
200
|
+
|
|
201
|
+
Vector includes a built-in CLI for development and production:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# Development server
|
|
205
|
+
bun run dev
|
|
206
|
+
|
|
207
|
+
# Production server
|
|
208
|
+
bun run start
|
|
209
|
+
|
|
210
|
+
# Run with custom options
|
|
211
|
+
bun run src/cli/index.ts dev --port 3000 --routes ./api
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Or use npm scripts:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# Start development server
|
|
218
|
+
bun run dev
|
|
219
|
+
|
|
220
|
+
# Start production server
|
|
221
|
+
bun run start
|
|
222
|
+
|
|
223
|
+
# Build for production
|
|
224
|
+
bun run build
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Route Options
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
interface RouteOptions {
|
|
231
|
+
method: string; // HTTP method (GET, POST, etc.)
|
|
232
|
+
path: string; // Route path with params (/users/:id)
|
|
233
|
+
expose?: boolean; // Make route accessible (default: false)
|
|
234
|
+
auth?: boolean; // Require authentication (default: false)
|
|
235
|
+
cache?:
|
|
236
|
+
| number
|
|
237
|
+
| {
|
|
238
|
+
// Cache configuration
|
|
239
|
+
ttl: number; // Time to live in seconds
|
|
240
|
+
key?: string; // Custom cache key
|
|
241
|
+
};
|
|
242
|
+
rawRequest?: boolean; // Skip body parsing (default: false)
|
|
243
|
+
rawResponse?: boolean; // Return raw response (default: false)
|
|
244
|
+
responseContentType?: string; // Response content type
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Middleware
|
|
249
|
+
|
|
250
|
+
### Before Middleware (Pre-handlers)
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
const authMiddleware = async (request) => {
|
|
254
|
+
// Modify request or return Response to short-circuit
|
|
255
|
+
request.customData = "value";
|
|
256
|
+
return request;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const rateLimitMiddleware = async (request) => {
|
|
260
|
+
// Return Response to stop processing
|
|
261
|
+
if (tooManyRequests) {
|
|
262
|
+
return new Response("Too Many Requests", { status: 429 });
|
|
263
|
+
}
|
|
264
|
+
return request;
|
|
265
|
+
};
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Finally Middleware (Post-handlers)
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
const corsMiddleware = async (response, request) => {
|
|
272
|
+
// Modify response headers
|
|
273
|
+
response.headers.set("X-Custom-Header", "value");
|
|
274
|
+
return response;
|
|
275
|
+
};
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Authentication
|
|
279
|
+
|
|
280
|
+
Implement your authentication logic:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
vector.protected = async (request) => {
|
|
284
|
+
const authHeader = request.headers.get("Authorization");
|
|
285
|
+
|
|
286
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
287
|
+
throw new Error("Invalid authorization header");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const token = authHeader.substring(7);
|
|
291
|
+
const user = await validateToken(token);
|
|
292
|
+
|
|
293
|
+
if (!user) {
|
|
294
|
+
throw new Error("Invalid token");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return user; // This will be available as req.authUser
|
|
298
|
+
};
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Caching
|
|
302
|
+
|
|
303
|
+
Implement your caching strategy:
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
vector.cache = async (key, factory, ttl) => {
|
|
307
|
+
// Example with Redis
|
|
308
|
+
const cached = await redis.get(key);
|
|
309
|
+
if (cached) return JSON.parse(cached);
|
|
310
|
+
|
|
311
|
+
const value = await factory();
|
|
312
|
+
await redis.setex(key, ttl, JSON.stringify(value));
|
|
313
|
+
|
|
314
|
+
return value;
|
|
315
|
+
};
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Configuration
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
interface VectorConfig {
|
|
322
|
+
port?: number; // Server port (default: 3000)
|
|
323
|
+
hostname?: string; // Server hostname (default: localhost)
|
|
324
|
+
reusePort?: boolean; // Reuse port (default: true)
|
|
325
|
+
development?: boolean; // Development mode
|
|
326
|
+
routesDir?: string; // Routes directory (default: ./routes)
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Middleware Configuration
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
// Add before middleware (runs before routes)
|
|
334
|
+
vector.before(async (request) => {
|
|
335
|
+
console.log(`${request.method} ${request.url}`);
|
|
336
|
+
return request;
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Add finally middleware (runs after routes)
|
|
340
|
+
vector.finally(async (response, request) => {
|
|
341
|
+
response.headers.set("X-Response-Time", Date.now() - request.startTime);
|
|
342
|
+
return response;
|
|
343
|
+
});
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Project Structure
|
|
347
|
+
|
|
348
|
+
```
|
|
349
|
+
my-app/
|
|
350
|
+
├── routes/ # Auto-discovered routes
|
|
351
|
+
│ ├── users.ts
|
|
352
|
+
│ ├── posts.ts
|
|
353
|
+
│ └── health.ts
|
|
354
|
+
├── middleware/ # Custom middleware
|
|
355
|
+
│ ├── auth.ts
|
|
356
|
+
│ └── logging.ts
|
|
357
|
+
├── server.ts # Main server file
|
|
358
|
+
└── package.json
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## TypeScript Support
|
|
362
|
+
|
|
363
|
+
Vector is written in TypeScript and provides full type safety with customizable types:
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
import { createVector, route, APIError } from "vector";
|
|
367
|
+
import type { VectorRequest, VectorTypes } from "vector";
|
|
368
|
+
|
|
369
|
+
// Define your custom user type
|
|
370
|
+
interface MyUser {
|
|
371
|
+
id: string;
|
|
372
|
+
email: string;
|
|
373
|
+
role: "admin" | "user";
|
|
374
|
+
permissions: string[];
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Extend Vector types
|
|
378
|
+
interface MyAppTypes extends VectorTypes {
|
|
379
|
+
auth: MyUser;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Create typed Vector instance
|
|
383
|
+
const vector = createVector<MyAppTypes>();
|
|
384
|
+
|
|
385
|
+
// Configure authentication with your custom type
|
|
386
|
+
vector.protected = async (request): Promise<MyUser> => {
|
|
387
|
+
// Your auth logic here
|
|
388
|
+
return {
|
|
389
|
+
id: "user-123",
|
|
390
|
+
email: "user@example.com",
|
|
391
|
+
role: "admin",
|
|
392
|
+
permissions: ["read", "write"],
|
|
393
|
+
};
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// Routes automatically have typed authUser
|
|
397
|
+
vector.route(
|
|
398
|
+
{ method: "GET", path: "/admin", expose: true, auth: true },
|
|
399
|
+
async (request) => {
|
|
400
|
+
// request.authUser is typed as MyUser
|
|
401
|
+
if (request.authUser?.role !== "admin") {
|
|
402
|
+
throw APIError.forbidden("Admin access required");
|
|
403
|
+
}
|
|
404
|
+
return { adminData: "..." };
|
|
405
|
+
}
|
|
406
|
+
);
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## Error Handling
|
|
410
|
+
|
|
411
|
+
Vector provides comprehensive built-in error responses for all HTTP status codes:
|
|
412
|
+
|
|
413
|
+
### Common Client Errors (4xx)
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
import { APIError } from "vector";
|
|
417
|
+
|
|
418
|
+
// Basic errors
|
|
419
|
+
APIError.badRequest("Invalid input"); // 400
|
|
420
|
+
APIError.unauthorized("Please login"); // 401
|
|
421
|
+
APIError.forbidden("Access denied"); // 403
|
|
422
|
+
APIError.notFound("Resource not found"); // 404
|
|
423
|
+
APIError.conflict("Resource already exists"); // 409
|
|
424
|
+
|
|
425
|
+
// Validation and input errors
|
|
426
|
+
APIError.unprocessableEntity("Invalid data"); // 422
|
|
427
|
+
APIError.invalidArgument("Field required"); // 422 (alias)
|
|
428
|
+
APIError.payloadTooLarge("File too large"); // 413
|
|
429
|
+
APIError.unsupportedMediaType("Invalid type"); // 415
|
|
430
|
+
|
|
431
|
+
// Rate limiting and timeouts
|
|
432
|
+
APIError.tooManyRequests("Rate limit exceeded"); // 429
|
|
433
|
+
APIError.rateLimitExceeded("Try again later"); // 429 (alias)
|
|
434
|
+
APIError.requestTimeout("Request took too long"); // 408
|
|
435
|
+
|
|
436
|
+
// Method and protocol errors
|
|
437
|
+
APIError.methodNotAllowed("POST not allowed"); // 405
|
|
438
|
+
APIError.notAcceptable("Cannot produce response"); // 406
|
|
439
|
+
APIError.preconditionFailed("ETag mismatch"); // 412
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Server Errors (5xx)
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
// Server errors
|
|
446
|
+
APIError.internalServerError("Something went wrong"); // 500
|
|
447
|
+
APIError.notImplemented("Feature coming soon"); // 501
|
|
448
|
+
APIError.badGateway("Upstream server error"); // 502
|
|
449
|
+
APIError.serviceUnavailable("Service down"); // 503
|
|
450
|
+
APIError.maintenance("Under maintenance"); // 503 (alias)
|
|
451
|
+
APIError.gatewayTimeout("Upstream timeout"); // 504
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Custom Errors
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
// Create custom error with any status code
|
|
458
|
+
APIError.custom(456, 'Custom error message');
|
|
459
|
+
|
|
460
|
+
// All errors include additional metadata
|
|
461
|
+
// Response format:
|
|
462
|
+
{
|
|
463
|
+
"error": true,
|
|
464
|
+
"message": "Error message",
|
|
465
|
+
"statusCode": 400,
|
|
466
|
+
"timestamp": "2025-01-01T00:00:00.000Z"
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Usage in Routes
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
vector.route(
|
|
474
|
+
{ method: "GET", path: "/api/data/:id", expose: true },
|
|
475
|
+
async (req) => {
|
|
476
|
+
// Validation
|
|
477
|
+
if (!req.params?.id) {
|
|
478
|
+
throw APIError.badRequest("ID is required");
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Check rate limits
|
|
482
|
+
if (await isRateLimited(req)) {
|
|
483
|
+
throw APIError.tooManyRequests("Please wait before trying again");
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Fetch data
|
|
487
|
+
const data = await fetchData(req.params.id);
|
|
488
|
+
if (!data) {
|
|
489
|
+
throw APIError.notFound("Data not found");
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Check permissions
|
|
493
|
+
if (!canAccess(req.authUser, data)) {
|
|
494
|
+
throw APIError.forbidden("You cannot access this resource");
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return data;
|
|
498
|
+
}
|
|
499
|
+
);
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
## Contributing
|
|
503
|
+
|
|
504
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
505
|
+
|
|
506
|
+
## License
|
|
507
|
+
|
|
508
|
+
MIT
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DefaultVectorTypes, GetAuthType, ProtectedHandler, VectorRequest, VectorTypes } from '../types';
|
|
2
|
+
export declare class AuthManager<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
3
|
+
private protectedHandler;
|
|
4
|
+
setProtectedHandler(handler: ProtectedHandler<TTypes>): void;
|
|
5
|
+
authenticate(request: VectorRequest<TTypes>): Promise<GetAuthType<TTypes> | null>;
|
|
6
|
+
isAuthenticated(request: VectorRequest<TTypes>): boolean;
|
|
7
|
+
getUser(request: VectorRequest<TTypes>): GetAuthType<TTypes> | null;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=protected.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protected.d.ts","sourceRoot":"","sources":["../../src/auth/protected.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,WAAW,EACZ,MAAM,UAAU,CAAC;AAElB,qBAAa,WAAW,CAAC,MAAM,SAAS,WAAW,GAAG,kBAAkB;IACtE,OAAO,CAAC,gBAAgB,CAAyC;IAEjE,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,CAAC,MAAM,CAAC;IAI/C,YAAY,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAkBvF,eAAe,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO;IAIxD,OAAO,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI;CAGpE"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export class AuthManager {
|
|
2
|
+
protectedHandler = null;
|
|
3
|
+
setProtectedHandler(handler) {
|
|
4
|
+
this.protectedHandler = handler;
|
|
5
|
+
}
|
|
6
|
+
async authenticate(request) {
|
|
7
|
+
if (!this.protectedHandler) {
|
|
8
|
+
throw new Error('Protected handler not configured. Use vector.protected() to set authentication handler.');
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const authUser = await this.protectedHandler(request);
|
|
12
|
+
request.authUser = authUser;
|
|
13
|
+
return authUser;
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
throw new Error(`Authentication failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
isAuthenticated(request) {
|
|
20
|
+
return !!request.authUser;
|
|
21
|
+
}
|
|
22
|
+
getUser(request) {
|
|
23
|
+
return request.authUser || null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=protected.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protected.js","sourceRoot":"","sources":["../../src/auth/protected.ts"],"names":[],"mappings":"AAQA,MAAM,OAAO,WAAW;IACd,gBAAgB,GAAoC,IAAI,CAAC;IAEjE,mBAAmB,CAAC,OAAiC;QACnD,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,OAA8B;QAC/C,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CACb,yFAAyF,CAC1F,CAAC;QACJ,CAAC;QAED,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;YACtD,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAC5B,OAAO,QAAQ,CAAC;QAClB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,0BAA0B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACnF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,eAAe,CAAC,OAA8B;QAC5C,OAAO,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IAC5B,CAAC;IAED,OAAO,CAAC,OAA8B;QACpC,OAAO,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC;IAClC,CAAC;CACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CacheHandler, DefaultVectorTypes, GetCacheType, VectorTypes } from '../types';
|
|
2
|
+
export declare class CacheManager<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
3
|
+
private cacheHandler;
|
|
4
|
+
private memoryCache;
|
|
5
|
+
private cleanupInterval;
|
|
6
|
+
setCacheHandler(handler: CacheHandler): void;
|
|
7
|
+
get<T = GetCacheType<TTypes>>(key: string, factory: () => Promise<T>, ttl?: number): Promise<T>;
|
|
8
|
+
private getFromMemoryCache;
|
|
9
|
+
private isCacheValid;
|
|
10
|
+
private setInMemoryCache;
|
|
11
|
+
private scheduleCleanup;
|
|
12
|
+
private cleanupExpired;
|
|
13
|
+
clear(): void;
|
|
14
|
+
set<T = GetCacheType<TTypes>>(key: string, value: T, ttl?: number): Promise<void>;
|
|
15
|
+
delete(key: string): boolean;
|
|
16
|
+
has(key: string): boolean;
|
|
17
|
+
generateKey(request: Request, options?: {
|
|
18
|
+
authUser?: any;
|
|
19
|
+
}): string;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/cache/manager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAO5F,qBAAa,YAAY,CAAC,MAAM,SAAS,WAAW,GAAG,kBAAkB;IACvE,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,WAAW,CAAsC;IACzD,OAAO,CAAC,eAAe,CAAsB;IAE7C,eAAe,CAAC,OAAO,EAAE,YAAY;IAI/B,GAAG,CAAC,CAAC,GAAG,YAAY,CAAC,MAAM,CAAC,EAChC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,GAAG,GAAE,MAAiC,GACrC,OAAO,CAAC,CAAC,CAAC;YAYC,kBAAkB;IAkBhC,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,cAAc;IActB,KAAK,IAAI,IAAI;IAQP,GAAG,CAAC,CAAC,GAAG,YAAY,CAAC,MAAM,CAAC,EAChC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,CAAC,EACR,GAAG,GAAE,MAAiC,GACrC,OAAO,CAAC,IAAI,CAAC;IAchB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAI5B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAYzB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,GAAG,CAAA;KAAE,GAAG,MAAM;CAMpE"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { DEFAULT_CONFIG } from '../constants';
|
|
2
|
+
export class CacheManager {
|
|
3
|
+
cacheHandler = null;
|
|
4
|
+
memoryCache = new Map();
|
|
5
|
+
cleanupInterval = null;
|
|
6
|
+
setCacheHandler(handler) {
|
|
7
|
+
this.cacheHandler = handler;
|
|
8
|
+
}
|
|
9
|
+
async get(key, factory, ttl = DEFAULT_CONFIG.CACHE_TTL) {
|
|
10
|
+
if (ttl <= 0) {
|
|
11
|
+
return factory();
|
|
12
|
+
}
|
|
13
|
+
if (this.cacheHandler) {
|
|
14
|
+
return this.cacheHandler(key, factory, ttl);
|
|
15
|
+
}
|
|
16
|
+
return this.getFromMemoryCache(key, factory, ttl);
|
|
17
|
+
}
|
|
18
|
+
async getFromMemoryCache(key, factory, ttl) {
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
const cached = this.memoryCache.get(key);
|
|
21
|
+
if (this.isCacheValid(cached, now)) {
|
|
22
|
+
return cached.value;
|
|
23
|
+
}
|
|
24
|
+
const value = await factory();
|
|
25
|
+
this.setInMemoryCache(key, value, ttl);
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
isCacheValid(entry, now) {
|
|
29
|
+
return entry !== undefined && entry.expires > now;
|
|
30
|
+
}
|
|
31
|
+
setInMemoryCache(key, value, ttl) {
|
|
32
|
+
const expires = Date.now() + ttl * 1000;
|
|
33
|
+
this.memoryCache.set(key, { value, expires });
|
|
34
|
+
this.scheduleCleanup();
|
|
35
|
+
}
|
|
36
|
+
scheduleCleanup() {
|
|
37
|
+
if (this.cleanupInterval)
|
|
38
|
+
return;
|
|
39
|
+
this.cleanupInterval = setInterval(() => {
|
|
40
|
+
this.cleanupExpired();
|
|
41
|
+
}, 60000); // Clean up every minute
|
|
42
|
+
}
|
|
43
|
+
cleanupExpired() {
|
|
44
|
+
const now = Date.now();
|
|
45
|
+
for (const [key, entry] of this.memoryCache.entries()) {
|
|
46
|
+
if (entry.expires <= now) {
|
|
47
|
+
this.memoryCache.delete(key);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (this.memoryCache.size === 0 && this.cleanupInterval) {
|
|
51
|
+
clearInterval(this.cleanupInterval);
|
|
52
|
+
this.cleanupInterval = null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
clear() {
|
|
56
|
+
this.memoryCache.clear();
|
|
57
|
+
if (this.cleanupInterval) {
|
|
58
|
+
clearInterval(this.cleanupInterval);
|
|
59
|
+
this.cleanupInterval = null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async set(key, value, ttl = DEFAULT_CONFIG.CACHE_TTL) {
|
|
63
|
+
if (ttl <= 0) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (this.cacheHandler) {
|
|
67
|
+
// Custom cache handler can implement its own set logic
|
|
68
|
+
await this.cacheHandler(key, async () => value, ttl);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
this.setInMemoryCache(key, value, ttl);
|
|
72
|
+
}
|
|
73
|
+
delete(key) {
|
|
74
|
+
return this.memoryCache.delete(key);
|
|
75
|
+
}
|
|
76
|
+
has(key) {
|
|
77
|
+
const entry = this.memoryCache.get(key);
|
|
78
|
+
if (!entry)
|
|
79
|
+
return false;
|
|
80
|
+
if (entry.expires <= Date.now()) {
|
|
81
|
+
this.memoryCache.delete(key);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
generateKey(request, options) {
|
|
87
|
+
const url = new URL(request.url);
|
|
88
|
+
const parts = [request.method, url.pathname, url.search, options?.authUser?.id || 'anonymous'];
|
|
89
|
+
return parts.join(':');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/cache/manager.ts"],"names":[],"mappings":"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"}
|