Skip to content

Latest commit

 

History

History
 
 

graphile-config

graphile-config

GitHub Sponsors Patreon sponsor button Discord chat room Follow

PRERELEASE: this is pre-release software; use at your own risk. This will likely change a lot before it's ultimately released.

graphile-config provides a standard plugin interface and helpers that can be used across the entire of the Graphile suite. Primarily users will only use this as import type Plugin from 'graphile-config'; so that they can export plugins.

This package provides two interfaces: Plugin and Preset (alias Config).

Plugin

A plugin is responsible for adding capabilities to a Graphile package. Each Graphile package will register its own "scope" within the plugin's spec; commonly these scopes may contain capabilities such as 'hooks' or 'events' which this package attempts to standardize.

A Graphile Plugin is an object with the following properties:

  • name (string): The name of the plugin, this must be unique and will be used for capabilities such as skipPlugins
  • version (string): a semver-compliant version for the plugin, this would normally match the version in the package.json but does not need to (e.g. if the module in question contains multiple plugins)
  • description (optional string): human-readable description of the plugin in CommonMark (markdown) format.
  • provides (optional string[]): an optional list of "feature labels" that this plugin provides, this is primarily used to govern the order in which the plugin (and its hooks and events) are executed. Feature labels must be unique within the list of loaded plugins, for example two different plugins should not both provide subscriptions. If unspecified, defaults to the plugin name.
  • after (optional string[]): indicates that this plugin should be loaded after the named features (if present)
  • before (optional string[]): indicates that this plugin should be loaded before the named features (if present)

In addition to the properties above, plugins may also contain properties for each of the supported scopes, for example there may be a postgraphile scope for PostGraphile, or a worker scope for Graphile Worker. The value for each of these scopes will be an object, but the contents of that object are defined by the projects in question.

NOTE: Currently this plugin system is only intended for Graphile usage (and thus we do not need to "reserve" keys), but should you find it useful for other projects please reach out via GitHub issues and we can discuss what's necessary to make this more universal. Should you decide to not heed this advice, please at least make sure that the "scopes" you add are namespaced in a way to avoid future conflicts with features we may wish to add.

Preset

A preset bundles together a list of plugins, and options for various of the "scopes". You may use more than one preset at a time, and presets may also compose (extend) other presets. When a library is passed a list of presets it results in a resolved preset (a preset that has no "extends") using the ResolvePresets algorithm; broadly all the extends are resolved in order, the plugins specified are merged as a set (each plugin will only be included once) and the options are merged via object merging such that the options specified last win.

NOTE: if you compose two presets (PresetA and PresetB) that both extends the same underlying preset (BASE) and apply some overrides, then the overrides in PresetA will be overridden by re-applying the BASE preset again. For this reason, presets that are expected to be combined with other presets should not extends common/shared presets, instead the end-user should be expected to add these presets themselves.

NOTE: the order of presets is significant.

ResolvePresets(presets):

  1. Let {finalPreset} be an empty preset.
  2. For each {preset} in {presets}:
    1. Let {resolvedPreset} be {ResolvePreset(preset)}.
    2. Let {finalPreset} be {MergePreset(finalPreset, resolvedPreset)}.
  3. Return {finalPreset}.

ResolvePreset(preset):

  1. Let {presets} be the list specified in the {extends} property of {preset} (or an empty list if none specified).
  2. Let {basePreset} be {ResolvePresets(presets)}.
  3. Return {MergePreset(basePreset, preset)}.

MergePreset(basePreset, extendingPreset):

  1. Let {finalPreset} be an empty preset.
  2. Assert: {basePreset} has an empty or non-existent {extends} property.
  3. Let {plugins} be the list of plugins defined in {basePreset} union the list of plugins in {extendingPreset}.
  4. Let the list of plugins for {finalPreset} be {plugins}.
  5. Let {scopes} be the list of scopes defined in {basePreset} union the list of scopes in {extendingPreset}.
  6. For each {scope} in {scopes}:
    1. Let {baseScope} be the {scope} in {basePreset}.
    2. Let {extendingScope} be the {scope} in {extendingPreset}.
    3. If {baseScope} and {extendingScope} both exist:
      1. Let {scope} in {finalPreset} be the result of merging {baseScope} and {extendingScope} akin to Object.assign({}, baseScope, extendingScope).
    4. Else: let {scope} in {finalPreset} be whichever of {baseScope} and {extendingScope} actually exist.
  7. Return {finalPreset}.

IMPORTANT: the default name must not be used as a top-level key in a preset to enable compatibility with the various ESM emulations.

Crowd-funded open-source software

To help us develop this software sustainably, we ask all individuals and businesses that use it to help support its ongoing maintenance and development via sponsorship.

And please give some love to our featured sponsors 🤩:

The Guild
The Guild
*
Dovetail
Dovetail
*
Netflix
Netflix
*
Chad Furman
Chad Furman
*
Stellate
Stellate
*
Accenture
Accenture
*
We Love Micro
We Love Micro
*

* Sponsors the entire Graphile suite

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'll 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.