@superfunctions/http-fastify 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,244 @@
1
+ # @superfunctions/http-fastify
2
+
3
+ Fastify adapter for [@superfunctions/http](../http) - Use framework-agnostic routers with Fastify.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @superfunctions/http @superfunctions/http-fastify fastify
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import Fastify from 'fastify';
15
+ import { createRouter } from '@superfunctions/http';
16
+ import { toFastifyPlugin } from '@superfunctions/http-fastify';
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 Fastify
32
+ const fastify = Fastify();
33
+ await fastify.register(toFastifyPlugin(apiRouter), { prefix: '/api' });
34
+ await fastify.listen({ port: 3000 });
35
+ ```
36
+
37
+ ## API
38
+
39
+ ### `toFastifyPlugin(router)`
40
+
41
+ Converts a `@superfunctions/http` router to a Fastify plugin.
42
+
43
+ **Parameters:**
44
+ - `router`: Router instance from `@superfunctions/http`
45
+
46
+ **Returns:** `FastifyPluginAsync`
47
+
48
+ **Example:**
49
+ ```typescript
50
+ import { toFastifyPlugin } from '@superfunctions/http-fastify';
51
+
52
+ await fastify.register(toFastifyPlugin(myRouter), {
53
+ prefix: '/api'
54
+ });
55
+ ```
56
+
57
+
58
+ ## Usage Examples
59
+
60
+ ### With Plugin Options
61
+
62
+ ```typescript
63
+ import Fastify from 'fastify';
64
+ import { toFastifyPlugin } from '@superfunctions/http-fastify';
65
+
66
+ const fastify = Fastify();
67
+
68
+ // Register with prefix
69
+ await fastify.register(toFastifyPlugin(apiRouter), {
70
+ prefix: '/api/v1'
71
+ });
72
+
73
+ // Routes accessible at: /api/v1/*
74
+ await fastify.listen({ port: 3000 });
75
+ ```
76
+
77
+ ### With Middleware
78
+
79
+ ```typescript
80
+ import { createRouter } from '@superfunctions/http';
81
+ import { toFastifyPlugin } from '@superfunctions/http-fastify';
82
+
83
+ const router = createRouter({
84
+ middleware: [
85
+ async (req, ctx, next) => {
86
+ // Auth middleware
87
+ const token = req.headers.get('Authorization');
88
+ if (!token) {
89
+ return Response.json({ error: 'Unauthorized' }, { status: 401 });
90
+ }
91
+ return next();
92
+ },
93
+ ],
94
+ routes: [
95
+ {
96
+ method: 'GET',
97
+ path: '/protected',
98
+ handler: async () => Response.json({ data: 'secret' }),
99
+ },
100
+ ],
101
+ });
102
+
103
+ await fastify.register(toFastifyPlugin(router), { prefix: '/api' });
104
+ ```
105
+
106
+ ### With Custom Context
107
+
108
+ ```typescript
109
+ interface AppContext {
110
+ db: Database;
111
+ }
112
+
113
+ const router = createRouter<AppContext>({
114
+ context: { db: myDatabase },
115
+ routes: [
116
+ {
117
+ method: 'GET',
118
+ path: '/users',
119
+ handler: async (req, ctx) => {
120
+ const users = await ctx.db.findMany({ model: 'users' });
121
+ return Response.json(users);
122
+ },
123
+ },
124
+ ],
125
+ });
126
+
127
+ await fastify.register(toFastifyPlugin(router), { prefix: '/api' });
128
+ ```
129
+
130
+ ### Multiple Routers
131
+
132
+ ```typescript
133
+ const usersRouter = createRouter({ routes: [/* user routes */] });
134
+ const postsRouter = createRouter({ routes: [/* post routes */] });
135
+
136
+ await fastify.register(toFastifyPlugin(usersRouter), { prefix: '/api/users' });
137
+ await fastify.register(toFastifyPlugin(postsRouter), { prefix: '/api/posts' });
138
+ ```
139
+
140
+ ### With Fastify Decorators
141
+
142
+ You can combine with other Fastify plugins:
143
+
144
+ ```typescript
145
+ import Fastify from 'fastify';
146
+ import fastifyCors from '@fastify/cors';
147
+ import { toFastifyPlugin } from '@superfunctions/http-fastify';
148
+
149
+ const fastify = Fastify();
150
+
151
+ // Register CORS plugin
152
+ await fastify.register(fastifyCors, {
153
+ origin: 'https://example.com'
154
+ });
155
+
156
+ // Register your router
157
+ await fastify.register(toFastifyPlugin(apiRouter), { prefix: '/api' });
158
+
159
+ await fastify.listen({ port: 3000 });
160
+ ```
161
+
162
+ ## Important Notes
163
+
164
+ ### Plugin Registration
165
+
166
+ The adapter returns a Fastify plugin that must be registered with `await fastify.register()`:
167
+
168
+ ```typescript
169
+ // ✅ Correct - await registration
170
+ await fastify.register(toFastifyPlugin(router));
171
+
172
+ // ❌ Incorrect - missing await
173
+ fastify.register(toFastifyPlugin(router));
174
+ ```
175
+
176
+ ### Prefix Handling
177
+
178
+ The adapter automatically handles the prefix from plugin options:
179
+
180
+ ```typescript
181
+ const router = createRouter({
182
+ routes: [
183
+ { method: 'GET', path: '/hello', handler: () => Response.json({ msg: 'hi' }) }
184
+ ]
185
+ });
186
+
187
+ // Route accessible at: /api/hello
188
+ await fastify.register(toFastifyPlugin(router), { prefix: '/api' });
189
+ ```
190
+
191
+ ### JSON Serialization
192
+
193
+ Fastify automatically serializes JSON responses. The adapter handles this by:
194
+ - Parsing JSON from Web Response
195
+ - Sending parsed objects to Fastify
196
+ - Letting Fastify handle serialization
197
+
198
+ ### Body Parsing
199
+
200
+ Fastify automatically parses JSON and form data. The adapter accesses the parsed body via `request.body`.
201
+
202
+ ### Testing
203
+
204
+ Use Fastify's built-in `inject` method for testing:
205
+
206
+ ```typescript
207
+ const response = await fastify.inject({
208
+ method: 'GET',
209
+ url: '/api/hello'
210
+ });
211
+
212
+ expect(response.statusCode).toBe(200);
213
+ expect(response.json()).toEqual({ message: 'Hello' });
214
+ ```
215
+
216
+ ## TypeScript
217
+
218
+ Full TypeScript support with proper types:
219
+
220
+ ```typescript
221
+ import type { Router } from '@superfunctions/http';
222
+ import type { FastifyPluginAsync } from 'fastify';
223
+
224
+ const myRouter: Router = createRouter({ routes: [...] });
225
+ const plugin: FastifyPluginAsync = toFastifyPlugin(myRouter);
226
+
227
+ await fastify.register(plugin, { prefix: '/api' });
228
+ ```
229
+
230
+ ## Performance
231
+
232
+ The adapter is designed for minimal overhead:
233
+ - Uses catch-all route to delegate to the router
234
+ - Minimal request/response translation
235
+ - Leverages Fastify's fast JSON serialization
236
+
237
+ ## Compatibility
238
+
239
+ - Fastify 4.x ✅
240
+ - Fastify 5.x ✅
241
+
242
+ ## License
243
+
244
+ MIT
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Fastify adapter for @superfunctions/http
3
+ */
4
+ import type { Router } from '@superfunctions/http';
5
+ import type { FastifyPluginAsync } from 'fastify';
6
+ /**
7
+ * Convert a @superfunctions/http Router to a Fastify plugin
8
+ *
9
+ * Usage:
10
+ * ```typescript
11
+ * fastify.register(toFastifyPlugin(router), { prefix: '/api' });
12
+ * ```
13
+ */
14
+ export declare function toFastifyPlugin(router: Router): FastifyPluginAsync;
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,KAAK,EAAmB,kBAAkB,EAAgC,MAAM,SAAS,CAAC;AAEjG;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,CAmBlE"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Fastify adapter for @superfunctions/http
3
+ */
4
+ /**
5
+ * Convert a @superfunctions/http Router to a Fastify plugin
6
+ *
7
+ * Usage:
8
+ * ```typescript
9
+ * fastify.register(toFastifyPlugin(router), { prefix: '/api' });
10
+ * ```
11
+ */
12
+ export function toFastifyPlugin(router) {
13
+ const plugin = async (fastify, opts) => {
14
+ const prefix = opts.prefix || '';
15
+ // Catch all route that delegates to the router
16
+ fastify.all('/*', async (request, reply) => {
17
+ // Convert Fastify Request to Web Standard Request
18
+ // Strip the prefix from the URL so router can match correctly
19
+ const webRequest = await convertToWebRequest(request, prefix);
20
+ // Handle with router (router will do the routing)
21
+ const webResponse = await router.handle(webRequest);
22
+ // Convert Web Response to Fastify Reply
23
+ await convertToFastifyReply(webResponse, reply);
24
+ });
25
+ };
26
+ return plugin;
27
+ }
28
+ /**
29
+ * Convert Fastify Request to Web Standard Request
30
+ */
31
+ async function convertToWebRequest(request, prefix = '') {
32
+ // Build full URL
33
+ // Strip prefix from URL so router can match routes correctly
34
+ let path = request.url;
35
+ if (prefix && path.startsWith(prefix)) {
36
+ path = path.slice(prefix.length) || '/';
37
+ }
38
+ const protocol = request.protocol;
39
+ const host = request.hostname;
40
+ const url = `${protocol}://${host}${path}`;
41
+ // Convert headers
42
+ const headers = new Headers();
43
+ for (const [key, value] of Object.entries(request.headers)) {
44
+ if (value) {
45
+ if (Array.isArray(value)) {
46
+ value.forEach((v) => headers.append(key, v));
47
+ }
48
+ else {
49
+ headers.set(key, value);
50
+ }
51
+ }
52
+ }
53
+ // Handle body
54
+ let body = null;
55
+ if (request.method !== 'GET' && request.method !== 'HEAD') {
56
+ // Fastify parses body automatically if content-type parser is registered
57
+ if (request.body && Object.keys(request.body).length > 0) {
58
+ body = JSON.stringify(request.body);
59
+ if (!headers.has('Content-Type')) {
60
+ headers.set('Content-Type', 'application/json');
61
+ }
62
+ }
63
+ }
64
+ return new Request(url, {
65
+ method: request.method,
66
+ headers,
67
+ body,
68
+ });
69
+ }
70
+ /**
71
+ * Convert Web Standard Response to Fastify Reply
72
+ */
73
+ async function convertToFastifyReply(webResponse, reply) {
74
+ // Set status
75
+ reply.status(webResponse.status);
76
+ // Set headers
77
+ webResponse.headers.forEach((value, key) => {
78
+ reply.header(key, value);
79
+ });
80
+ // Send body
81
+ if (webResponse.body) {
82
+ const text = await webResponse.text();
83
+ // Fastify expects parsed JSON if Content-Type is application/json
84
+ const contentType = webResponse.headers.get('Content-Type');
85
+ if (contentType?.includes('application/json')) {
86
+ try {
87
+ reply.send(JSON.parse(text));
88
+ }
89
+ catch {
90
+ // If parsing fails, send as text
91
+ reply.send(text);
92
+ }
93
+ }
94
+ else {
95
+ reply.send(text);
96
+ }
97
+ }
98
+ else {
99
+ reply.send();
100
+ }
101
+ }
102
+ //# 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-fastify - Fastify adapter
3
+ */
4
+ export { toFastifyPlugin } 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,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @superfunctions/http-fastify - Fastify adapter
3
+ */
4
+ export { toFastifyPlugin } 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,MAAM,cAAc,CAAC"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@superfunctions/http-fastify",
3
+ "version": "0.1.0",
4
+ "description": "Fastify 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
+ "fastify": "^4.0.0 || ^5.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "@superfunctions/http": "^0.1.0",
31
+ "@types/node": "^22.0.0",
32
+ "fastify": "^5.2.0",
33
+ "typescript": "^5.6.0",
34
+ "vitest": "^3.2.4"
35
+ },
36
+ "keywords": [
37
+ "http",
38
+ "fastify",
39
+ "adapter",
40
+ "router",
41
+ "superfunctions"
42
+ ],
43
+ "author": "21n",
44
+ "license": "MIT",
45
+ "bugs": {
46
+ "url": "https://github.com/21nCo/super-functions/issues"
47
+ },
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/21nCo/super-functions.git",
51
+ "directory": "packages/http-fastify"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ }
56
+ }