Audience: code assistants (Aider, Claude, Codex, Cody, Copilot, etc.) working on this repository.
This is a monorepo with a large number of interdependencies. The repo is split
into 4 main categories based on the root folders, each of which have a
<category>/website project for their own website:
utils- lots of helper projects that we use to build the main projects. Website: https://star.graphile.org ("star" as in "asterisk" as in "everything else")pg-sql2- safe (and extremely fast) SQL template stringsgraphile-config- standard utils around creating plugins and presets, used throughout Graphile projects.graphile-export- can export an in-memory GraphQL schema (with resolvers/plans) as an executable JS file - particularly useful for serverless.pg-introspection- exposes the PostgreSQL system catalog via TypeScript interfaces with documentation pulled from postgres.
grafast- a new cutting-edge planning and execution engine for GraphQL. Uses GraphQL.js for schema elements (new GraphQLObjectType, etc) but completely replaces execution and output logic. Website: https://grafast.orggrafserv- a Grafast-optimized server@grafserv/persisted- a plugin for Grafserv for persisted operations / trusted documents.ruru- a Grafast-optimized GraphiQL distribution@dataplan/pg- a collection of Grafast "step classes" for dealing with PostgreSQL databases
graphile-build- the Graphile Build project produces a GraphQL schema via a set of plugins. Website: https://build.graphile.orggraphile-build-pg- teaches Graphile Build how to add schema elements from PostgreSQL resources (tables, functions)@graphile/simplify-inflection- a highly recommended plugin that uses simpler names in GraphQL (but increases the chance of collisions, hence not included in the default experience)graphile-utils- the "bread and butter" plugin factories that help people do common manipulations of a PostGraphile schema with minimal effort
postgraphile- autogenerated and highly extensible/customizeable GraphQL API server/schema generator built using Graphile Build and executed via Grafast. Website: https://postgraphile.orgpgl- a shortcut to thepostgraphilebinary that also takes care of installing all the peerDependencies for you (for compatibility withnpx
- Never edit versioned docs: files under
*/website/versioned_docs/**are snapshots taken at release time. Edit the source (unversioned) doc instead. - Keep scope tight.
- Prefer many small PRs over one large PR.
- Avoid large refactors without an issue/plan.
- Avoid stylistic rewrites without functional gain.
- Avoid moving/renaming files/pages unless essential.
- Avoid changing lockfiles or versions unless essential.
- Edit in stages when performing larger edits; for example: when refactoring, reorder the code first (without changes), commit, then edit the functionality. Many commits are preferred as it allows stepping through the work completed in logical chunks.
- Read files and make changes to markdown, TypeScript, and
*.test.*files. - Write tests; update fixtures and test helpers.
- Improve and update documentation (typically under the
<category>/websitefolder). Be concise, clear, and accurate. Reflect nuances via admonitions where appropriate. Only make essential changes, avoid stylistic changes. - Spot mistakes in the docs where they do not correctly reflect the code.
- Add or adjust comments, JSDoc/TSDoc, README snippets.
- Fix typos in prose.
- Never use
<https://...>-style links, always use[text](https://...). - Links do not end in a slash
- Follow current formatting/lint rules; do not add style-only churn.
- Do not trim file suffixes from links (e.g.
.md/.mdx) - we use Docusaurus to resolve these. - Keep code samples runnable in principle; do not add scripts to run them.
- Wrap prose at 80 characters.
- Always use
Gra*fast*in Markdown when referring to Grafast - the "fast" is stylized in italics. When in MDX use the<Grafast />component if available, otherwiseGra*fast*is fine. - When discussing steps, say a step represents a value at plan-time and yields values at execution-time. Avoid "evaluates"/"resolves" unless quoting legacy APIs.
- Keep Grafast documentation backend-agnostic: do not reference
PostGraphile-specific helpers (e.g.
@dataplan/pg) unless the page is explicitly about that integration. - When adding
:::admonitions, be sure to have a blank line both above and below each:::line. - The documentation uses UK English, but the code uses US English. If a spelling
is valid in both US and UK English, prefer that shared form (e.g.
centralizerather than the UK-onlycentralise,optimizerather thanoptimise). Use UK spellings such asbehaviourin prose, but match US spellings in code identifiers and when quoting API names (e.g.BehaviorPlugin). - SQL statements should be lowercase throughout the code and documentation,
however when keywords such as
SELECTandDELETEare presented alone in prose they should be upper-cased for clarity. - Headings in the sidebar and inline in prose are in Sentence case
- Modal verbs: avoid unnecessary language which suggests some is tentative or unlikely (eg "may", "might"). Graphile follows RFC 2119 for the use of modal verbs.
When writing Grafast plan resolvers in documentation, try and only do one action per statement; for example instead of:
const $invoices = loadMany($customer.get("stripeId"), invoicesByStripId);prefer:
const $stripeId = $customer.get("stripeId");
const $invoices = loadMany($stripeId, invoicesByStripId);This does not apply to literal object or array values passed as the first
argument to lambda(), sideEffect(), loadOne() or loadMany() since these
are "multistep" objects provided for user convenience. Similarly list() should
always be called as list([ ... ]) and object() as object({ ... }) without
separate definition of the argument.
When representing the GraphQL context as a step, const $context = context();
should always be used since it's always the same GraphQL context object being
represented (even if the contents are mutated).
Exception to the above: const $foo = context().get("foo"); is fine, and
preferred over the two statement equivalent.
- Prefer incremental tests: cover public APIs and critical paths first.
- Use existing test utilities and patterns; mirror nearby tests.
- Keep tests deterministic.
Note: many of the integration tests work by writing *.test.graphql files,
which the system will run and capture snapshots of data, plans, errors and
executed SQL. Do not generate these snapshot files, only the .test.graphql
files are needed.
Further note: the test suite uses a lot of different schema configurations (see
the #> comments at the top of *.test.graphql files), so it may be hard to
determine which fields are available. Look for a test in the schemas folder
that has a similar configuration, its snapshot can be a guide, or ask.
Graphile owns the domain err.red (to be read as "errored"), and automatically
routes to the relevant website article based on a single character prefix:
gis for Grafast:https://err.red/g<ERROR_CODE>->https://grafast.org/errors/<ERROR_CODE>(which isgrafast/website/src/pages/errors/<ERROR_CODE>.mdx)- e.g.
https://err.red/gasddredirects tohttps://grafast.org/errors/asddwhich renders the content fromgrafast/website/src/pages/errors/asdd.mdx
- e.g.
pis for PostGraphile:https://err.red/p<ERROR_CODE>->https://postgraphile.org/postgraphile/next/errors/<ERROR_CODE>(which ispostgraphile/website/postgraphile/errors/<ERROR_CODE>.md)
When an error is too complex to explain in 5-6 words, it can be helpful to create an error page in the relevant location and then link to it from the error message.
- Operation plan - how to execute a GraphQL request, comprised of the "execution plan" and the "output plan". Reusable for similar future requests (typically: same document, same operation name, different variables/context).
- Execution plan - the tree of "steps" that need to be executed to calculate the result, and how data flows between them
- Output plan - how to tie the result of steps back into the structure that GraphQL requires we return to the user; importantly after all of the optimization of the execution plan it might not even reflect the shape of the operation any more, and so the Output Plan is vital for restructuring the results to comply with the GraphQL spec.
- Plan resolver - the function defined typically on a GraphQL field (though other types of plan resolver exist) that detail steps sufficient to satisfy the requirements of the
- Step - a single action to perform in a GraphQL request, for example "load
users by ids" or "extract the
.firstNameproperty". Steps are executed batched. The execution plan is formed from a tree of steps. - Unary step - a step that Grafast has determined will always represent exactly one value at run-time.
- Layer plan - every step belongs to exactly one layer plan. Layer plans
represent a boundary that may result in a change in the size of the current
batch, which starts at
1but changes as we traverse the GraphQL operation. For example traversing a list item increases the batch size from the number of lists represented to the number of items across all of the lists represented. Abstract types may create "polymorphic branching" where the previous batch size is now split across many different buckets. Eliminating nulls at a nullable boundary may decrease the batch size. Every layer plan except has a parent layer plan except: the "root" layer plan has no parent, and the "combined" layer plan has multiple layer plans which it can combine. - Bucket - a bucket is the runtime representation of a layer plan, it is where results from the steps in that layer plan are stored during the execution of a single GraphQL request. Layer plans belong to the execution plan and are shared across many executions; buckets belong to an individual request.
- Plan-time - whilst planning is occurring. Planning is synchronous and does not involve any data fetching.
- Execution-time - once the plan has been established it may be executed for each suitable matching request. Each step will be executed according to the execution plan, each step is executed once (with some exceptions) in a batched manner, being fed all the input values and expected to return all the output values. Each step may take as many or as few async operations (promises) as it requires.
- Polymorphic positions - positions in a GraphQL document where an abstract type is referenced and thus the data is likely polymorphic
"Aether" has been replaced with "operation plan".
Plan-time evaluation - when the value of variables/inputs is evaluated at
plan-time. This concept is being eradicated. It's removed from the public
interfaces already, but is still used internally currently, particularly when
evaluating @skip and @include directives. We hope to eliminate it further
over time to avoid plan branching. Users should not need to know about this in
the early parts of the documentation, perhaps only on a caveats-style page.
Plan branching - when plan-time evaluation leads to multiple plans being used for the same GraphQL document + operationName combination.
"Polymorphic types" should generally be replaced with "abstract types" as that's the more technically correct term. The data itself may be polymorphic though.
"Plan" used to be an overloaded term that referred to all the different parts
including "step", "operation plan", "execution plan" and "output plan". In
particular it was frequently used to refer to a "step", and we have a lot of
$plan in the code and docs that should probably be $step instead. Maybe. A
field plan resolver might result in the creation of many steps, and will return
one step. That step can be thought of, however, as the result of planning that
field, so it could be the field plan. We may need crispness here, or maybe not.
"Global dependencies" is now referred to as "Unary dependencies" for more precision.