@superfunctions/http-express 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 ADDED
@@ -0,0 +1,195 @@
1
+ # @superfunctions/http-express
2
+
3
+ Express adapter for [@superfunctions/http](../http) - Use framework-agnostic routers with Express.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @superfunctions/http @superfunctions/http-express express
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import express from 'express';
15
+ import { createRouter } from '@superfunctions/http';
16
+ import { toExpressRouter } from '@superfunctions/http-express';
17
+
18
+ // Define your router (framework-agnostic)
19
+ const apiRouter = createRouter({
20
+ routes: [
21
+ {
22
+ method: 'GET',
23
+ path: '/users/:id',
24
+ handler: async (req, ctx) => {
25
+ return Response.json({ id: ctx.params.id });
26
+ },
27
+ },
28
+ ],
29
+ });
30
+
31
+ // Use with Express
32
+ const app = express();
33
+ app.use(express.json()); // Important: Add body parser
34
+ app.use('/api', toExpressRouter(apiRouter));
35
+ app.listen(3000);
36
+ ```
37
+
38
+ ## API
39
+
40
+ ### `toExpressRouter(router)`
41
+
42
+ Converts a `@superfunctions/http` router to an Express Router.
43
+
44
+ **Parameters:**
45
+ - `router`: Router instance from `@superfunctions/http`
46
+
47
+ **Returns:** `express.Router`
48
+
49
+ **Example:**
50
+ ```typescript
51
+ import { toExpressRouter } from '@superfunctions/http-express';
52
+
53
+ const expressRouter = toExpressRouter(myRouter);
54
+ app.use('/api', expressRouter);
55
+ ```
56
+
57
+ ### `toExpressHandler(router)`
58
+
59
+ Converts a `@superfunctions/http` router to an Express RequestHandler (single middleware function).
60
+
61
+ **Parameters:**
62
+ - `router`: Router instance from `@superfunctions/http`
63
+
64
+ **Returns:** `express.RequestHandler`
65
+
66
+ **Example:**
67
+ ```typescript
68
+ import { toExpressHandler } from '@superfunctions/http-express';
69
+
70
+ const handler = toExpressHandler(myRouter);
71
+ app.use('/api', handler);
72
+ ```
73
+
74
+ ## Usage Examples
75
+
76
+ ### With Middleware
77
+
78
+ ```typescript
79
+ import { createRouter } from '@superfunctions/http';
80
+ import { toExpressRouter } from '@superfunctions/http-express';
81
+
82
+ const router = createRouter({
83
+ middleware: [
84
+ async (req, ctx, next) => {
85
+ // Auth middleware
86
+ const token = req.headers.get('Authorization');
87
+ if (!token) {
88
+ return Response.json({ error: 'Unauthorized' }, { status: 401 });
89
+ }
90
+ return next();
91
+ },
92
+ ],
93
+ routes: [
94
+ {
95
+ method: 'GET',
96
+ path: '/protected',
97
+ handler: async () => Response.json({ data: 'secret' }),
98
+ },
99
+ ],
100
+ });
101
+
102
+ app.use('/api', toExpressRouter(router));
103
+ ```
104
+
105
+ ### With Custom Context
106
+
107
+ ```typescript
108
+ interface AppContext {
109
+ db: Database;
110
+ }
111
+
112
+ const router = createRouter<AppContext>({
113
+ context: { db: myDatabase },
114
+ routes: [
115
+ {
116
+ method: 'GET',
117
+ path: '/users',
118
+ handler: async (req, ctx) => {
119
+ const users = await ctx.db.findMany({ model: 'users' });
120
+ return Response.json(users);
121
+ },
122
+ },
123
+ ],
124
+ });
125
+
126
+ app.use('/api', toExpressRouter(router));
127
+ ```
128
+
129
+ ### Multiple Routers
130
+
131
+ ```typescript
132
+ const usersRouter = createRouter({ routes: [/* user routes */] });
133
+ const postsRouter = createRouter({ routes: [/* post routes */] });
134
+
135
+ app.use('/api/users', toExpressRouter(usersRouter));
136
+ app.use('/api/posts', toExpressRouter(postsRouter));
137
+ ```
138
+
139
+ ## Important Notes
140
+
141
+ ### Body Parsing
142
+
143
+ You must add Express body parsing middleware before using the adapter:
144
+
145
+ ```typescript
146
+ app.use(express.json()); // For JSON bodies
147
+ app.use(express.urlencoded({ extended: true })); // For form data
148
+ ```
149
+
150
+ ### Error Handling
151
+
152
+ The adapter automatically handles errors from your route handlers. You can add Express error middleware for additional handling:
153
+
154
+ ```typescript
155
+ app.use((err, req, res, next) => {
156
+ console.error('Express error:', err);
157
+ res.status(500).json({ error: 'Server error' });
158
+ });
159
+ ```
160
+
161
+ ### Route Paths
162
+
163
+ Routes are registered relative to the mount point:
164
+
165
+ ```typescript
166
+ const router = createRouter({
167
+ routes: [
168
+ { method: 'GET', path: '/hello', handler: () => Response.json({ msg: 'hi' }) }
169
+ ]
170
+ });
171
+
172
+ // Route accessible at: /api/hello
173
+ app.use('/api', toExpressRouter(router));
174
+ ```
175
+
176
+ ## TypeScript
177
+
178
+ Full TypeScript support with proper types:
179
+
180
+ ```typescript
181
+ import type { Router } from '@superfunctions/http';
182
+ import type { Express, Router as ExpressRouter } from 'express';
183
+
184
+ const myRouter: Router = createRouter({ routes: [...] });
185
+ const expressRouter: ExpressRouter = toExpressRouter(myRouter);
186
+ ```
187
+
188
+ ## Compatibility
189
+
190
+ - Express 4.x ✅
191
+ - Express 5.x ✅
192
+
193
+ ## License
194
+
195
+ MIT
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Express adapter for @superfunctions/http
3
+ */
4
+ import type { Router } from '@superfunctions/http';
5
+ import express from 'express';
6
+ /**
7
+ * Convert a @superfunctions/http Router to an Express Router
8
+ */
9
+ export declare function toExpressRouter(router: Router): express.Router;
10
+ /**
11
+ * Convert a @superfunctions/http Router to an Express RequestHandler
12
+ * This is useful for mounting at a specific path with a catch-all
13
+ */
14
+ export declare function toExpressHandler(router: Router): express.RequestHandler;
15
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CA2B9D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,cAAc,CAexB"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Express adapter for @superfunctions/http
3
+ */
4
+ import express from 'express';
5
+ /**
6
+ * Convert a @superfunctions/http Router to an Express Router
7
+ */
8
+ export function toExpressRouter(router) {
9
+ const expressRouter = express.Router();
10
+ // Get all routes from the router
11
+ const routes = router.getRoutes();
12
+ // Register each route with Express
13
+ for (const route of routes) {
14
+ const method = route.method.toLowerCase();
15
+ expressRouter[method](route.path, async (req, res, next) => {
16
+ try {
17
+ // Convert Express Request to Web Standard Request
18
+ const webRequest = await convertToWebRequest(req);
19
+ // Handle with router
20
+ const webResponse = await router.handle(webRequest);
21
+ // Convert Web Response to Express Response
22
+ await convertToExpressResponse(webResponse, res);
23
+ }
24
+ catch (error) {
25
+ next(error);
26
+ }
27
+ });
28
+ }
29
+ return expressRouter;
30
+ }
31
+ /**
32
+ * Convert a @superfunctions/http Router to an Express RequestHandler
33
+ * This is useful for mounting at a specific path with a catch-all
34
+ */
35
+ export function toExpressHandler(router) {
36
+ return async (req, res, next) => {
37
+ try {
38
+ // Convert Express Request to Web Standard Request
39
+ const webRequest = await convertToWebRequest(req);
40
+ // Handle with router
41
+ const webResponse = await router.handle(webRequest);
42
+ // Convert Web Response to Express Response
43
+ await convertToExpressResponse(webResponse, res);
44
+ }
45
+ catch (error) {
46
+ next(error);
47
+ }
48
+ };
49
+ }
50
+ /**
51
+ * Convert Express Request to Web Standard Request
52
+ */
53
+ async function convertToWebRequest(req) {
54
+ // Build full URL
55
+ // Use req.url (not originalUrl) to get path relative to router mount point
56
+ const protocol = req.protocol;
57
+ const host = req.get('host') || 'localhost';
58
+ const url = `${protocol}://${host}${req.url}`;
59
+ // Convert headers
60
+ const headers = new Headers();
61
+ for (const [key, value] of Object.entries(req.headers)) {
62
+ if (value) {
63
+ if (Array.isArray(value)) {
64
+ value.forEach((v) => headers.append(key, v));
65
+ }
66
+ else {
67
+ headers.set(key, value);
68
+ }
69
+ }
70
+ }
71
+ // Handle body
72
+ let body = null;
73
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
74
+ // If body is already parsed (e.g., by express.json() middleware)
75
+ if (req.body && Object.keys(req.body).length > 0) {
76
+ body = JSON.stringify(req.body);
77
+ // Ensure Content-Type is set
78
+ if (!headers.has('Content-Type')) {
79
+ headers.set('Content-Type', 'application/json');
80
+ }
81
+ }
82
+ }
83
+ return new Request(url, {
84
+ method: req.method,
85
+ headers,
86
+ body,
87
+ });
88
+ }
89
+ /**
90
+ * Convert Web Standard Response to Express Response
91
+ */
92
+ async function convertToExpressResponse(webResponse, expressResponse) {
93
+ // Set status
94
+ expressResponse.status(webResponse.status);
95
+ // Set status text if available
96
+ if (webResponse.statusText) {
97
+ expressResponse.statusMessage = webResponse.statusText;
98
+ }
99
+ // Set headers
100
+ webResponse.headers.forEach((value, key) => {
101
+ expressResponse.setHeader(key, value);
102
+ });
103
+ // Send body
104
+ if (webResponse.body) {
105
+ const text = await webResponse.text();
106
+ expressResponse.send(text);
107
+ }
108
+ else {
109
+ expressResponse.end();
110
+ }
111
+ }
112
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @superfunctions/http-express - Express adapter
3
+ */
4
+ export { toExpressRouter, toExpressHandler } from './adapter.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @superfunctions/http-express - Express adapter
3
+ */
4
+ export { toExpressRouter, toExpressHandler } from './adapter.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@superfunctions/http-express",
3
+ "version": "0.1.0",
4
+ "description": "Express adapter for @superfunctions/http",
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
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "test": "vitest",
20
+ "test:watch": "vitest --watch",
21
+ "lint": "echo 'lint not configured'",
22
+ "typecheck": "tsc --noEmit",
23
+ "clean": "rm -rf dist"
24
+ },
25
+ "peerDependencies": {
26
+ "@superfunctions/http": "^0.1.0",
27
+ "express": "^4.0.0 || ^5.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "@superfunctions/http": "^0.1.0",
31
+ "@types/express": "^4.17.21",
32
+ "@types/node": "^22.0.0",
33
+ "@types/supertest": "^6.0.2",
34
+ "express": "^4.21.2",
35
+ "supertest": "^7.0.0",
36
+ "typescript": "^5.6.0",
37
+ "vitest": "^3.2.4"
38
+ },
39
+ "keywords": [
40
+ "http",
41
+ "express",
42
+ "adapter",
43
+ "router",
44
+ "superfunctions"
45
+ ],
46
+ "author": "21n",
47
+ "license": "MIT",
48
+ "bugs": {
49
+ "url": "https://github.com/21nCo/super-functions/issues"
50
+ },
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/21nCo/super-functions.git",
54
+ "directory": "packages/http-express"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ }
59
+ }