The Crystal Programming Language
<!-- prettier-ignore -->
# AGENTS.md
**Audience:** code assistants (Aider, Claude, Codex, Cody, Copilot, etc.)
working on this repository.
## Repository Overview
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 strings
- `graphile-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.org
- `grafserv` - 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.org
- `graphile-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.org
- `pgl` - a shortcut to the `postgraphile` binary that also takes care of
installing all the peerDependencies for you (for compatibility with `npx`
## Guardrails
1. **Never edit versioned docs:** files under `*/website/versioned_docs/**` are
snapshots taken at release time. Edit the source (unversioned) doc instead.
1. **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.
1. **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.
## Always Allowed
- 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>/website`
folder). 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.
## Preferred Style
- 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 Gra*fast* - the "fast" is
stylized in italics. When in MDX use the `<Grafast />` component if available,
otherwise `Gra*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. `centralize`
rather than the UK-only `centralise`, `optimize` rather than `optimise`). Use
UK spellings such as `behaviour` in 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 `SELECT` and `DELETE` are 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:
```ts
const $invoices = loadMany($customer.get("stripeId"), invoicesByStripId);
```
prefer:
```ts
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.
When writing plan resolvers against PostgreSQL resources, prefer the resource
and step helpers before constructing low-level plans by hand. For example, use
argument destructuring such as `(_$root, { $name })`, `users.find()`,
`$users.where(...)`, and `$users.placeholder($name, TYPES.text)` where they fit,
rather than building a custom `pgSelect({ identifiers, args, from })` wrapper.
Reach for low-level constructors only when the resource/step APIs do not express
the query shape.
## Testing Rules
- 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.
## Error pages
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:
- `g` is for Grafast: `https://err.red/g<ERROR_CODE>` ->
`https://grafast.org/errors/<ERROR_CODE>` (which is
`grafast/website/src/pages/errors/<ERROR_CODE>.mdx`)
- e.g. `https://err.red/gasdd` redirects to `https://grafast.org/errors/asdd`
which renders the content from `grafast/website/src/pages/errors/asdd.mdx`
- `p` is for PostGraphile: `https://err.red/p<ERROR_CODE>` ->
`https://postgraphile.org/postgraphile/next/errors/<ERROR_CODE>` (which is
`postgraphile/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.
## Glossary
### Grafast
- 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 `.firstName` property". Steps are executed
batched. The execution plan is formed from a tree of steps.
- Unary step - a step that Gra*fast* 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 `1` but 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
## Out of date terminology and concepts
"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.