Why am I encountering a Pipedream Internal Error when implementing a JS step to generate a PDF using PDFKit?

This topic was automatically generated from Slack. You can find the original thread here.

**** I have the following error when implementing a JS step to generate a PDF
Pipedream Internal Error
Please retry your event or reach out to Pipedream support at https://pipedream.com/support/

Here is part of my source code:

import PDFDocument from "pdfkit";
import fs from "fs";

export default defineComponent({
  async run({ steps, $ }) {
      const invoice = {
        shipping: {
          name: "John Doe",
          address: "1234 Main Street",
          city: "San Francisco",
          state: "CA",
          country: "US",
          postal_code: 94111
        },
        items: [
          {
            item: "TC 100",
            description: "Toner Cartridge",
            quantity: 2,
            amount: 6000
          },
          {
            item: "USB_EXT",
            description: "USB Cable Extender",
            quantity: 1,
            amount: 2000
          }
        ],
        subtotal: 8000,
        paid: 0,
        invoice_nr: 1234
      };

    return createInvoice(invoice, "invoice.pdf");
  },
})

function createInvoice(invoice, path) {
  let doc = new PDFDocument({ size: "A4", margin: 50 });

  generateHeader(doc);
  generateCustomerInformation(doc, invoice);
  generateInvoiceTable(doc, invoice);
  generateFooter(doc);

  doc.end();
  doc.pipe(fs.createWriteStream(path));
}

