Skip to main content

Composability

Since Interval actions are just async functions, sharing logic between actions is as simple as writing shared functions.

But actions also have access to special io and ctx objects. These objects are commonly accessed as arguments of the action handler function. For example:

const interval = new Interval({
actions: {
some_action: async (io, ctx) => {},
},
});

To extract and share logic from an action that uses io and/or ctx, your shared functions can use the io and ctx objects exported from the Interval SDK:

import Interval, { io, ctx } from "@interval/sdk";

Both objects are identical whether you access them as exports from the SDK or as arguments passed to your action handler functions. Which form you use is matter of stylistic preference.

warning

Trying to access io or ctx outside of an action will cause an error to be thrown. If you write shared functions that reference io or ctx, only call those functions within an action.

Example‚Äč

Imagine you have multiple actions that require the ability for person running the action to lookup a user from your database.

Your code might look something like this:

import Interval from "@interval/sdk";
import db from "./db";

const interval = new Interval({
apiKey: "<YOUR API KEY>", // get an API key at https://interval.com/dashboard/develop/keys
actions: {
refund_user: async (io, ctx) => {
let user: User | null = null;

if (ctx.params.id) {
const foundUser = await db.user.lookupById(ctx.params.id);
if (foundUser) {
user = foundUser;
}
}

if (!user) {
user = await io.search("Select a user", {
onSearch: async query => {
return user.find(query);
},
renderResult: user => ({
label: [user.firstName, user.lastName].join(" "),
description: user.email,
}),
});
}
// By this line, we have a guaranteed not null value for our user variable.
// ... logic to refund a user
},
apply_credit: async (io, ctx) => {
let user: User | null = null;

if (ctx.params.id) {
const foundUser = await db.user.lookupById(ctx.params.id);
if (foundUser) {
user = foundUser;
}
}

if (!user) {
user = await io.search("Select a user", {
onSearch: async query => {
return user.find(query);
},
renderResult: user => ({
label: [user.firstName, user.lastName].join(" "),
description: user.email,
}),
});
}
// By this line, we have a guaranteed not null value for our user variable.
// ... logic to apply user credit
},
},
});

interval.listen();

The above code is clearly very repetitive. We can extract our user lookup logic into its own function:

import Interval, { io, ctx } from "@interval/sdk";
import db from "./db";

async function lookupOrSearchForUser() {
let user: User | null = null;

if (ctx.params.id) {
const foundUser = await db.user.lookupById(ctx.params.id);
if (foundUser) {
user = foundUser;
}
}

if (!user) {
user = await io.search("Select a user", {
onSearch: async query => {
return user.find(query);
},
renderResult: user => ({
label: [user.firstName, user.lastName].join(" "),
description: user.email,
}),
});
}
}

const interval = new Interval({
apiKey: "<YOUR API KEY>", // get an API key at https://interval.com/dashboard/develop/keys
actions: {
refund_user: async () => {
const user = await lookupOrSearchForUser();
// By this line, we have a guaranteed not null value for our user variable.
// ... logic to refund a user
},
apply_credit: async () => {
const user = await lookupOrSearchForUser();
// By this line, we have a guaranteed not null value for our user variable.
// ... logic to apply user credit
},
},
});

interval.listen();
Was this section useful?
On this page