@flink-app/generic-request-plugin 0.12.1-alpha.4 → 0.12.1-alpha.44
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/package.json +4 -4
- package/readme.md +355 -18
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flink-app/generic-request-plugin",
|
|
3
|
-
"version": "0.12.1-alpha.
|
|
3
|
+
"version": "0.12.1-alpha.44",
|
|
4
4
|
"description": "Flink plugin that makes it possible to override default request handlers and handle specific requests manually",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\"",
|
|
7
|
-
"
|
|
7
|
+
"prepare": "tsc"
|
|
8
8
|
},
|
|
9
9
|
"author": "johan@frost.se",
|
|
10
10
|
"publishConfig": {
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"node-color-log": "^5.3.1"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@flink-app/flink": "^0.12.1-alpha.
|
|
21
|
+
"@flink-app/flink": "^0.12.1-alpha.44",
|
|
22
22
|
"@types/express": "4.17.11",
|
|
23
23
|
"@types/node": "22.13.10",
|
|
24
24
|
"ts-node": "^9.1.1",
|
|
25
25
|
"typescript": "5.4.5"
|
|
26
26
|
},
|
|
27
|
-
"gitHead": "
|
|
27
|
+
"gitHead": "4243e3b3cd6d4e1ca001a61baa8436bf2bbe4113"
|
|
28
28
|
}
|
package/readme.md
CHANGED
|
@@ -1,35 +1,372 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @flink-app/generic-request-plugin
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A lightweight Flink plugin that allows you to register custom request handlers with direct access to Express request and response objects. This plugin is useful when you need to bypass Flink's standard handler system and implement custom request handling logic.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- Register custom Express route handlers
|
|
8
|
+
- Direct access to Express `req`, `res`, and Flink `app` objects
|
|
9
|
+
- Support for all HTTP methods (GET, POST, PUT, DELETE)
|
|
10
|
+
- Bypass Flink's standard validation and response handling
|
|
11
|
+
- Useful for webhooks, custom integrations, and special endpoints
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @flink-app/generic-request-plugin
|
|
11
17
|
```
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
## Usage
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
### Basic Setup
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { FlinkApp } from "@flink-app/flink";
|
|
25
|
+
import { genericRequestPlugin, HttpMethod } from "@flink-app/generic-request-plugin";
|
|
17
26
|
|
|
18
27
|
function start() {
|
|
19
28
|
new FlinkApp<AppContext>({
|
|
20
29
|
name: "My app",
|
|
21
30
|
plugins: [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
})
|
|
31
|
+
genericRequestPlugin({
|
|
32
|
+
path: "/custom-endpoint",
|
|
33
|
+
method: HttpMethod.get,
|
|
34
|
+
handler: (req, res, app) => {
|
|
35
|
+
res.setHeader("Content-Type", "text/html");
|
|
36
|
+
res.end("Hello world!");
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
31
39
|
],
|
|
32
40
|
}).start();
|
|
33
41
|
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Configuration Options
|
|
45
|
+
|
|
46
|
+
- `path` (required): The URL path for the endpoint (e.g., `/webhook`, `/custom`)
|
|
47
|
+
- `method` (required): HTTP method for the request
|
|
48
|
+
- `handler` (required): Function that handles the request
|
|
49
|
+
|
|
50
|
+
## HTTP Methods
|
|
51
|
+
|
|
52
|
+
The plugin supports the following HTTP methods via the `HttpMethod` enum:
|
|
53
|
+
|
|
54
|
+
- `HttpMethod.get` - GET requests
|
|
55
|
+
- `HttpMethod.post` - POST requests
|
|
56
|
+
- `HttpMethod.put` - PUT requests
|
|
57
|
+
- `HttpMethod.delete` - DELETE requests
|
|
58
|
+
|
|
59
|
+
## Handler Function
|
|
60
|
+
|
|
61
|
+
The handler function receives three parameters:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
handler: (req, res, app) => void
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- `req` - Express Request object
|
|
68
|
+
- `res` - Express Response object
|
|
69
|
+
- `app` - FlinkApp instance (provides access to context, repos, etc.)
|
|
70
|
+
|
|
71
|
+
## Examples
|
|
72
|
+
|
|
73
|
+
### Simple GET Endpoint
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
genericRequestPlugin({
|
|
77
|
+
path: "/hello",
|
|
78
|
+
method: HttpMethod.get,
|
|
79
|
+
handler: (req, res, app) => {
|
|
80
|
+
res.json({ message: "Hello from Flink!" });
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### POST Endpoint with JSON Body
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
genericRequestPlugin({
|
|
89
|
+
path: "/webhook",
|
|
90
|
+
method: HttpMethod.post,
|
|
91
|
+
handler: (req, res, app) => {
|
|
92
|
+
const payload = req.body;
|
|
93
|
+
console.log("Received webhook:", payload);
|
|
94
|
+
res.status(200).json({ received: true });
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Endpoint with Database Access
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
genericRequestPlugin({
|
|
103
|
+
path: "/custom-data",
|
|
104
|
+
method: HttpMethod.get,
|
|
105
|
+
handler: async (req, res, app) => {
|
|
106
|
+
try {
|
|
107
|
+
const data = await app.ctx.repos.myRepo.findAll();
|
|
108
|
+
res.json({ data });
|
|
109
|
+
} catch (error) {
|
|
110
|
+
res.status(500).json({ error: "Failed to fetch data" });
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Endpoint with Query Parameters
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
genericRequestPlugin({
|
|
120
|
+
path: "/search",
|
|
121
|
+
method: HttpMethod.get,
|
|
122
|
+
handler: (req, res, app) => {
|
|
123
|
+
const query = req.query.q;
|
|
124
|
+
const results = performSearch(query);
|
|
125
|
+
res.json({ results });
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Endpoint with Path Parameters
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
genericRequestPlugin({
|
|
134
|
+
path: "/user/:id",
|
|
135
|
+
method: HttpMethod.get,
|
|
136
|
+
handler: async (req, res, app) => {
|
|
137
|
+
const userId = req.params.id;
|
|
138
|
+
const user = await app.ctx.repos.userRepo.findById(userId);
|
|
139
|
+
|
|
140
|
+
if (!user) {
|
|
141
|
+
res.status(404).json({ error: "User not found" });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
res.json({ user });
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Custom HTML Response
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
genericRequestPlugin({
|
|
154
|
+
path: "/status",
|
|
155
|
+
method: HttpMethod.get,
|
|
156
|
+
handler: (req, res, app) => {
|
|
157
|
+
const html = `
|
|
158
|
+
<!DOCTYPE html>
|
|
159
|
+
<html>
|
|
160
|
+
<head><title>Status</title></head>
|
|
161
|
+
<body>
|
|
162
|
+
<h1>System Status: OK</h1>
|
|
163
|
+
<p>All systems operational</p>
|
|
164
|
+
</body>
|
|
165
|
+
</html>
|
|
166
|
+
`;
|
|
167
|
+
res.setHeader("Content-Type", "text/html");
|
|
168
|
+
res.end(html);
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Webhook Handler
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
genericRequestPlugin({
|
|
177
|
+
path: "/webhook/stripe",
|
|
178
|
+
method: HttpMethod.post,
|
|
179
|
+
handler: async (req, res, app) => {
|
|
180
|
+
const signature = req.headers["stripe-signature"];
|
|
181
|
+
const payload = req.body;
|
|
182
|
+
|
|
183
|
+
// Verify webhook signature
|
|
184
|
+
if (!verifyStripeSignature(payload, signature)) {
|
|
185
|
+
res.status(401).json({ error: "Invalid signature" });
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Process webhook
|
|
190
|
+
await app.ctx.repos.orderRepo.updatePaymentStatus(payload.data);
|
|
191
|
+
|
|
192
|
+
res.status(200).json({ received: true });
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### File Download Endpoint
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
genericRequestPlugin({
|
|
201
|
+
path: "/download/:fileId",
|
|
202
|
+
method: HttpMethod.get,
|
|
203
|
+
handler: async (req, res, app) => {
|
|
204
|
+
const fileId = req.params.fileId;
|
|
205
|
+
const file = await app.ctx.repos.fileRepo.findById(fileId);
|
|
206
|
+
|
|
207
|
+
if (!file) {
|
|
208
|
+
res.status(404).json({ error: "File not found" });
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
res.setHeader("Content-Type", file.mimeType);
|
|
213
|
+
res.setHeader("Content-Disposition", `attachment; filename="${file.name}"`);
|
|
214
|
+
res.end(file.buffer);
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Custom Authentication
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
genericRequestPlugin({
|
|
223
|
+
path: "/admin/action",
|
|
224
|
+
method: HttpMethod.post,
|
|
225
|
+
handler: async (req, res, app) => {
|
|
226
|
+
const apiKey = req.headers["x-api-key"];
|
|
227
|
+
|
|
228
|
+
// Custom authentication logic
|
|
229
|
+
if (apiKey !== process.env.ADMIN_API_KEY) {
|
|
230
|
+
res.status(401).json({ error: "Unauthorized" });
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Perform admin action
|
|
235
|
+
const result = await performAdminAction(req.body);
|
|
236
|
+
res.json({ success: true, result });
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Multiple Endpoints
|
|
242
|
+
|
|
243
|
+
You can register multiple generic request handlers by including multiple plugin instances:
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
new FlinkApp<AppContext>({
|
|
247
|
+
name: "My app",
|
|
248
|
+
plugins: [
|
|
249
|
+
genericRequestPlugin({
|
|
250
|
+
path: "/webhook/stripe",
|
|
251
|
+
method: HttpMethod.post,
|
|
252
|
+
handler: stripeWebhookHandler,
|
|
253
|
+
}),
|
|
254
|
+
genericRequestPlugin({
|
|
255
|
+
path: "/webhook/github",
|
|
256
|
+
method: HttpMethod.post,
|
|
257
|
+
handler: githubWebhookHandler,
|
|
258
|
+
}),
|
|
259
|
+
genericRequestPlugin({
|
|
260
|
+
path: "/status",
|
|
261
|
+
method: HttpMethod.get,
|
|
262
|
+
handler: statusHandler,
|
|
263
|
+
}),
|
|
264
|
+
],
|
|
265
|
+
}).start();
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## When to Use This Plugin
|
|
269
|
+
|
|
270
|
+
Use the generic request plugin when you need to:
|
|
271
|
+
|
|
272
|
+
- Handle webhooks from external services
|
|
273
|
+
- Implement custom authentication or authorization logic
|
|
274
|
+
- Return non-JSON responses (HTML, XML, plain text, binary files)
|
|
275
|
+
- Bypass Flink's request validation
|
|
276
|
+
- Access low-level Express request/response objects
|
|
277
|
+
- Implement server-sent events (SSE)
|
|
278
|
+
- Handle file uploads/downloads with custom logic
|
|
279
|
+
- Integrate with third-party services requiring specific response formats
|
|
280
|
+
|
|
281
|
+
## When NOT to Use This Plugin
|
|
282
|
+
|
|
283
|
+
For standard API endpoints, use Flink's built-in handler system instead:
|
|
284
|
+
|
|
285
|
+
- Standard CRUD operations - Use `FlinkHttpHandler` with GET/POST/PUT/DELETE
|
|
286
|
+
- Endpoints requiring validation - Use Flink's schema validation
|
|
287
|
+
- Type-safe endpoints - Use Flink's typed handlers
|
|
288
|
+
- Auto-documented endpoints - Use standard handlers with the API docs plugin
|
|
289
|
+
|
|
290
|
+
## Access to FlinkApp Context
|
|
291
|
+
|
|
292
|
+
The handler receives the full FlinkApp instance, giving you access to:
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
handler: (req, res, app) => {
|
|
296
|
+
// Access repositories
|
|
297
|
+
const data = await app.ctx.repos.myRepo.findAll();
|
|
298
|
+
|
|
299
|
+
// Access plugins
|
|
300
|
+
const stripeAPI = app.ctx.plugins.stripePlugin.stripeAPI;
|
|
301
|
+
|
|
302
|
+
// Access database
|
|
303
|
+
const collection = app.db.collection("myCollection");
|
|
304
|
+
|
|
305
|
+
// Access any custom context properties
|
|
306
|
+
const config = app.ctx.config;
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Error Handling
|
|
311
|
+
|
|
312
|
+
Always implement proper error handling in your custom handlers:
|
|
34
313
|
|
|
314
|
+
```typescript
|
|
315
|
+
genericRequestPlugin({
|
|
316
|
+
path: "/custom",
|
|
317
|
+
method: HttpMethod.post,
|
|
318
|
+
handler: async (req, res, app) => {
|
|
319
|
+
try {
|
|
320
|
+
// Your logic here
|
|
321
|
+
const result = await performOperation(req.body);
|
|
322
|
+
res.json({ success: true, result });
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.error("Error in custom handler:", error);
|
|
325
|
+
res.status(500).json({
|
|
326
|
+
error: "Internal server error",
|
|
327
|
+
message: error.message
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
});
|
|
35
332
|
```
|
|
333
|
+
|
|
334
|
+
## TypeScript Support
|
|
335
|
+
|
|
336
|
+
The plugin includes TypeScript definitions. For better type safety, you can type your handlers:
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
import { Request, Response } from "express";
|
|
340
|
+
import { FlinkApp } from "@flink-app/flink";
|
|
341
|
+
|
|
342
|
+
const myHandler = async (
|
|
343
|
+
req: Request,
|
|
344
|
+
res: Response,
|
|
345
|
+
app: FlinkApp<AppContext>
|
|
346
|
+
) => {
|
|
347
|
+
// Fully typed handler
|
|
348
|
+
const userId: string = req.params.id;
|
|
349
|
+
const user = await app.ctx.repos.userRepo.findById(userId);
|
|
350
|
+
res.json({ user });
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
genericRequestPlugin({
|
|
354
|
+
path: "/user/:id",
|
|
355
|
+
method: HttpMethod.get,
|
|
356
|
+
handler: myHandler,
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Security Considerations
|
|
361
|
+
|
|
362
|
+
- Always validate and sanitize input data
|
|
363
|
+
- Implement authentication/authorization when needed
|
|
364
|
+
- Be careful with direct database access
|
|
365
|
+
- Validate webhook signatures for external integrations
|
|
366
|
+
- Use HTTPS in production
|
|
367
|
+
- Rate limit sensitive endpoints
|
|
368
|
+
- Never expose sensitive information in error messages
|
|
369
|
+
|
|
370
|
+
## License
|
|
371
|
+
|
|
372
|
+
MIT
|