Skip to main content

TypeScript

Graphile Worker is written in TypeScript. By default, for safety, payloads are typed as unknown since they may have been populated by out of date code, or even from other sources. This requires you to add a type guard or similar to ensure the payload conforms to what you expect. It can be convenient to declare the payload types up front to avoid this unknown, but doing so might be unsafe please be sure to read the caveats below.

Using type assertion functions

To ensure your system is as safe as possible (and guard against old jobs, or jobs specified outside of TypeScript's type checking) we recommend that you use type assertion functions to assert that your payload is of the expected type.

If this is too manual, you might prefer to use a library such as runtypes or one of the many others of a similar kind.

Example of using an assertion function

The following is an example implementation of sending emails using Amazon SES.

import type { Task, WorkerUtils } from "graphile-worker";
import { ses } from "./aws";

interface Payload {
to: string;
subject: string;
body: string;
}

function assertPayload(payload: any): asserts payload is Payload {
if (typeof payload !== "object" || !payload) throw new Error("invalid");
if (typeof payload.to !== "string") throw new Error("invalid");
if (typeof payload.subject !== "string") throw new Error("invalid");
if (typeof payload.body !== "string") throw new Error("invalid");
}

export const send_email: Task = async function (payload) {
assertPayload(payload);
const { to, subject, body } = payload;
await ses.sendEmail({
Destination: {
ToAddresses: [to],
FromAddresses: ["[email protected]"],
},
Message: {
Subject: {
Charset: "UTF-8",
Data: subject,
},
Body: {
Text: {
Charset: "UTF-8",
Data: body,
},
},
},
});
};

If now we introduce a new functionality to set the from address, we have to take into account that older jobs will not have the from address set. We should adjust our code like so:

import type { Task, WorkerUtils } from "graphile-worker";
import { ses } from "./aws";

interface Payload {
to: string;
subject: string;
body: string;
+ from?: string;
}

function assertPayload(payload: any): asserts payload is Payload {
if (typeof payload !== "object" || !payload) throw new Error("invalid");
if (typeof payload.to !== "string") throw new Error("invalid");
if (typeof payload.subject !== "string") throw new Error("invalid");
if (typeof payload.body !== "string") throw new Error("invalid");
+ if (typeof payload.from !== "string" && typeof payload.from !== "undefined")
+ throw new Error("invalid");
}

export const send_email: Task = async function (payload) {
assertPayload(payload);
- const { to, subject, body } = payload;
+ const { to, subject, body, from } = payload;
await ses.sendEmail({
Destination: {
ToAddresses: [to],
- FromAddresses: ["[email protected]"],
+ FromAddresses: [from ?? "[email protected]"],
},
Message: {
Subject: {
Charset: "UTF-8",
Data: subject,
},
Body: {
Text: {
Charset: "UTF-8",
Data: body,
},
},
},
});
};

Assuming type via GraphileWorker.Tasks

As an alternative to the recommended use of assertion functions, you can register types for Graphile Worker tasks using the following syntax in a shared TypeScript file in your project:

declare global {
namespace GraphileWorker {
interface Tasks {
// <name>: <payload type>; e.g.:
myTaskIdentifier: { details: "are"; specified: "here" };
}
}
}

This should then enable auto-complete and payload type safety for addJob and addJobAdhoc, and should also allow the payloads of your task functions to be inferred when defined like this:

const task: Task<"myTaskIdentifier"> = async (payload, helpers) => {
const { details, specified } = payload;
/* ... */
};

or like this:

const tasks: TaskList = {
async myTaskIdentifier(payload, helpers) {
const { details, specified } = payload;
/* ... */
},
};
danger

Using TypeScript types like this can be misleading. Graphile Worker jobs can be created in the database directly via the graphile_worker.add_job() or .add_jobs() APIs; and these APIs cannot check that the payloads added conform to your TypeScript types. Further, you may modify the payload type of a task in a later version of your application, but existing jobs may exist in the database using the old format. This can lead to you assuming that something is a number when actually it's null, resulting in more bugs in your code, so care must be taken.

We recommend you use assertion functions instead.

Example of assuming type

The following takes the Amazon SES example above, but uses the technique of assuming type instead:

import type { Task, WorkerUtils } from "graphile-worker";
import { ses } from "./aws";

declare global {
namespace GraphileWorker {
interface Tasks {
send_email: {
to: string;
subject: string;
body: string;
};
}
}
}

export const send_email: Task<"send_email"> = async function (payload) {
const { to, subject, body } = payload;
await ses.sendEmail({
Destination: {
ToAddresses: [to],
FromAddresses: ["[email protected]"],
},
Message: {
Subject: {
Charset: "UTF-8",
Data: subject,
},
Body: {
Text: {
Charset: "UTF-8",
Data: body,
},
},
},
});
};

If now we introduce the new functionality to set the from address, the changes we make have to take into account that older jobs may not have the from address set, like so:

import type { Task, WorkerUtils } from "graphile-worker";
import { ses } from "./aws";

declare global {
namespace GraphileWorker {
interface Tasks {
send_email: {
to: string;
subject: string;
body: string;
+ from?: string;
};
}
}
}

export const send_email: Task<"send_email"> = async function (payload) {
- const { to, subject, body } = payload;
+ const { to, subject, body, from } = payload;
await ses.sendEmail({
Destination: {
ToAddresses: [to],
- FromAddresses: ["[email protected]"],
+ FromAddresses: [from ?? "[email protected]"],
},
Message: {
Subject: {
Charset: "UTF-8",
Data: subject,
},
Body: {
Text: {
Charset: "UTF-8",
Data: body,
},
},
},
});
};
tip

All of the declarations would normally be put in a shared interface file, or similar. The example above defines one with the task for ease of reading.

Three ants crawling atop the footer