node.jstypescriptgoogle-cloud-functionsgoogle-cloud-pubsub

How do I add type safety to my Pub/Sub Cloud Run (gen2) function?


I have a Google Cloud Run Function (formerly Cloud Functions 2nd Gen) running TypeScript/Node.js. The documentation in the Pub/Sub tutorial shows that you get a cloudEvent, but how can I add type safety to it?

Specifically if I want to define the the type of the attributes or the message body.

This is their example:

const functions = require('@google-cloud/functions-framework');

// Register a CloudEvent callback with the Functions Framework that will
// be executed when the Pub/Sub trigger topic receives a message.
functions.cloudEvent('helloPubSub', cloudEvent => {
  // The Pub/Sub message is passed as the CloudEvent's data payload.
  const base64name = cloudEvent.data.message.data;

  const name = base64name
    ? Buffer.from(base64name, 'base64').toString()
    : 'World';

  console.log(`Hello, ${name}!`);
});

Solution

  • There are some types defined in @google-cloud/functions-framework.

    npm install @google-cloud/functions-framework@^3.3.0
    

    Then you can define your own types with the following code:

    import functions, { CloudEvent } from '@google-cloud/functions-framework'
    import { RawPubSubBody } from '@google-cloud/functions-framework/build/src/pubsub_middleware'
    
    /**
     * We get them as `attributes?: { [key: string]: string }`
     * But since we know the structure, we can define it as an interface
     */
    interface PubSubAttributes {
      myTestAttribute: string
    }
    
    /** Extend the RawPubSubBody to use PubSubAttributes */
    interface FunctionPubSubBody extends RawPubSubBody {
      message: {
        attributes: PubSubAttributes
      } & RawPubSubBody['message']
    }
    
    /** An interface that defines the shape of the body of the message */
    interface PubSubBodyData {
      testBodyParam: string
    }
    
    // https://cloudevents.github.io/sdk-javascript/classes/CloudEvent.html
    functions.cloudEvent<FunctionPubSubBody>(
      'syncAllMoviesNode',
      // You don't technically need to declare this type (it's inferred from above)
      async (cloudEvent: CloudEvent<FunctionPubSubBody>) => {
        console.log(cloudEvent)
    
        // The Pub/Sub message is passed as the CloudEvent's data payload.
        // It's base64 encoded, so we need to decode it and parse it as JSON.
        const base64Data = cloudEvent.data.message.data
        const base64String = Buffer.from(base64Data, 'base64').toString()
        const pubSubBody: PubSubBodyData = JSON.parse(base64String)
    
        const pubSubAttributes: PubSubAttributes = cloudEvent.data.message.attributes
    
        // The "Message Body" with type PubSubBodyData
        console.log(pubSubBody)
    
        // The "Attributes" as PubSubAttributes
        console.log(pubSubAttributes)
      }
    )
    

    Configuring in Cloud Scheduler

    cloud-scheduler-config

    Attributes vs Body

    This Reddit thread has some thoughts, but my takeaway is:

    But honestly you can use them however you want.