@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flink-app/flink",
3
- "version": "2.0.0-alpha.63",
3
+ "version": "2.0.0-alpha.65",
4
4
  "description": "Typescript only framework for creating REST-like APIs on top of Express and mongodb",
5
5
  "types": "dist/src/index.d.ts",
6
6
  "main": "dist/src/index.js",
@@ -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
+ });
@@ -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?: "html" | "csv" | "text" | "xml" | (string & {});
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.