@superfunctions/http 0.1.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 +261 -0
- package/dist/context.d.ts +13 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +47 -0
- package/dist/context.js.map +1 -0
- package/dist/errors.d.ts +43 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +87 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/cors.d.ts +9 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +67 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware/index.d.ts +5 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +5 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware.d.ts +17 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +31 -0
- package/dist/middleware.js.map +1 -0
- package/dist/path-matcher.d.ts +31 -0
- package/dist/path-matcher.d.ts.map +1 -0
- package/dist/path-matcher.js +65 -0
- package/dist/path-matcher.js.map +1 -0
- package/dist/router.d.ts +9 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +132 -0
- package/dist/router.js.map +1 -0
- package/dist/types.d.ts +87 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# @superfunctions/http
|
|
2
|
+
|
|
3
|
+
Framework-agnostic HTTP abstraction layer for building web APIs that work across Express, Hono, Fastify, Next.js, and more.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Framework Agnostic**: Write your API logic once, deploy to any framework
|
|
8
|
+
- **Web Standards**: Built on standard `Request` and `Response` APIs
|
|
9
|
+
- **Type Safe**: Full TypeScript support with generic context types
|
|
10
|
+
- **Zero Dependencies**: Core package has no runtime dependencies
|
|
11
|
+
- **Middleware Support**: Compose middleware chains for authentication, CORS, etc.
|
|
12
|
+
- **Path Parameters**: Express-style path patterns with parameter extraction
|
|
13
|
+
- **Edge Compatible**: Works in edge runtimes (Cloudflare Workers, Deno, Bun)
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @superfunctions/http
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For framework adapters:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Express
|
|
25
|
+
npm install @superfunctions/http-express
|
|
26
|
+
|
|
27
|
+
# Hono
|
|
28
|
+
npm install @superfunctions/http-hono
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### Define Your API (Framework Agnostic)
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { createRouter } from '@superfunctions/http';
|
|
37
|
+
|
|
38
|
+
const apiRouter = createRouter({
|
|
39
|
+
routes: [
|
|
40
|
+
{
|
|
41
|
+
method: 'GET',
|
|
42
|
+
path: '/users',
|
|
43
|
+
handler: async () => {
|
|
44
|
+
const users = await db.query('SELECT * FROM users');
|
|
45
|
+
return Response.json(users);
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
method: 'GET',
|
|
50
|
+
path: '/users/:id',
|
|
51
|
+
handler: async (req, ctx) => {
|
|
52
|
+
const user = await db.query(
|
|
53
|
+
'SELECT * FROM users WHERE id = ?',
|
|
54
|
+
[ctx.params.id]
|
|
55
|
+
);
|
|
56
|
+
return Response.json(user);
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
method: 'POST',
|
|
61
|
+
path: '/users',
|
|
62
|
+
handler: async (req, ctx) => {
|
|
63
|
+
const data = await ctx.json();
|
|
64
|
+
const user = await db.insert('users', data);
|
|
65
|
+
return Response.json(user, { status: 201 });
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Use with Express
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import express from 'express';
|
|
76
|
+
import { toExpressRouter } from '@superfunctions/http-express';
|
|
77
|
+
import { apiRouter } from './api';
|
|
78
|
+
|
|
79
|
+
const app = express();
|
|
80
|
+
app.use(express.json());
|
|
81
|
+
app.use('/api', toExpressRouter(apiRouter));
|
|
82
|
+
app.listen(3000);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Use with Hono
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { Hono } from 'hono';
|
|
89
|
+
import { toHonoHandler } from '@superfunctions/http-hono';
|
|
90
|
+
import { apiRouter } from './api';
|
|
91
|
+
|
|
92
|
+
const app = new Hono();
|
|
93
|
+
app.route('/api', toHonoHandler(apiRouter));
|
|
94
|
+
|
|
95
|
+
export default app;
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Use Directly (Universal Handler)
|
|
99
|
+
|
|
100
|
+
For Fetch-native frameworks:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { createRouter } from '@superfunctions/http';
|
|
104
|
+
|
|
105
|
+
const router = createRouter({ routes: [...] });
|
|
106
|
+
|
|
107
|
+
// Cloudflare Workers
|
|
108
|
+
export default {
|
|
109
|
+
fetch: (req) => router.handler(req)
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Deno
|
|
113
|
+
Deno.serve((req) => router.handler(req));
|
|
114
|
+
|
|
115
|
+
// Bun
|
|
116
|
+
Bun.serve({ fetch: (req) => router.handler(req) });
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Features
|
|
120
|
+
|
|
121
|
+
### Middleware
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
const authMiddleware = async (req, ctx, next) => {
|
|
125
|
+
const token = req.headers.get('Authorization');
|
|
126
|
+
if (!token) {
|
|
127
|
+
return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
|
128
|
+
}
|
|
129
|
+
ctx.user = await verifyToken(token);
|
|
130
|
+
return next();
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const router = createRouter({
|
|
134
|
+
middleware: [authMiddleware],
|
|
135
|
+
routes: [...],
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Context
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
interface AppContext {
|
|
143
|
+
db: Database;
|
|
144
|
+
user?: User;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const router = createRouter<AppContext>({
|
|
148
|
+
context: async (req) => ({
|
|
149
|
+
db: adapter,
|
|
150
|
+
user: await getUser(req),
|
|
151
|
+
}),
|
|
152
|
+
routes: [
|
|
153
|
+
{
|
|
154
|
+
method: 'GET',
|
|
155
|
+
path: '/profile',
|
|
156
|
+
handler: async (req, ctx) => {
|
|
157
|
+
// ctx is typed as AppContext & RouteContext
|
|
158
|
+
return Response.json({ user: ctx.user });
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Error Handling
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { RouterError, UnauthorizedError } from '@superfunctions/http';
|
|
169
|
+
|
|
170
|
+
const router = createRouter({
|
|
171
|
+
routes: [
|
|
172
|
+
{
|
|
173
|
+
method: 'GET',
|
|
174
|
+
path: '/protected',
|
|
175
|
+
handler: async () => {
|
|
176
|
+
throw new UnauthorizedError('Invalid token');
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
onError: (error, req) => {
|
|
181
|
+
console.error('Error:', error);
|
|
182
|
+
return Response.json(
|
|
183
|
+
{ error: error.message },
|
|
184
|
+
{ status: error instanceof RouterError ? error.statusCode : 500 }
|
|
185
|
+
);
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### CORS
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { corsMiddleware } from '@superfunctions/http/middleware';
|
|
194
|
+
|
|
195
|
+
const router = createRouter({
|
|
196
|
+
middleware: [
|
|
197
|
+
corsMiddleware({
|
|
198
|
+
origin: 'https://example.com',
|
|
199
|
+
methods: ['GET', 'POST'],
|
|
200
|
+
credentials: true,
|
|
201
|
+
}),
|
|
202
|
+
],
|
|
203
|
+
routes: [...],
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## API Reference
|
|
208
|
+
|
|
209
|
+
### `createRouter<TContext>(options)`
|
|
210
|
+
|
|
211
|
+
Creates a framework-agnostic router.
|
|
212
|
+
|
|
213
|
+
**Options:**
|
|
214
|
+
- `routes`: Array of route definitions
|
|
215
|
+
- `middleware?`: Global middleware array
|
|
216
|
+
- `context?`: Static context or factory function
|
|
217
|
+
- `onError?`: Custom error handler
|
|
218
|
+
- `basePath?`: Base path prefix for all routes
|
|
219
|
+
- `cors?`: CORS configuration
|
|
220
|
+
|
|
221
|
+
**Returns:** `Router<TContext>`
|
|
222
|
+
|
|
223
|
+
### Route Definition
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
interface Route<TContext> {
|
|
227
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';
|
|
228
|
+
path: string;
|
|
229
|
+
handler: RouteHandler<TContext>;
|
|
230
|
+
middleware?: Middleware<TContext>[];
|
|
231
|
+
meta?: Record<string, any>;
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Route Context
|
|
236
|
+
|
|
237
|
+
Every handler receives a context with:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
interface RouteContext {
|
|
241
|
+
params: Record<string, string>; // Path parameters
|
|
242
|
+
query: URLSearchParams; // Query string
|
|
243
|
+
url: URL; // Full URL
|
|
244
|
+
json: () => Promise<any>; // Parse JSON body
|
|
245
|
+
formData: () => Promise<FormData>; // Parse form data
|
|
246
|
+
text: () => Promise<string>; // Get text body
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Framework Adapters
|
|
251
|
+
|
|
252
|
+
| Framework | Package | Function |
|
|
253
|
+
|-----------|---------|----------|
|
|
254
|
+
| Express | `@superfunctions/http-express` | `toExpressRouter()` |
|
|
255
|
+
| Hono | `@superfunctions/http-hono` | `toHonoHandler()` |
|
|
256
|
+
|
|
257
|
+
More adapters coming soon: Fastify, Next.js, SvelteKit, Remix, etc.
|
|
258
|
+
|
|
259
|
+
## License
|
|
260
|
+
|
|
261
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context helpers for request handling
|
|
3
|
+
*/
|
|
4
|
+
import type { RouteContext } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Create a RouteContext from a Request and path params
|
|
7
|
+
*/
|
|
8
|
+
export declare function createRouteContext(request: Request, params: Record<string, string>): RouteContext;
|
|
9
|
+
/**
|
|
10
|
+
* Merge user context with route context
|
|
11
|
+
*/
|
|
12
|
+
export declare function mergeContexts<TContext>(userContext: TContext, routeContext: RouteContext): TContext & RouteContext;
|
|
13
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,YAAY,CAmCd;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EACpC,WAAW,EAAE,QAAQ,EACrB,YAAY,EAAE,YAAY,GACzB,QAAQ,GAAG,YAAY,CAKzB"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context helpers for request handling
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Create a RouteContext from a Request and path params
|
|
6
|
+
*/
|
|
7
|
+
export function createRouteContext(request, params) {
|
|
8
|
+
const url = new URL(request.url);
|
|
9
|
+
const query = url.searchParams;
|
|
10
|
+
// Cache for parsed bodies to avoid multiple parsing
|
|
11
|
+
let jsonCache = null;
|
|
12
|
+
let textCache = null;
|
|
13
|
+
let formDataCache = null;
|
|
14
|
+
return {
|
|
15
|
+
params,
|
|
16
|
+
query,
|
|
17
|
+
url,
|
|
18
|
+
json: async () => {
|
|
19
|
+
if (!jsonCache) {
|
|
20
|
+
jsonCache = request.json();
|
|
21
|
+
}
|
|
22
|
+
return jsonCache;
|
|
23
|
+
},
|
|
24
|
+
text: async () => {
|
|
25
|
+
if (!textCache) {
|
|
26
|
+
textCache = request.text();
|
|
27
|
+
}
|
|
28
|
+
return textCache;
|
|
29
|
+
},
|
|
30
|
+
formData: async () => {
|
|
31
|
+
if (!formDataCache) {
|
|
32
|
+
formDataCache = request.formData();
|
|
33
|
+
}
|
|
34
|
+
return formDataCache;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Merge user context with route context
|
|
40
|
+
*/
|
|
41
|
+
export function mergeContexts(userContext, routeContext) {
|
|
42
|
+
return {
|
|
43
|
+
...userContext,
|
|
44
|
+
...routeContext,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAgB,EAChB,MAA8B;IAE9B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC;IAE/B,oDAAoD;IACpD,IAAI,SAAS,GAAwB,IAAI,CAAC;IAC1C,IAAI,SAAS,GAA2B,IAAI,CAAC;IAC7C,IAAI,aAAa,GAA6B,IAAI,CAAC;IAEnD,OAAO;QACL,MAAM;QACN,KAAK;QACL,GAAG;QAEH,IAAI,EAAE,KAAK,IAAyB,EAAE;YACpC,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,SAAS,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;YAC7B,CAAC;YACD,OAAO,SAAuB,CAAC;QACjC,CAAC;QAED,IAAI,EAAE,KAAK,IAAqB,EAAE;YAChC,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,SAAS,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;YAC7B,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,QAAQ,EAAE,KAAK,IAAuB,EAAE;YACtC,IAAI,CAAC,aAAa,EAAE,CAAC;gBACnB,aAAa,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrC,CAAC;YACD,OAAO,aAAa,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAC3B,WAAqB,EACrB,YAA0B;IAE1B,OAAO;QACL,GAAG,WAAW;QACd,GAAG,YAAY;KAChB,CAAC;AACJ,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error classes for the HTTP abstraction layer
|
|
3
|
+
*/
|
|
4
|
+
export declare class RouterError extends Error {
|
|
5
|
+
statusCode: number;
|
|
6
|
+
code?: string | undefined;
|
|
7
|
+
constructor(message: string, statusCode?: number, code?: string | undefined);
|
|
8
|
+
toResponse(): Response;
|
|
9
|
+
}
|
|
10
|
+
export declare class BadRequestError extends RouterError {
|
|
11
|
+
constructor(message?: string, code?: string);
|
|
12
|
+
}
|
|
13
|
+
export declare class UnauthorizedError extends RouterError {
|
|
14
|
+
constructor(message?: string, code?: string);
|
|
15
|
+
}
|
|
16
|
+
export declare class ForbiddenError extends RouterError {
|
|
17
|
+
constructor(message?: string, code?: string);
|
|
18
|
+
}
|
|
19
|
+
export declare class NotFoundError extends RouterError {
|
|
20
|
+
constructor(message?: string, code?: string);
|
|
21
|
+
}
|
|
22
|
+
export declare class MethodNotAllowedError extends RouterError {
|
|
23
|
+
constructor(message?: string, code?: string);
|
|
24
|
+
}
|
|
25
|
+
export declare class ConflictError extends RouterError {
|
|
26
|
+
constructor(message?: string, code?: string);
|
|
27
|
+
}
|
|
28
|
+
export declare class UnprocessableEntityError extends RouterError {
|
|
29
|
+
constructor(message?: string, code?: string);
|
|
30
|
+
}
|
|
31
|
+
export declare class TooManyRequestsError extends RouterError {
|
|
32
|
+
constructor(message?: string, code?: string);
|
|
33
|
+
}
|
|
34
|
+
export declare class InternalServerError extends RouterError {
|
|
35
|
+
constructor(message?: string, code?: string);
|
|
36
|
+
}
|
|
37
|
+
export declare class NotImplementedError extends RouterError {
|
|
38
|
+
constructor(message?: string, code?: string);
|
|
39
|
+
}
|
|
40
|
+
export declare class ServiceUnavailableError extends RouterError {
|
|
41
|
+
constructor(message?: string, code?: string);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,qBAAa,WAAY,SAAQ,KAAK;IAG3B,UAAU,EAAE,MAAM;IAClB,IAAI,CAAC,EAAE,MAAM;gBAFpB,OAAO,EAAE,MAAM,EACR,UAAU,GAAE,MAAY,EACxB,IAAI,CAAC,EAAE,MAAM,YAAA;IAOtB,UAAU,IAAI,QAAQ;CASvB;AAED,qBAAa,eAAgB,SAAQ,WAAW;gBAClC,OAAO,GAAE,MAAsB,EAAE,IAAI,CAAC,EAAE,MAAM;CAI3D;AAED,qBAAa,iBAAkB,SAAQ,WAAW;gBACpC,OAAO,GAAE,MAAuB,EAAE,IAAI,CAAC,EAAE,MAAM;CAI5D;AAED,qBAAa,cAAe,SAAQ,WAAW;gBACjC,OAAO,GAAE,MAAoB,EAAE,IAAI,CAAC,EAAE,MAAM;CAIzD;AAED,qBAAa,aAAc,SAAQ,WAAW;gBAChC,OAAO,GAAE,MAAoB,EAAE,IAAI,CAAC,EAAE,MAAM;CAIzD;AAED,qBAAa,qBAAsB,SAAQ,WAAW;gBACxC,OAAO,GAAE,MAA6B,EAAE,IAAI,CAAC,EAAE,MAAM;CAIlE;AAED,qBAAa,aAAc,SAAQ,WAAW;gBAChC,OAAO,GAAE,MAAmB,EAAE,IAAI,CAAC,EAAE,MAAM;CAIxD;AAED,qBAAa,wBAAyB,SAAQ,WAAW;gBAC3C,OAAO,GAAE,MAA+B,EAAE,IAAI,CAAC,EAAE,MAAM;CAIpE;AAED,qBAAa,oBAAqB,SAAQ,WAAW;gBACvC,OAAO,GAAE,MAA4B,EAAE,IAAI,CAAC,EAAE,MAAM;CAIjE;AAED,qBAAa,mBAAoB,SAAQ,WAAW;gBACtC,OAAO,GAAE,MAAgC,EAAE,IAAI,CAAC,EAAE,MAAM;CAIrE;AAED,qBAAa,mBAAoB,SAAQ,WAAW;gBACtC,OAAO,GAAE,MAA0B,EAAE,IAAI,CAAC,EAAE,MAAM;CAI/D;AAED,qBAAa,uBAAwB,SAAQ,WAAW;gBAC1C,OAAO,GAAE,MAA8B,EAAE,IAAI,CAAC,EAAE,MAAM;CAInE"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error classes for the HTTP abstraction layer
|
|
3
|
+
*/
|
|
4
|
+
export class RouterError extends Error {
|
|
5
|
+
statusCode;
|
|
6
|
+
code;
|
|
7
|
+
constructor(message, statusCode = 500, code) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.statusCode = statusCode;
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.name = 'RouterError';
|
|
12
|
+
Object.setPrototypeOf(this, RouterError.prototype);
|
|
13
|
+
}
|
|
14
|
+
toResponse() {
|
|
15
|
+
return Response.json({
|
|
16
|
+
error: this.message,
|
|
17
|
+
code: this.code,
|
|
18
|
+
}, { status: this.statusCode });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class BadRequestError extends RouterError {
|
|
22
|
+
constructor(message = 'Bad Request', code) {
|
|
23
|
+
super(message, 400, code);
|
|
24
|
+
this.name = 'BadRequestError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class UnauthorizedError extends RouterError {
|
|
28
|
+
constructor(message = 'Unauthorized', code) {
|
|
29
|
+
super(message, 401, code);
|
|
30
|
+
this.name = 'UnauthorizedError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export class ForbiddenError extends RouterError {
|
|
34
|
+
constructor(message = 'Forbidden', code) {
|
|
35
|
+
super(message, 403, code);
|
|
36
|
+
this.name = 'ForbiddenError';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export class NotFoundError extends RouterError {
|
|
40
|
+
constructor(message = 'Not Found', code) {
|
|
41
|
+
super(message, 404, code);
|
|
42
|
+
this.name = 'NotFoundError';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export class MethodNotAllowedError extends RouterError {
|
|
46
|
+
constructor(message = 'Method Not Allowed', code) {
|
|
47
|
+
super(message, 405, code);
|
|
48
|
+
this.name = 'MethodNotAllowedError';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export class ConflictError extends RouterError {
|
|
52
|
+
constructor(message = 'Conflict', code) {
|
|
53
|
+
super(message, 409, code);
|
|
54
|
+
this.name = 'ConflictError';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export class UnprocessableEntityError extends RouterError {
|
|
58
|
+
constructor(message = 'Unprocessable Entity', code) {
|
|
59
|
+
super(message, 422, code);
|
|
60
|
+
this.name = 'UnprocessableEntityError';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export class TooManyRequestsError extends RouterError {
|
|
64
|
+
constructor(message = 'Too Many Requests', code) {
|
|
65
|
+
super(message, 429, code);
|
|
66
|
+
this.name = 'TooManyRequestsError';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export class InternalServerError extends RouterError {
|
|
70
|
+
constructor(message = 'Internal Server Error', code) {
|
|
71
|
+
super(message, 500, code);
|
|
72
|
+
this.name = 'InternalServerError';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export class NotImplementedError extends RouterError {
|
|
76
|
+
constructor(message = 'Not Implemented', code) {
|
|
77
|
+
super(message, 501, code);
|
|
78
|
+
this.name = 'NotImplementedError';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export class ServiceUnavailableError extends RouterError {
|
|
82
|
+
constructor(message = 'Service Unavailable', code) {
|
|
83
|
+
super(message, 503, code);
|
|
84
|
+
this.name = 'ServiceUnavailableError';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @superfunctions/http - Framework-agnostic HTTP abstraction layer
|
|
3
|
+
*/
|
|
4
|
+
export { createRouter } from './router.js';
|
|
5
|
+
export type { Router, RouterOptions, Route, RouteHandler, RouteContext, Middleware, MatchedRoute, HttpMethod, CorsOptions, } from './types.js';
|
|
6
|
+
export { RouterError, BadRequestError, UnauthorizedError, ForbiddenError, NotFoundError, MethodNotAllowedError, ConflictError, UnprocessableEntityError, TooManyRequestsError, InternalServerError, NotImplementedError, ServiceUnavailableError, } from './errors.js';
|
|
7
|
+
export { compilePattern, matchPath, normalizePath, joinPaths, } from './path-matcher.js';
|
|
8
|
+
export { createRouteContext, mergeContexts, } from './context.js';
|
|
9
|
+
export { executeMiddlewareChain, combineMiddleware, } from './middleware.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,YAAY,EACV,MAAM,EACN,aAAa,EACb,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,UAAU,EACV,WAAW,GACZ,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,qBAAqB,EACrB,aAAa,EACb,wBAAwB,EACxB,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,cAAc,EACd,SAAS,EACT,aAAa,EACb,SAAS,GACV,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,kBAAkB,EAClB,aAAa,GACd,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @superfunctions/http - Framework-agnostic HTTP abstraction layer
|
|
3
|
+
*/
|
|
4
|
+
// Core router
|
|
5
|
+
export { createRouter } from './router.js';
|
|
6
|
+
// Errors
|
|
7
|
+
export { RouterError, BadRequestError, UnauthorizedError, ForbiddenError, NotFoundError, MethodNotAllowedError, ConflictError, UnprocessableEntityError, TooManyRequestsError, InternalServerError, NotImplementedError, ServiceUnavailableError, } from './errors.js';
|
|
8
|
+
// Utilities (exported for advanced use cases)
|
|
9
|
+
export { compilePattern, matchPath, normalizePath, joinPaths, } from './path-matcher.js';
|
|
10
|
+
export { createRouteContext, mergeContexts, } from './context.js';
|
|
11
|
+
export { executeMiddlewareChain, combineMiddleware, } from './middleware.js';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc;AACd,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAe3C,SAAS;AACT,OAAO,EACL,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,qBAAqB,EACrB,aAAa,EACb,wBAAwB,EACxB,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAErB,8CAA8C;AAC9C,OAAO,EACL,cAAc,EACd,SAAS,EACT,aAAa,EACb,SAAS,GACV,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,kBAAkB,EAClB,aAAa,GACd,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CORS middleware
|
|
3
|
+
*/
|
|
4
|
+
import type { Middleware, CorsOptions } from '../types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Create CORS middleware with configurable options
|
|
7
|
+
*/
|
|
8
|
+
export declare function corsMiddleware<TContext = any>(options?: CorsOptions): Middleware<TContext>;
|
|
9
|
+
//# sourceMappingURL=cors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../../src/middleware/cors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE3D;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,GAAG,GAAG,EAC3C,OAAO,GAAE,WAAgB,GACxB,UAAU,CAAC,QAAQ,CAAC,CA8EtB"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CORS middleware
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Create CORS middleware with configurable options
|
|
6
|
+
*/
|
|
7
|
+
export function corsMiddleware(options = {}) {
|
|
8
|
+
const { origin = '*', methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'], allowedHeaders = ['Content-Type', 'Authorization'], exposedHeaders = [], credentials = false, maxAge = 86400, // 24 hours
|
|
9
|
+
} = options;
|
|
10
|
+
return async (request, _context, next) => {
|
|
11
|
+
const requestOrigin = request.headers.get('Origin') || '';
|
|
12
|
+
// Determine if origin is allowed
|
|
13
|
+
let allowedOrigin = null;
|
|
14
|
+
if (typeof origin === 'string') {
|
|
15
|
+
allowedOrigin = origin;
|
|
16
|
+
}
|
|
17
|
+
else if (Array.isArray(origin)) {
|
|
18
|
+
if (origin.includes(requestOrigin)) {
|
|
19
|
+
allowedOrigin = requestOrigin;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else if (typeof origin === 'function') {
|
|
23
|
+
if (origin(requestOrigin)) {
|
|
24
|
+
allowedOrigin = requestOrigin;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Handle preflight request
|
|
28
|
+
if (request.method === 'OPTIONS') {
|
|
29
|
+
const headers = new Headers();
|
|
30
|
+
if (allowedOrigin) {
|
|
31
|
+
headers.set('Access-Control-Allow-Origin', allowedOrigin);
|
|
32
|
+
}
|
|
33
|
+
headers.set('Access-Control-Allow-Methods', methods.join(', '));
|
|
34
|
+
headers.set('Access-Control-Allow-Headers', allowedHeaders.join(', '));
|
|
35
|
+
if (credentials) {
|
|
36
|
+
headers.set('Access-Control-Allow-Credentials', 'true');
|
|
37
|
+
}
|
|
38
|
+
if (maxAge) {
|
|
39
|
+
headers.set('Access-Control-Max-Age', maxAge.toString());
|
|
40
|
+
}
|
|
41
|
+
if (exposedHeaders.length > 0) {
|
|
42
|
+
headers.set('Access-Control-Expose-Headers', exposedHeaders.join(', '));
|
|
43
|
+
}
|
|
44
|
+
return new Response(null, { status: 204, headers });
|
|
45
|
+
}
|
|
46
|
+
// Handle actual request
|
|
47
|
+
const response = await next();
|
|
48
|
+
// Add CORS headers to response
|
|
49
|
+
if (allowedOrigin) {
|
|
50
|
+
const newHeaders = new Headers(response.headers);
|
|
51
|
+
newHeaders.set('Access-Control-Allow-Origin', allowedOrigin);
|
|
52
|
+
if (credentials) {
|
|
53
|
+
newHeaders.set('Access-Control-Allow-Credentials', 'true');
|
|
54
|
+
}
|
|
55
|
+
if (exposedHeaders.length > 0) {
|
|
56
|
+
newHeaders.set('Access-Control-Expose-Headers', exposedHeaders.join(', '));
|
|
57
|
+
}
|
|
58
|
+
return new Response(response.body, {
|
|
59
|
+
status: response.status,
|
|
60
|
+
statusText: response.statusText,
|
|
61
|
+
headers: newHeaders,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return response;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=cors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cors.js","sourceRoot":"","sources":["../../src/middleware/cors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware chain execution
|
|
3
|
+
*/
|
|
4
|
+
import type { Middleware, RouteContext } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Execute a chain of middleware functions
|
|
7
|
+
* Middleware can:
|
|
8
|
+
* - Call next() to continue to the next middleware/handler
|
|
9
|
+
* - Return a Response to short-circuit the chain
|
|
10
|
+
* - Throw an error to trigger error handling
|
|
11
|
+
*/
|
|
12
|
+
export declare function executeMiddlewareChain<TContext>(middlewares: Middleware<TContext>[], request: Request, context: TContext & RouteContext, finalHandler: () => Promise<Response>): Promise<Response>;
|
|
13
|
+
/**
|
|
14
|
+
* Combine multiple middleware arrays into one
|
|
15
|
+
*/
|
|
16
|
+
export declare function combineMiddleware<TContext>(...middlewareArrays: (Middleware<TContext>[] | undefined)[]): Middleware<TContext>[];
|
|
17
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE3D;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAAC,QAAQ,EACnD,WAAW,EAAE,UAAU,CAAC,QAAQ,CAAC,EAAE,EACnC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,QAAQ,GAAG,YAAY,EAChC,YAAY,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,GACpC,OAAO,CAAC,QAAQ,CAAC,CAiBnB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EACxC,GAAG,gBAAgB,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,GAAG,SAAS,CAAC,EAAE,GAC1D,UAAU,CAAC,QAAQ,CAAC,EAAE,CAExB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware chain execution
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Execute a chain of middleware functions
|
|
6
|
+
* Middleware can:
|
|
7
|
+
* - Call next() to continue to the next middleware/handler
|
|
8
|
+
* - Return a Response to short-circuit the chain
|
|
9
|
+
* - Throw an error to trigger error handling
|
|
10
|
+
*/
|
|
11
|
+
export async function executeMiddlewareChain(middlewares, request, context, finalHandler) {
|
|
12
|
+
let index = 0;
|
|
13
|
+
const next = async () => {
|
|
14
|
+
// If we've reached the end of middleware, call the final handler
|
|
15
|
+
if (index >= middlewares.length) {
|
|
16
|
+
return finalHandler();
|
|
17
|
+
}
|
|
18
|
+
// Get current middleware and increment index
|
|
19
|
+
const middleware = middlewares[index++];
|
|
20
|
+
// Execute middleware
|
|
21
|
+
return middleware(request, context, next);
|
|
22
|
+
};
|
|
23
|
+
return next();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Combine multiple middleware arrays into one
|
|
27
|
+
*/
|
|
28
|
+
export function combineMiddleware(...middlewareArrays) {
|
|
29
|
+
return middlewareArrays.filter((arr) => !!arr).flat();
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,WAAmC,EACnC,OAAgB,EAChB,OAAgC,EAChC,YAAqC;IAErC,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,MAAM,IAAI,GAAG,KAAK,IAAuB,EAAE;QACzC,iEAAiE;QACjE,IAAI,KAAK,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC;YAChC,OAAO,YAAY,EAAE,CAAC;QACxB,CAAC;QAED,6CAA6C;QAC7C,MAAM,UAAU,GAAG,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC;QAExC,qBAAqB;QACrB,OAAO,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAC5C,CAAC,CAAC;IAEF,OAAO,IAAI,EAAE,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,GAAG,gBAAwD;IAE3D,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC,GAAG,EAAiC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACvF,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path matching and parameter extraction for routes
|
|
3
|
+
*/
|
|
4
|
+
export interface PathMatch {
|
|
5
|
+
matched: boolean;
|
|
6
|
+
params: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
export interface CompiledPattern {
|
|
9
|
+
pattern: RegExp;
|
|
10
|
+
keys: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Convert Express-style path pattern to regex with named capture groups
|
|
14
|
+
* Examples:
|
|
15
|
+
* - /users/:id -> /users/(?<id>[^/]+)
|
|
16
|
+
* - /posts/:id/comments/:commentId -> /posts/(?<id>[^/]+)/comments/(?<commentId>[^/]+)
|
|
17
|
+
*/
|
|
18
|
+
export declare function compilePattern(pattern: string): CompiledPattern;
|
|
19
|
+
/**
|
|
20
|
+
* Match a path against a compiled pattern and extract parameters
|
|
21
|
+
*/
|
|
22
|
+
export declare function matchPath(compiledPattern: CompiledPattern, path: string): PathMatch;
|
|
23
|
+
/**
|
|
24
|
+
* Normalize path by removing trailing slashes and ensuring leading slash
|
|
25
|
+
*/
|
|
26
|
+
export declare function normalizePath(path: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Join base path with route path
|
|
29
|
+
*/
|
|
30
|
+
export declare function joinPaths(basePath: string, routePath: string): string;
|
|
31
|
+
//# sourceMappingURL=path-matcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-matcher.d.ts","sourceRoot":"","sources":["../src/path-matcher.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAgB/D;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,eAAe,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,CAiBnF;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYlD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CASrE"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path matching and parameter extraction for routes
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Convert Express-style path pattern to regex with named capture groups
|
|
6
|
+
* Examples:
|
|
7
|
+
* - /users/:id -> /users/(?<id>[^/]+)
|
|
8
|
+
* - /posts/:id/comments/:commentId -> /posts/(?<id>[^/]+)/comments/(?<commentId>[^/]+)
|
|
9
|
+
*/
|
|
10
|
+
export function compilePattern(pattern) {
|
|
11
|
+
const keys = [];
|
|
12
|
+
// Escape special regex characters except :
|
|
13
|
+
let regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
14
|
+
// Replace :param with named capture groups
|
|
15
|
+
regexPattern = regexPattern.replace(/:(\w+)/g, (_, key) => {
|
|
16
|
+
keys.push(key);
|
|
17
|
+
return `(?<${key}>[^/]+)`;
|
|
18
|
+
});
|
|
19
|
+
// Add start and end anchors
|
|
20
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
21
|
+
return { pattern: regex, keys };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Match a path against a compiled pattern and extract parameters
|
|
25
|
+
*/
|
|
26
|
+
export function matchPath(compiledPattern, path) {
|
|
27
|
+
const match = path.match(compiledPattern.pattern);
|
|
28
|
+
if (!match) {
|
|
29
|
+
return { matched: false, params: {} };
|
|
30
|
+
}
|
|
31
|
+
const params = {};
|
|
32
|
+
// Extract named groups
|
|
33
|
+
if (match.groups) {
|
|
34
|
+
for (const key of compiledPattern.keys) {
|
|
35
|
+
params[key] = decodeURIComponent(match.groups[key]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { matched: true, params };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Normalize path by removing trailing slashes and ensuring leading slash
|
|
42
|
+
*/
|
|
43
|
+
export function normalizePath(path) {
|
|
44
|
+
// Remove trailing slash (except for root path)
|
|
45
|
+
if (path.length > 1 && path.endsWith('/')) {
|
|
46
|
+
path = path.slice(0, -1);
|
|
47
|
+
}
|
|
48
|
+
// Ensure leading slash
|
|
49
|
+
if (!path.startsWith('/')) {
|
|
50
|
+
path = '/' + path;
|
|
51
|
+
}
|
|
52
|
+
return path;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Join base path with route path
|
|
56
|
+
*/
|
|
57
|
+
export function joinPaths(basePath, routePath) {
|
|
58
|
+
const normalizedBase = normalizePath(basePath);
|
|
59
|
+
const normalizedRoute = normalizePath(routePath);
|
|
60
|
+
if (normalizedBase === '/') {
|
|
61
|
+
return normalizedRoute;
|
|
62
|
+
}
|
|
63
|
+
return normalizedBase + normalizedRoute;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=path-matcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-matcher.js","sourceRoot":"","sources":["../src/path-matcher.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,MAAM,IAAI,GAAa,EAAE,CAAC;IAE1B,2CAA2C;IAC3C,IAAI,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC;IAEjE,2CAA2C;IAC3C,YAAY,GAAG,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE;QACxD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,OAAO,MAAM,GAAG,SAAS,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,4BAA4B;IAC5B,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,IAAI,YAAY,GAAG,CAAC,CAAC;IAE9C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAClC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,eAAgC,EAAE,IAAY;IACtE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IAElD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACxC,CAAC;IAED,MAAM,MAAM,GAA2B,EAAE,CAAC;IAE1C,uBAAuB;IACvB,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,KAAK,MAAM,GAAG,IAAI,eAAe,CAAC,IAAI,EAAE,CAAC;YACvC,MAAM,CAAC,GAAG,CAAC,GAAG,kBAAkB,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,+CAA+C;IAC/C,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1C,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC;IAED,uBAAuB;IACvB,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC;IACpB,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,SAAiB;IAC3D,MAAM,cAAc,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC/C,MAAM,eAAe,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IAEjD,IAAI,cAAc,KAAK,GAAG,EAAE,CAAC;QAC3B,OAAO,eAAe,CAAC;IACzB,CAAC;IAED,OAAO,cAAc,GAAG,eAAe,CAAC;AAC1C,CAAC"}
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core router implementation
|
|
3
|
+
*/
|
|
4
|
+
import type { Router, RouterOptions } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Create a framework-agnostic HTTP router
|
|
7
|
+
*/
|
|
8
|
+
export declare function createRouter<TContext = any>(options: RouterOptions<TContext>): Router<TContext>;
|
|
9
|
+
//# sourceMappingURL=router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,MAAM,EACN,aAAa,EAId,MAAM,YAAY,CAAC;AAYpB;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,GAAG,GAAG,EACzC,OAAO,EAAE,aAAa,CAAC,QAAQ,CAAC,GAC/B,MAAM,CAAC,QAAQ,CAAC,CA6JlB"}
|
package/dist/router.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core router implementation
|
|
3
|
+
*/
|
|
4
|
+
import { compilePattern, matchPath, normalizePath, joinPaths } from './path-matcher.js';
|
|
5
|
+
import { createRouteContext, mergeContexts } from './context.js';
|
|
6
|
+
import { executeMiddlewareChain, combineMiddleware } from './middleware.js';
|
|
7
|
+
import { RouterError, NotFoundError } from './errors.js';
|
|
8
|
+
/**
|
|
9
|
+
* Create a framework-agnostic HTTP router
|
|
10
|
+
*/
|
|
11
|
+
export function createRouter(options) {
|
|
12
|
+
const { routes, middleware: globalMiddleware = [], context: contextFactory, onError, basePath = '/', } = options;
|
|
13
|
+
// Pre-compile all route patterns
|
|
14
|
+
const compiledRoutes = routes.map((route) => {
|
|
15
|
+
const fullPath = joinPaths(basePath, route.path);
|
|
16
|
+
return {
|
|
17
|
+
route,
|
|
18
|
+
compiledPattern: compilePattern(fullPath),
|
|
19
|
+
fullPath,
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Match a route by method and path
|
|
24
|
+
*/
|
|
25
|
+
function match(method, path) {
|
|
26
|
+
const normalizedPath = normalizePath(path);
|
|
27
|
+
for (const entry of compiledRoutes) {
|
|
28
|
+
if (entry.route.method !== method) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const pathMatch = matchPath(entry.compiledPattern, normalizedPath);
|
|
32
|
+
if (pathMatch.matched) {
|
|
33
|
+
return {
|
|
34
|
+
route: entry.route,
|
|
35
|
+
params: pathMatch.params,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Handle an incoming request
|
|
43
|
+
*/
|
|
44
|
+
async function handle(request) {
|
|
45
|
+
try {
|
|
46
|
+
const url = new URL(request.url);
|
|
47
|
+
const path = url.pathname;
|
|
48
|
+
const method = request.method;
|
|
49
|
+
// Try to match a route
|
|
50
|
+
const matched = match(method, path);
|
|
51
|
+
if (!matched) {
|
|
52
|
+
throw new NotFoundError(`Route not found: ${method} ${path}`);
|
|
53
|
+
}
|
|
54
|
+
// Create route context
|
|
55
|
+
const routeContext = createRouteContext(request, matched.params);
|
|
56
|
+
// Create user context
|
|
57
|
+
let userContext;
|
|
58
|
+
if (contextFactory !== undefined) {
|
|
59
|
+
if (typeof contextFactory === 'function') {
|
|
60
|
+
userContext = await contextFactory(request);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
userContext = contextFactory;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
userContext = {};
|
|
68
|
+
}
|
|
69
|
+
// Merge contexts
|
|
70
|
+
const fullContext = mergeContexts(userContext, routeContext);
|
|
71
|
+
// Combine global and route-specific middleware
|
|
72
|
+
const allMiddleware = combineMiddleware(globalMiddleware, matched.route.middleware);
|
|
73
|
+
// Execute middleware chain + handler
|
|
74
|
+
const response = await executeMiddlewareChain(allMiddleware, request, fullContext, async () => matched.route.handler(request, fullContext));
|
|
75
|
+
return response;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
// Handle errors
|
|
79
|
+
if (onError) {
|
|
80
|
+
try {
|
|
81
|
+
return await onError(error, request);
|
|
82
|
+
}
|
|
83
|
+
catch (handlerError) {
|
|
84
|
+
// If error handler itself fails, return 500
|
|
85
|
+
return Response.json({ error: 'Internal Server Error' }, { status: 500 });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Default error handling
|
|
89
|
+
if (error instanceof RouterError) {
|
|
90
|
+
return error.toResponse();
|
|
91
|
+
}
|
|
92
|
+
// Unknown error - don't expose details
|
|
93
|
+
console.error('Router error:', error);
|
|
94
|
+
return Response.json({ error: 'Internal Server Error' }, { status: 500 });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get all routes
|
|
99
|
+
*/
|
|
100
|
+
function getRoutes() {
|
|
101
|
+
return routes;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Add a route dynamically
|
|
105
|
+
*/
|
|
106
|
+
function addRoute(route) {
|
|
107
|
+
routes.push(route);
|
|
108
|
+
const fullPath = joinPaths(basePath, route.path);
|
|
109
|
+
compiledRoutes.push({
|
|
110
|
+
route,
|
|
111
|
+
compiledPattern: compilePattern(fullPath),
|
|
112
|
+
fullPath,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Add middleware dynamically
|
|
117
|
+
*/
|
|
118
|
+
function use(middleware) {
|
|
119
|
+
globalMiddleware.push(middleware);
|
|
120
|
+
}
|
|
121
|
+
// Create router object
|
|
122
|
+
const router = {
|
|
123
|
+
handle,
|
|
124
|
+
getRoutes,
|
|
125
|
+
addRoute,
|
|
126
|
+
use,
|
|
127
|
+
match,
|
|
128
|
+
handler: handle, // Alias for convenience
|
|
129
|
+
};
|
|
130
|
+
return router;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for the HTTP abstraction layer
|
|
3
|
+
*/
|
|
4
|
+
export interface RouterOptions<TContext = any> {
|
|
5
|
+
/** Route definitions */
|
|
6
|
+
routes: Route<TContext>[];
|
|
7
|
+
/** Global middleware (runs before all routes) */
|
|
8
|
+
middleware?: Middleware<TContext>[];
|
|
9
|
+
/** Context factory (creates context per request) */
|
|
10
|
+
context?: TContext | ((request: Request) => Promise<TContext> | TContext);
|
|
11
|
+
/** Error handler */
|
|
12
|
+
onError?: (error: Error, request: Request) => Response | Promise<Response>;
|
|
13
|
+
/** Base path for all routes */
|
|
14
|
+
basePath?: string;
|
|
15
|
+
/** CORS configuration */
|
|
16
|
+
cors?: CorsOptions | false;
|
|
17
|
+
}
|
|
18
|
+
export interface Route<TContext = any> {
|
|
19
|
+
/** HTTP method */
|
|
20
|
+
method: HttpMethod;
|
|
21
|
+
/** Route path (supports params: /users/:id) */
|
|
22
|
+
path: string;
|
|
23
|
+
/** Route handler */
|
|
24
|
+
handler: RouteHandler<TContext>;
|
|
25
|
+
/** Route-specific middleware */
|
|
26
|
+
middleware?: Middleware<TContext>[];
|
|
27
|
+
/** Metadata (for docs, validation, etc.) */
|
|
28
|
+
meta?: Record<string, any>;
|
|
29
|
+
}
|
|
30
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';
|
|
31
|
+
export type RouteHandler<TContext = any> = (request: Request, context: TContext & RouteContext) => Promise<Response> | Response;
|
|
32
|
+
export interface RouteContext {
|
|
33
|
+
/** Parsed path params */
|
|
34
|
+
params: Record<string, string>;
|
|
35
|
+
/** Query string parsed as URLSearchParams */
|
|
36
|
+
query: URLSearchParams;
|
|
37
|
+
/** Request URL */
|
|
38
|
+
url: URL;
|
|
39
|
+
/** Convenience: parse JSON body */
|
|
40
|
+
json: <T = any>() => Promise<T>;
|
|
41
|
+
/** Convenience: parse form data */
|
|
42
|
+
formData: () => Promise<FormData>;
|
|
43
|
+
/** Convenience: get text body */
|
|
44
|
+
text: () => Promise<string>;
|
|
45
|
+
}
|
|
46
|
+
export type Middleware<TContext = any> = (request: Request, context: TContext & RouteContext, next: () => Promise<Response>) => Promise<Response> | Response;
|
|
47
|
+
export interface Router<TContext = any> {
|
|
48
|
+
/** Handle a Web Standard Request */
|
|
49
|
+
handle(request: Request): Promise<Response>;
|
|
50
|
+
/** Get all routes */
|
|
51
|
+
getRoutes(): Route<TContext>[];
|
|
52
|
+
/** Add route dynamically */
|
|
53
|
+
addRoute(route: Route<TContext>): void;
|
|
54
|
+
/** Add middleware dynamically */
|
|
55
|
+
use(middleware: Middleware<TContext>): void;
|
|
56
|
+
/** Match route by method and path */
|
|
57
|
+
match(method: string, path: string): MatchedRoute<TContext> | null;
|
|
58
|
+
/**
|
|
59
|
+
* Universal handler - can be used directly in Fetch-native frameworks
|
|
60
|
+
* This is an alias to router.handle for convenience
|
|
61
|
+
*/
|
|
62
|
+
handler: (request: Request) => Promise<Response>;
|
|
63
|
+
}
|
|
64
|
+
export interface MatchedRoute<TContext = any> {
|
|
65
|
+
route: Route<TContext>;
|
|
66
|
+
params: Record<string, string>;
|
|
67
|
+
}
|
|
68
|
+
export interface CorsOptions {
|
|
69
|
+
/** Allowed origins (string, array, or function) */
|
|
70
|
+
origin?: string | string[] | ((origin: string) => boolean);
|
|
71
|
+
/** Allowed methods */
|
|
72
|
+
methods?: HttpMethod[];
|
|
73
|
+
/** Allowed headers */
|
|
74
|
+
allowedHeaders?: string[];
|
|
75
|
+
/** Exposed headers */
|
|
76
|
+
exposedHeaders?: string[];
|
|
77
|
+
/** Allow credentials */
|
|
78
|
+
credentials?: boolean;
|
|
79
|
+
/** Max age for preflight cache */
|
|
80
|
+
maxAge?: number;
|
|
81
|
+
}
|
|
82
|
+
export interface CompiledRoute<TContext = any> {
|
|
83
|
+
route: Route<TContext>;
|
|
84
|
+
pattern: RegExp;
|
|
85
|
+
keys: string[];
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@superfunctions/http",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Framework-agnostic HTTP abstraction layer for Superfunctions libraries",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./middleware": {
|
|
14
|
+
"types": "./dist/middleware/index.d.ts",
|
|
15
|
+
"default": "./dist/middleware/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./types": {
|
|
18
|
+
"types": "./dist/types.d.ts"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"test": "vitest",
|
|
27
|
+
"test:watch": "vitest --watch",
|
|
28
|
+
"lint": "echo 'lint not configured'",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"clean": "rm -rf dist"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"http",
|
|
34
|
+
"router",
|
|
35
|
+
"framework-agnostic",
|
|
36
|
+
"web-standard",
|
|
37
|
+
"express",
|
|
38
|
+
"hono",
|
|
39
|
+
"fastify",
|
|
40
|
+
"superfunctions"
|
|
41
|
+
],
|
|
42
|
+
"author": "21n",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/21nCo/super-functions/issues"
|
|
46
|
+
},
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/21nCo/super-functions.git",
|
|
50
|
+
"directory": "packages/http"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^22.0.0",
|
|
57
|
+
"typescript": "^5.6.0",
|
|
58
|
+
"vitest": "^3.2.4"
|
|
59
|
+
}
|
|
60
|
+
}
|