Skip to main content

How Graphile Export works

The system works by converting values in memory into source code strings. One of the key things that's challenging to export is functions (and function-derived things such as classes). In JavaScript you can see the source code of a function by calling .toString() on it:

> (function add(a, b) { return a + b }).toString()
'function add(a, b) { return a + b }'

However this quickly falls down if you are using values from a parent closure (aka a "higher scope"):

> const a = 7;
undefined
> function add(b) { return a + b };
undefined
> add(3)
10
> add.toString()
'function add(b) { return a + b }'

See how the function definition string add.toString() returns its definition, but you cannot determine from that what the value of a is. This is a problem. If we were to define and execute this function in a new clean JS environment we'd get an error:

> function add(b) { return a + b }
undefined
> add(3)
Uncaught ReferenceError: a is not defined
at add (REPL1:1:19)

Graphile Export solves this by having you define your functions via a pure factory function that accepts an explicit list of dependencies:

> const { EXPORTABLE } = require("graphile-export")
undefined
> const a = 7;
undefined
> const add = EXPORTABLE((a) => function add(b) { return a + b; }, [a]);
undefined

This may feel a little bit familiar to people who are used to working with React hooks.

When you use this EXPORTABLE wrapper, the add function is augmented with the hidden properties $exporter$factory and $exporter$args that represent the first and second arguments to the EXPORTABLE(factory, args, nameHint) function respectively.

The function still works as before:

> add(3)
10
> add.toString()
'function add(b) { return a + b; }'

But graphile-export can access these special properties when it writes the code out, and now it can see the value of that "invisible" a=7:

> add.$exporter$factory.toString()
'(a) => function add(b) { return a + b; }'
> add.$exporter$args
[ 7 ]

And since we can see it, we can export it:

const a = 7;
function add(b) {
return a + b;
}

Of course, if the dependency a were a complex value (e.g. another function, or a class instance), we'd also need to make that either exportable or importable, so lets find out more about making values exportable or importable.