Skip to main content

Custom functions

Custom functions can be used to run custom code at different points of a typegraph. These constructs fall under materializers which are, concretly, functions attached to a specific runtime. For some common tasks, like simple operations on database tables for example, runtime implementations provide materializer generators to minimize boilerplate. For cases not expressible by generators, runtimes like the DenoRuntime allow us to write more powerful custom functions.

Custom functions are commonly used for:

  • Specialized business logic to respond directly to incoming requests
  • Authentication policy logic

The following example uses the DenoRuntime to respond to requests and define a policy.

Loading...

Note that for the fib root materializer, we're using a typescript module in an external file. Here's what scripts/fib.ts looks like:

const CACHE = [1, 1];
const MAX_CACHE_SIZE = 1000;

export default function fib({ size }: { size: number }) {
if (size > MAX_CACHE_SIZE) {
throw new Error(`unsupported size ${size} > ${MAX_CACHE_SIZE}`);
}
let i = CACHE.length;
while (i++ < size) {
CACHE.push(CACHE[i - 2] + CACHE[i - 3]);
}
return CACHE.slice(0, size);
}

The following runtimes can be used to run custom functions:

Accessing function context

Beta

The following feature is currently only implemented for the DenoRuntime.

On some runtimes, custom functions are passed the context object along with the materializer inputs. This object provides access to all kind of information about the context in which the function is running. The following example illustrates availaible fields:

Loading...

Note, the typescript version of the sample uses a closure instead of a string snippet to define the function. This is a simple syntax sugar availaible when using DenoRuntime through the typescript sdk or the PythonRuntime the python one. Consult the reference for each runtime to look at what's availaible.

Accessing the typegraph

Beta

The following feature is currently only implemented for the DenoRuntime.

To do anything meaningful with custom functions, you'll want to access the rest of functionality implemented on your typegraph. The primary way of doing this is by sending GraphqQl queries from within your function. On the DenoRuntime, to make this easier, there's a gql object passed to all functions. The following exapmle illustrates how it functions:

Loading...

And scripts/createVote.ts looks like:

export async function handle(
inp: { ideaId: string; authorEmail: string },
_ctx: any,
// the third paramter contains the gql client object
{ gql }: any,
) {
// find the referenced idea from the typegraph
const { data: { idea } } = await gql`
query getIdeaAuthorEmail($ideaId: String!) {
idea: i_get_idea(where: { id: $ideaId }) {
authorEmail
}
}
`.run({ ideaId: inp.ideaId });

// we check if the idea exists
if (!idea) {
throw new Error(`no idea found under id ${inp.ideaId}`);
}

// and that the author and voter aren't the same
if (inp.authorEmail == idea.authorEmail) {
throw new Error(`author of idea can't vote for idea`);
}

// we persist the vote with another gql call
const { data: { vote } } = await gql`
mutation insertVote($ideaId: String!, $authorEmail: String!) {
vote: i_create_vote(data: {
authorEmail: $authorEmail,
idea: { connect: { id: $ideaId } }
}) {
id
}
}
`.run(inp);
return { voteId: vote.id };
}