Based on the code you’ve provided, it looks like you’re trying to generate a PDF using PDFKit and save it to a file within a Pipedream workflow. However, there are a few issues with the code that could be causing the error you’re encountering. Firstly, in Pipedream’s Node.js environment, you must write files to the /tmp directory, as it’s the only writable directory you have access to. You’re currently attempting to write to invoice.pdf, which is not a valid path in the Pipedream environment. Secondly, you’re using a callback function with doc.pipe(fs.createWriteStream(path)), but you’re not waiting for the PDF generation to complete before the function exits. You need to wrap this in a Promise to ensure the PDF is fully written before the step completes. Here’s a revised version of your code that addresses these issues: import PDFDocument from "pdfkit"; import fs from "fs"; export default defineComponent({ async run({ steps, $ }) { const invoice = { // ... your invoice data }; const path = `/tmp/invoice.pdf`; await createInvoice(invoice, path); // Now you can use the file at `path` for further steps in your workflow }, }); function createInvoice(invoice, path) { return new Promise((resolve, reject) => { let doc = new PDFDocument({ size: "A4", margin: 50 }); generateHeader(doc); generateCustomerInformation(doc, invoice); generateInvoiceTable(doc, invoice); generateFooter(doc); const stream = fs.createWriteStream(path); doc.pipe(stream); doc.end(); stream.on("finish", () => resolve()); stream.on("error", reject); }); } In this updated code, I’ve made the following changes: 1. Changed the file path to /tmp/invoice.pdf. 2. Wrapped the createInvoice function in a Promise to ensure the PDF is fully written before the step completes. 3. Added event listeners for the finish and error events to resolve or reject the Promise accordingly. Please try this updated code in your Pipedream workflow. If you continue to experience issues, don’t hesitate to reach out to Pipedream support at https://pipedream.com/support/.

I have updated my code with your suggestion but I now hve the following warning :
Warning (likely out of date)
These results are likely out of date. Test this step again

Here is my code

import PDFDocument from "pdfkit";
import fs from "fs";

export default defineComponent({
  async run({ steps, $ }) {


      const invoice = {
// invoice date
      };

      let test = await createInvoice(invoice, "/tmp/invoice.pdf");


      


    return test;
  },
})





function createInvoice(invoice, path, picture) {
  return new Promise((resolve, reject) => {

  let doc = new PDFDocument({ size: "A4", margin: 50 });

  generateHeader(doc/**, picture**/);
  generateCustomerInformation(doc, invoice);
  generateInvoiceTable(doc, invoice);
  generateFooter(doc);

      const stream = fs.createWriteStream(path);
    doc.pipe(stream);
    doc.end();

    stream.on("finish", () => resolve());
    stream.on("error", reject);
});
}

Based on the code you’ve provided and the warning message, it seems like the warning is indicating that the results from the test run of your Pipedream component may be out of date. This can happen if you’ve made changes to the code and haven’t tested it again since making those changes. To address this, you should test the step again within the Pipedream workflow UI to ensure that the changes you’ve made are working as expected. Testing the step will execute the code with the current configuration and should clear the warning if everything is set up correctly. Regarding the code you’ve provided, it looks like you’ve implemented the PDF creation logic within a Promise to ensure that the PDF generation completes before the step finishes, which aligns with the best practices for handling asynchronous operations in Pipedream. Here’s a slightly modified version of your code to ensure that the path to the generated PDF is returned after the createInvoice function resolves: ```
import PDFDocument from “pdfkit”; import fs from “fs”; export default defineComponent({ async run({ steps, $ }) { const invoice = { // invoice data }; const path = “/tmp/invoice.pdf”; await createInvoice(invoice, path); // Return the path to the generated PDF return { path }; }, }); function createInvoice(invoice, path) { return new Promise((resolve, reject) => { let doc = new PDFDocument({ size: “A4”, margin: 50 }); generateHeader(doc); generateCustomerInformation(doc, invoice); generateInvoiceTable(doc, invoice); generateFooter(doc); const stream = fs.createWriteStream(path); doc.pipe(stream); doc.end(); stream.on(“finish”, () => resolve(path)); stream.on(“error”, reject); }); } // Define the generateHeader, generateCustomerInformation, generateInvoiceTable, and generateFooter functions here

Hi Benjamin,

I notice a few things that might be the cause of the issue.

  1. Your code appears to attempt to save the file in the current directory. Only /tmp in workflows is writeable.
  2. Your async function doesn’t return a promise that resolves when the end of the stream is finished. The code step might pass execution to the next step if there’s no promise to hold the execution to finish the stream
  3. I’m not 100% sure if pdfkit is supported, if it needs to compile binaries outside of /tmp, then you’ll run into the same issue as #1. I suggest attempting to install that package in it’s own code step just as a test to make sure it’s compatible.

Hi 1 - Yes, I realized that I forgot to add the /tm/ folder, thanks for pointing out !
2 - How should I do to hold the execution ?
3 - I have created a simple step to test PDFKit is supported by Pipedream

import PDFDocument from "pdfkit";
import fs from "fs";

export default defineComponent({
  async run({ steps, $ }) {
    // Reference previous step data using the steps object and return data to use it in future steps

    let doc = new PDFDocument({ size: "A4", margin: 50 });
    return doc

  },
})

But I have the following error :
CIRCULAR_RETURN_VALUE: Return value contains [Circular] reference(s) that were filtered out.

You can hold execution by returning a Promise.

For example:

return new Promise((resolve, reject) => {
  // create your stream

  stream.on('end', () => resolve());
});

Otherwise, the stream has no way of holding execution.

Regarding the error, remember that only JSON serializable data can be returned from code steps.

I suspect an instance of a PDFDocument is not JSON serializable.

I recommend using the new $.files helper to upload files to the Project’s File Store for quick and easy testing, rather than trying to write to tmp alone.

Hi I am using the following Promise but i still have the Warning
Code was still running when the step ended. Make sure to await all Promises, or promisify callback functions. May be a false positive

function createInvoice(invoice, path) {
  return new Promise((resolve, reject) => {

  let doc = new PDFDocument({ size: "A4", margin: 50 });

  generateHeader(doc);
  generateCustomerInformation(doc, invoice);
  generateInvoiceTable(doc, invoice);
  generateFooter(doc);
   const stream = fs.createWriteStream(path);
    doc.pipe(stream);
    doc.end();
    stream.on("finish", () => resolve());
    stream.on("error", reject);
});
}

Are you certain the doc.end() resolves before stream.on('finish')?

I think there’s a race condition there

Hi Pierce, I have done a test with the Pipedream pdfKit example (Running asynchronous code in Node.js), but I am still encountering a “Warning: Code was still running when the step ended. Make sure to await all Promises, or promisify callback functions. May be a false positive

Here below is the source code:

import PDFDocument from "pdfkit";
import fs from "fs";

export default defineComponent({
  async run({ steps, $ }) {
    // Reference previous step data using the steps object and return data to use it in future steps

    let doc = new PDFDocument({ size: "A4", margin: 50 });
    this.fileName = `/tmp/test.pdf`;
    let file = fs.createWriteStream(this.fileName);
    doc.pipe(file);
    doc.text("Hello world!");

    // Finalize PDF file
    doc.end();
    // Wait for PDF to finalize
    await new Promise((resolve) => file.on("finish", resolve));
    // Once done, get stats
    const stats = fs.statSync(this.fileName);
    return stats
  },
})

Hi ,

I finally implemented my step with PDFKIt with the following warning message “Warning: Code was still running when the step ended. Make sure to await all Promises, or promisify callback functions. May be a false positive
I have no issue so far after several workflow run so I consider it as a false positive, even if I would be more confortable to identify why Pipedream consider code is still running at the end of the step