GraphileConfig.Preset
Target Audience: all Graphile Config users ⚙️🔌📚
A preset bundles together a list of options and plugins to configure and extend
a library. GraphileConfig.Preset is the primary interface that most Graphile
Config users will use.
Library consumers should define their preset as the default export of a
graphile.config.js (or .ts, .mjs, etc.) file.
Here's an example in JavaScript:
const SomePlugin = require("some-plugin");
module.exports = {
plugins: [SomePlugin],
someScope: {
someConfigOption: 10,
},
};
And an equivalent configuration in TypeScript:
import type {} from "graphile-config";
import SomePlugin from "some-plugin";
const preset: GraphileConfig.Preset = {
plugins: [SomePlugin],
someScope: {
someConfigOption: 10,
},
};
export default preset;
Adding the import type {} from "graphile-config" statement tells TypeScript
about the GraphileConfig global namespace; this may or may not be necessary
depending on the other imports that you have, but we generally recommend it.
No code from the graphile-config library will be included in the output
JavaScript for graphile.config.ts above; see the TypeScript docs for more on
type-only imports.
Supporting TypeScript ESM
You can specify a graphile.config.ts file, but if that uses export default,
and your TypeScript is configured to export ESM, then you may get an error
telling you that you cannot require an ES Module:
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /path/to/graphile.config.ts
require() of ES modules is not supported.
require() of /path/to/graphile.config.ts from /path/to/node_modules/graphile-config/dist/loadConfig.js is an ES module file as it is a .ts file whose nearest parent package.json contains "type": "module" which defines all .ts files in that package scope as ES modules.
Instead change the requiring code to use import(), or remove "type": "module" from /path/to/package.json.
Or, in newer versions, an error saying unknown file extension:
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /path/to/graphile.config.ts
To solve this, use the experimental loaders API to add support for TS ESM via
the ts-node/esm loader:
export NODE_OPTIONS="$NODE_OPTIONS --loader ts-node/esm"
Then run your command again.
Preset scopes
Libraries that use Graphile Config define scopes. Scopes are properties in a
Graphile Config preset within which configuration options can be set. For
example, PostGraphile's dependencies define scopes
including pgServices, schema, grafast, grafserv, and more.
Graphile Worker defines a worker scope.
Scopes are also used in plugins. Plugin authors 🔌 should see Plugin for more details.
Preset composition
Library authors can create Graphile Config presets that allow consumers to
easily use the library with default or recommended settings. The extends
property in a preset allows library consumers to extend one or more presets with
their own configuration values or plugins. Extending presets resolves values
according to the preset resolution algorithm.
The following is a simple example of a Graphile Config preset that could be used to configure usage of Graphile Worker:
import { WorkerPreset } from "graphile-worker";
const preset: GraphileConfig.Preset = {
extends: [WorkerPreset],
worker: {
connectionString: "postgres:///my_db",
},
};
export default preset;
As a library consumer, you can build and share presets with your collaborators or even extend 3rd party presets provided by the community. As with any 3rd party code, take caution to ensure you can trust the code you are importing.
Preset resolution
Presets may compose (extend) zero or more other presets. When a library is
passed a preset, it resolves the preset using the ResolvePreset algorithm
below.
TL;DR:
- All the presets in
extendsare resolved, recursively, depth-first, in order (order is important!). - The plugins are merged as a set (each plugin will only be included once) and
sorted according to
before/after. - The options are merged such that options specified last win.
ResolvePreset(preset):
- Let
flattenedPresetbeFlattenPreset(preset). - Let
resolvedPresetbe a copy offlattenedPresetwith thepluginsproperty sorted according to the plugin ordering rules. - Return
resolvedPreset.
FlattenPreset(preset):
- Let
extendsbe the list specified in theextendsproperty ofpreset(or an empty list if none specified). - Let
extendsPresetbeMergePresets(extends). - Return
ExtendPreset(preset, extendsPreset).
MergePresets(presets):
- Let
mergedPresetbe an empty preset. - For each
presetinpresets:- Let
flattenedPresetbeFlattenPreset(preset). - Let
mergedPresetbeExtendPreset(mergedPreset, flattenedPreset).
- Let
- Return
mergedPreset.
ExtendPreset(basePreset, extensionPreset):
- Note: this algorithm ignores the
extendsproperty of bothbasePresetandextensionPreset, assuming they has already been resolved elsewhere. - Let
mergedPresetbe an empty preset. - Let
pluginsbe the unique list of plugins defined inbasePresetfollowed those defined inextensionPresetand not already defined inbasePreset. - Let the list of plugins in
mergedPresetbeplugins. - Let
scopesbe the set of scopes defined inbasePresetunion those defined inextensionPreset. - For each
scopeinscopes:- Let
baseScopebe the value of thescopeinbasePreset. - Let
extendingScopebe the value of thescopeinextensionPreset. - If
baseScopeandextendingScopeboth exist:- Let
scopeinmergedPresetbe the result of mergingbaseScopeandextendingScopeakin toObject.assign({}, baseScope, extendingScope).
- Let
- Otherwise: let
scopeinmergedPresetbe whichever ofbaseScopeandextendingScopeactually exist.
- Let
- Return
mergedPreset.
When extending presets, Graphile Config merges options scopes with logic
equivalent to Object.assign({}, baseScope, extendingScope). This has the
following implications:
- When an option is set to
undefinedornullin the extending preset, it will override the value in the base preset. Depending on the library behavior, this may result in the library default being used for the option. - When an option is not present in the extending scope, the resolved value will be that of the option in the base scope (if any).
Consider a preset, APreset, that extends two other presets: Preset1 and Preset2,
each of which extends the same preset, Preset0:
const Preset0 = { myScope: { option1: false, option2: false } };
const Preset1 = { extends: [Preset0], myScope: { option1: true } };
const Preset2 = { extends: [Preset0], myScope: { option2: true } };
const APreset = { extends: [Preset1, Preset2] };
Any overrides to the options set in Preset0 by Preset1 will be reset in APreset since they will be overridden when Preset2 applies the Preset0 options again:
// Resolving the presets operates depth first:
const Preset0 = { myScope: { option1: false, option2: false } };
const Preset1 = { myScope: { ...Preset0.myScope, option1: true } };
const Preset2 = { myScope: { ...Preset0.myScope, option2: true } };
const APreset = { myScope: { ...Preset1.myScope, ...Preset2.myScope } };
// Thus:
const APreset = { myScope: { option1: false, option2: true } };
For this reason, presets that are expected to be combined with other presets
should not extends common or shared presets; instead, the end user should be
expected to add these presets themselves.
Said another way, the tree of presets should only ever have a depth of 2 or less to avoid unexpected behavior.
graphile CLI
graphile CLI!Graphile's open source projects only exist thanks to the sponsorship of
individuals and organizations that use them. To help convince your boss to fund
the ongoing development of the OSS that your company relies on, sponsorship
comes with perks. One such perk is the graphile CLI which is only licensed for
usage by sponsors. See the
README
for more details.
The graphile CLI has a config subcommand with various tools to help library
consumers.
# Output the options your config may contain
npx graphile config options
# Print your resolved configuration
npx graphile config print