@flink-app/flink 2.0.0-alpha.63 → 2.0.0-alpha.65
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/CHANGELOG.md +26 -0
- package/dist/src/FlinkHttpHandler.d.ts +14 -1
- package/package.json +1 -1
- package/spec/FlinkApp.htmlResponse.spec.ts +129 -0
- package/src/FlinkHttpHandler.ts +26 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @flink-app/flink
|
|
2
2
|
|
|
3
|
+
## 2.0.0-alpha.65
|
|
4
|
+
|
|
5
|
+
## 2.0.0-alpha.64
|
|
6
|
+
|
|
7
|
+
### Patch Changes
|
|
8
|
+
|
|
9
|
+
- 95b99ee: Add image and binary Buffer response support
|
|
10
|
+
|
|
11
|
+
Handlers can now return a Node.js `Buffer` as `data` when `responseType` is set to an image or binary MIME type. The buffer is sent as raw bytes with the correct `Content-Type` — no JSON wrapping.
|
|
12
|
+
|
|
13
|
+
Common image and binary MIME types are now first-class literals in `RouteProps.responseType` with autocomplete support: `"image/png"`, `"image/jpeg"`, `"image/gif"`, `"image/webp"`, `"image/svg+xml"`, `"application/pdf"`, `"application/octet-stream"`.
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
export const Route: RouteProps = {
|
|
19
|
+
path: "/logo",
|
|
20
|
+
responseType: "image/png",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const handler: GetHandler<Ctx, Buffer> = async () => {
|
|
24
|
+
const buf = await fs.promises.readFile("./assets/logo.png");
|
|
25
|
+
return { data: buf };
|
|
26
|
+
};
|
|
27
|
+
```
|
|
28
|
+
|
|
3
29
|
## 2.0.0-alpha.63
|
|
4
30
|
|
|
5
31
|
### Patch Changes
|
|
@@ -250,8 +250,21 @@ export interface RouteProps {
|
|
|
250
250
|
* return { data: `id,name\n1,Alice` };
|
|
251
251
|
* };
|
|
252
252
|
* ```
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```typescript
|
|
256
|
+
* export const Route: RouteProps = {
|
|
257
|
+
* path: "/logo",
|
|
258
|
+
* responseType: "image/png",
|
|
259
|
+
* };
|
|
260
|
+
*
|
|
261
|
+
* const handler: GetHandler<AppContext, Buffer> = async ({ ctx }) => {
|
|
262
|
+
* const imageBuffer = await fs.promises.readFile("./logo.png");
|
|
263
|
+
* return { data: imageBuffer };
|
|
264
|
+
* };
|
|
265
|
+
* ```
|
|
253
266
|
*/
|
|
254
|
-
responseType?: "html" | "csv" | "text" | "xml" | (string & {});
|
|
267
|
+
responseType?: "html" | "csv" | "text" | "xml" | "image/png" | "image/jpeg" | "image/gif" | "image/svg+xml" | "image/webp" | "application/pdf" | "application/octet-stream" | (string & {});
|
|
255
268
|
/**
|
|
256
269
|
* Direct JSON Schema for request body validation.
|
|
257
270
|
* When set, bypasses manifest lookup for request schema.
|
package/package.json
CHANGED
|
@@ -129,3 +129,132 @@ describe("HTML response handler (html: true in RouteProps)", () => {
|
|
|
129
129
|
expect(response.text).toBe("<h1>Hello World</h1>");
|
|
130
130
|
});
|
|
131
131
|
});
|
|
132
|
+
|
|
133
|
+
describe("Binary Buffer response handler (responseType: image/png)", () => {
|
|
134
|
+
let app: FlinkApp<TestContext>;
|
|
135
|
+
|
|
136
|
+
afterEach(async () => {
|
|
137
|
+
if (app && app.started) {
|
|
138
|
+
await app.stop();
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("should return image/png content type for Buffer response", async () => {
|
|
143
|
+
// Minimal valid 1x1 red PNG (67 bytes)
|
|
144
|
+
const pngBuffer = Buffer.from(
|
|
145
|
+
"89504e470d0a1a0a0000000d49484452000000010000000108020000009001" +
|
|
146
|
+
"2e00000000c4944415478016360f8cfc00000000200016934e360000000049454e44ae426082",
|
|
147
|
+
"hex"
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const handler: GetHandler<TestContext, Buffer> = async () => {
|
|
151
|
+
return { data: pngBuffer };
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
app = new FlinkApp<TestContext>({ name: "test-png-ct", port: 4110 });
|
|
155
|
+
await app.start();
|
|
156
|
+
|
|
157
|
+
app.addHandler({
|
|
158
|
+
default: handler,
|
|
159
|
+
Route: { method: HttpMethod.get, path: "/image", responseType: "image/png" },
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const response = await request(app.expressApp).get("/image");
|
|
163
|
+
|
|
164
|
+
expect(response.status).toBe(200);
|
|
165
|
+
expect(response.headers["content-type"]).toMatch(/image\/png/);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should return the exact buffer bytes as the response body", async () => {
|
|
169
|
+
const pngBuffer = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
170
|
+
|
|
171
|
+
const handler: GetHandler<TestContext, Buffer> = async () => {
|
|
172
|
+
return { data: pngBuffer };
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
app = new FlinkApp<TestContext>({ name: "test-png-body", port: 4111 });
|
|
176
|
+
await app.start();
|
|
177
|
+
|
|
178
|
+
app.addHandler({
|
|
179
|
+
default: handler,
|
|
180
|
+
Route: { method: HttpMethod.get, path: "/image", responseType: "image/png" },
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const response = await request(app.expressApp)
|
|
184
|
+
.get("/image")
|
|
185
|
+
.buffer(true)
|
|
186
|
+
.parse((res: any, callback: any) => {
|
|
187
|
+
const chunks: Buffer[] = [];
|
|
188
|
+
res.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
189
|
+
res.on("end", () => callback(null, Buffer.concat(chunks)));
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
expect(response.status).toBe(200);
|
|
193
|
+
expect(Buffer.compare(response.body, pngBuffer)).toBe(0);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("should respect a custom status code for binary responses", async () => {
|
|
197
|
+
const handler: GetHandler<TestContext, Buffer> = async () => {
|
|
198
|
+
return { status: 404, data: Buffer.from("not found") };
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
app = new FlinkApp<TestContext>({ name: "test-png-status", port: 4112 });
|
|
202
|
+
await app.start();
|
|
203
|
+
|
|
204
|
+
app.addHandler({
|
|
205
|
+
default: handler,
|
|
206
|
+
Route: { method: HttpMethod.get, path: "/image", responseType: "image/png" },
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const response = await request(app.expressApp).get("/image");
|
|
210
|
+
|
|
211
|
+
expect(response.status).toBe(404);
|
|
212
|
+
expect(response.headers["content-type"]).toMatch(/image\/png/);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("should support image/jpeg content type", async () => {
|
|
216
|
+
const jpegBuffer = Buffer.from([0xff, 0xd8, 0xff, 0xe0]);
|
|
217
|
+
|
|
218
|
+
const handler: GetHandler<TestContext, Buffer> = async () => {
|
|
219
|
+
return { data: jpegBuffer };
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
app = new FlinkApp<TestContext>({ name: "test-jpeg-ct", port: 4113 });
|
|
223
|
+
await app.start();
|
|
224
|
+
|
|
225
|
+
app.addHandler({
|
|
226
|
+
default: handler,
|
|
227
|
+
Route: { method: HttpMethod.get, path: "/photo", responseType: "image/jpeg" },
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const response = await request(app.expressApp).get("/photo");
|
|
231
|
+
|
|
232
|
+
expect(response.status).toBe(200);
|
|
233
|
+
expect(response.headers["content-type"]).toMatch(/image\/jpeg/);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("should support application/octet-stream for generic binary data", async () => {
|
|
237
|
+
const binaryData = Buffer.from([0x00, 0x01, 0x02, 0x03, 0xff]);
|
|
238
|
+
|
|
239
|
+
const handler: GetHandler<TestContext, Buffer> = async () => {
|
|
240
|
+
return { data: binaryData };
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
app = new FlinkApp<TestContext>({ name: "test-octet", port: 4114 });
|
|
244
|
+
await app.start();
|
|
245
|
+
|
|
246
|
+
app.addHandler({
|
|
247
|
+
default: handler,
|
|
248
|
+
Route: {
|
|
249
|
+
method: HttpMethod.get,
|
|
250
|
+
path: "/binary",
|
|
251
|
+
responseType: "application/octet-stream",
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const response = await request(app.expressApp).get("/binary");
|
|
256
|
+
|
|
257
|
+
expect(response.status).toBe(200);
|
|
258
|
+
expect(response.headers["content-type"]).toMatch(/application\/octet-stream/);
|
|
259
|
+
});
|
|
260
|
+
});
|
package/src/FlinkHttpHandler.ts
CHANGED
|
@@ -272,8 +272,33 @@ export interface RouteProps {
|
|
|
272
272
|
* return { data: `id,name\n1,Alice` };
|
|
273
273
|
* };
|
|
274
274
|
* ```
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```typescript
|
|
278
|
+
* export const Route: RouteProps = {
|
|
279
|
+
* path: "/logo",
|
|
280
|
+
* responseType: "image/png",
|
|
281
|
+
* };
|
|
282
|
+
*
|
|
283
|
+
* const handler: GetHandler<AppContext, Buffer> = async ({ ctx }) => {
|
|
284
|
+
* const imageBuffer = await fs.promises.readFile("./logo.png");
|
|
285
|
+
* return { data: imageBuffer };
|
|
286
|
+
* };
|
|
287
|
+
* ```
|
|
275
288
|
*/
|
|
276
|
-
responseType?:
|
|
289
|
+
responseType?:
|
|
290
|
+
| "html"
|
|
291
|
+
| "csv"
|
|
292
|
+
| "text"
|
|
293
|
+
| "xml"
|
|
294
|
+
| "image/png"
|
|
295
|
+
| "image/jpeg"
|
|
296
|
+
| "image/gif"
|
|
297
|
+
| "image/svg+xml"
|
|
298
|
+
| "image/webp"
|
|
299
|
+
| "application/pdf"
|
|
300
|
+
| "application/octet-stream"
|
|
301
|
+
| (string & {});
|
|
277
302
|
|
|
278
303
|
/**
|
|
279
304
|
* Direct JSON Schema for request body validation.
|