Thanks for your interesting in contributing to Graphile's GraphQL libraries!
First, and most importantly, contributions to Graphile are governed by the Graphile Code of Conduct (which uses the Contributor Covenant); you can read it here: https://www.graphile.org/postgraphile/code-of-conduct/
Following are some guidelines for contributions.
We use yarn
to manage this monorepo; we strongly recommend that you only use
yarn
when dealing with it - not npm
, pnpm
or similar. (Not because these
technologies are in any way inferior to yarn
, simply because they're not 100%
compatible with each other and we require that you use yarn
to contribute.)
Install the dependencies with yarn
, and then run yarn watch
which will
compile all the source code with tsc
(TypeScript) and will keep watching the
filesystem for changes. (You can do yarn build
for a one-time build if you
prefer.)
yarn
yarn watch # or 'yarn build'
We assume you have a local PostgreSQL server running in "trust" authentication
mode. Other options may or may not work - you may need to set PGHOST
,
PGUSER
, PGPASSWORD
and/or similar config variables.
If you don't have such a server, you can use docker to run it locally:
# Run a temporary postgres instance on port 6432
docker run --rm -it -e POSTGRES_HOST_AUTH_METHOD=trust -p 6432:5432 postgres:17 -c wal_level=logical
Note that this Docker will keep running until you kill it (e.g. with Ctrl-C
)
and thus you will need to continue with a different terminal window.
Be sure to set the required environmental variables for this setup before you attempt to run the tests; you will need these for each terminal window that you attempt to run the tests from:
export PGUSER=postgres
export PGHOST=127.0.0.1
export PGPORT=6432
Tip
If you want to keep the data between sessions, run docker in the background, or the above doesn't work for you, this version:
- mounts permanent storage into
/var/run/postgresql
- detaches (runs in background)
- explicitly uses host networking
docker run -v /var/run/postgresql/:/var/run/postgresql/ --network host -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_HOST_AUTH_METHOD=trust -e POSTGRES_INITDB_ARGS='--auth-host=trust' -d postgres
You'll need to indicate the PostgreSQL user to use:
export PGUSER=postgres
If you previously set PGHOST/PGPORT then you will need to reset these for this configuration too:
export PGHOST=/var/run/postgresql/
export PGPORT=5432
The command psql
should now work (exit with Ctrl-D
). We require this utility
to install the test fixtures; if you don't have psql
installed, t you may
install it using your preferred package manager, for example:
sudo apt update && sudo apt install postgresql-client
You do not need to run this every time, but if you make changes to the SQL test fixtures (or pull down any updates) then you will need to run it to apply your changes.
yarn pretest
Now you're ready to run the tests:
yarn test
Note
This may make your fans (if you have any) spin a bit as it uses significant concurrency.
If the above succeeds, you're good to go! If not, please try again after running
yarn install --force
and always feel free to reach out via
our discord chat on the #core-development channel.
macOS comes with an older version of diff
. We rely on diff
for snapshot
testing. Should your snapshot tests fail unexpectedly (for instance, with
varying line endings), consider updating diff
.
You can make use of homebrew
or other macOS package managers for this purpose.
Here's an example for homebrew
:
brew install diffutils
There's nothing worse than having your PR with 3 days of work in it rejected because it's just too complex to be sensibly reviewed! If you're interested in opening a PR please open an issue to discuss it first, or come chat with us: http://discord.gg/graphile
Sometimes, your suggestions are more appropriate as a plugin rather than in core - if this is the case then we'll let you know and can guide you how to achieve this. Often it may make sense to change your PR into a series of smaller PRs that each focus on changing one thing.
Small, focussed PRs are generally welcome without previous approval.
Please do not include refactoring of existing code in a PR that adds a feature - you should always aim for PRs to change as few lines of code as possible to make them easier to review and iterate. If refactoring is necessary, it often makes sense to raise this refactoring in a separate PR (or series of PRs) so that it can be reviewed/merged separately.
At Graphile we're obsessed with performance, and it is critical to a wide range of our users; as such we favour performance over legibility. This means that we typically eschew immutability in favour of more performant operations and we tend to use explicit loops rather than functional equivalents. Most of our optimisations are focussed around the V8 engine (and Node.js in particular).
Typically we care more about the load on the Garbage Collector than we do on the absolute performance of the code - so try and avoid allocations where possible.
This said, we still want our software to be maintainable so code quality and legibility is important. We're not super interested in micro-optimisations; if you want to do a PR with performance enhancements that are more than a couple lines of code, please come chat with us first. If your PR is to improve performance, you should include before/after benchmarks and a script that shows how to reproduce the results.
Prefer mutability in reduce. If you want to break out into a for
loop that's
fine too - it's faster but does not reduce the load on the garbage collector
significantly.
// Slow:
const result = ["a", "b", "c"].reduce(
(memo, letter) => ({ ...memo, [letter]: true }),
{},
);
// Faster:
const result = ["a", "b", "c"].reduce((memo, letter) => {
memo[letter] = true;
return memo;
}, {});
// Fastest:
const result = {};
for (const letter of ["a", "b", "c"]) {
result[letter] = true;
}
Regexps that end with $
like /_id$/
are typically fairly expensive (much
more so than regexps that lock the start /^foo_
); is generally more efficient
to do .endsWith('_id')
when appropriate, but use whatever makes sense.
Try to avoid repeating yourself - by putting shared logic into a shared function, V8 can perform shared JIT optimisations and ultimately this should mean that the code runs faster (and uses less memory).
Graphile's software typically requires at least Node 16; this means we get access to a lot of ES2021 goodness. Use https://node.green/ to check what we have access to.
Object.values(obj)
is better thanObject.keys(obj).map(k => obj[k])
.arr.find(...)
is better thanarr.filter(...)[0]
- use
async
/await
- it's fast!