fastify-multipart icon indicating copy to clipboard operation
fastify-multipart copied to clipboard

Improve typescript types

Open StarpTech opened this issue 5 years ago • 11 comments

🚀 Feature Proposal

  • Some interfaces aren't covered e.g onFile handler in the new promise API.
  • Validate TS types in the CI.

Motivation

Full TS support.

StarpTech avatar Aug 25 '20 19:08 StarpTech

Would maintainer okay with the idea of adding one extra property? I'm thinking type with literal string as value, for both MultipartFile with value ("file") and MultipartValue with value ("field") to make type narrowing feel more intuitive.

Use case: https://github.com/fastify/fastify-multipart#handle-multiple-file-streams-and-fields

fastify.post('/upload/raw/any', async function (req, reply) {
  const parts = req.parts()
  for await (const part of parts) {
    if (part.file) {  // Failed type narrowing
      await pump(part.file, fs.createWriteStream(part.filename))
    } else {
      console.log(part)
    }
  }
  reply.send()
})

However

fastify.post('/upload/raw/any', async function (req, reply) {
  const parts = req.parts()
  for await (const part of parts) {
    if ("file" in part) {  // Success type narrowing
      await pump(part.file, fs.createWriteStream(part.filename))
    } else {
      console.log(part)
    }
  }
  reply.send()
})

I feel like having type property would feel more intuitive. It would look like:

fastify.post('/upload/raw/any', async function (req, reply) {
  const parts = req.parts()
  for await (const part of parts) {
    if (part.type === 'file') {  // Using discriminated unions
      await pump(part.file, fs.createWriteStream(part.filename))
    } else {
      console.log(part)
    }
  }
  reply.send()
})

Alternative, expose isFilePart as type predicate, but i felt this bit too much.

function isFilePart(part: Multipart): part is MultipartFile {
  return (part as MultipartFile).file !== undefined;
}

fastify.post('/upload/raw/any', async function (req, reply) {
  const parts = req.parts()
  for await (const part of parts) {
    if (isFilePart(part)) {  // Using discriminated unions
      await pump(part.file, fs.createWriteStream(part.filename))
    } else {
      console.log(part)
    }
  }
  reply.send()
})

nadhifikbarw avatar Mar 12 '23 10:03 nadhifikbarw

Sure!

mcollina avatar Mar 12 '23 10:03 mcollina

I spent a lot of time trying to make TypeScript to be happy with me using req.file in my router handler. The solution ended up being adding fastify.d.ts with this:

import { Readable } from "stream";
declare module "fastify" {
  export interface FastifyRequest {
    file: () => Promise<{
      file: Readable;
      fieldname: string;
      filename: string;
      encoding: string;
      mimetype: string;
      fields: { [key: string]: string };
    }>;
  }
}

and then including that file in my typeRoots in my tsconfig.json:

{
  "compilerOptions": {
    "typeRoots": ["fastify.d.ts"]
  },
}

Posting here in case it helps others.

jsjoeio avatar Mar 18 '23 03:03 jsjoeio

Would you like to send a PR?

@jsjoeio

gurgunday avatar Jan 28 '24 10:01 gurgunday

Sure! I'll see if I can find time this week or next!

jsjoeio avatar Jan 29 '24 16:01 jsjoeio

Okay I started looking into this and I'm very confused 🤔

Here's a repo.

I thought by installing @fastify/multipart and then registering it like so:

const server = fastify();

server.register(require("@fastify/multipart"));

then TypeScript would pick up the library's types, but if you look here, it doesn't have isMultipart for example (here)

image

So I'm not really sure how to fix (without digging in further). Anyone else have any ideas on what I'm doing wrong? Here's the commit adding multipart

jsjoeio avatar Jan 30 '24 20:01 jsjoeio

Import the module at the top, otherwise TS won't load the declaration merging.

mcollina avatar Jan 30 '24 21:01 mcollina

Import the module at the top, otherwise TS won't load the declaration merging.

😅 wow...sorry, totally spaced on that. that fixed and i'm no longer able to reproduce the issue I had before:

diff --git a/index.ts b/index.ts
index 18fe828..9dfc3c7 100644
--- a/index.ts
+++ b/index.ts
@@ -1,4 +1,5 @@
 import fastify from "fastify";
+import "@fastify/multipart";
 
 const server = fastify();
 
@@ -13,7 +14,7 @@ server.post("/upload", async function (req, reply) {
     const data = await req.file();
 
     reply.status(200).send({
-      fileName: data.filename,
+      fileName: data?.filename,
     });
   } catch (error) {
     reply.status(400).send("something went wrong");

@gurgunday should we close this then, or did you have something else in mind for the PR?

jsjoeio avatar Jan 30 '24 23:01 jsjoeio

I see in the issue description that onFile doesn’t have corresponding type information

Can you please try adding it?

gurgunday avatar Jan 31 '24 06:01 gurgunday

I probably won't get to this so anyone else, feel free to take on instead

jsjoeio avatar Mar 25 '24 18:03 jsjoeio