Skip to content

Commit dd081e8

Browse files
authored
graphile config doc overhaul (#2282)
2 parents b9c4e89 + 0a62056 commit dd081e8

File tree

10 files changed

+881
-420
lines changed

10 files changed

+881
-420
lines changed

utils/graphile-config/src/functionality.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export function orderedApply<
5151
after: (keyof GraphileConfig.Plugins | keyof GraphileConfig.Provides)[];
5252
callback: UnwrapCallback<TFunctionality[keyof TFunctionality]>;
5353
};
54-
// Normalize all the hooks and gather them into collections
54+
// Normalize all the plugin "functionalities" and gather them into collections
5555
const allFunctionalities: {
5656
[key in keyof TFunctionality]?: Array<FullFunctionalitySpec>;
5757
} = Object.create(null);

utils/website/graphile-config/index.md

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,34 @@ sidebar_position: 1
55
# Graphile Config
66

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

10-
`graphile-config` provides a standard plugin interface and helpers that can be
11-
used across the entire of the Graphile suite. Primarily users will only use this
12-
as `import type Plugin from 'graphile-config';` so that they can export plugins.
10+
Graphile Config helps Node.js library authors make their libraries configurable
11+
and _extensible_. Graphile Config is used across the Graphile suite to provide a
12+
standard configuration and plugin interface.
1313

14-
This package provides two interfaces: `Plugin` and `Preset`
14+
## Features
1515

16-
## Supporting TypeScript ESM
16+
- Define and document strongly typed configuration options for your library.
17+
- Allow users to extend the functionality of your library via plugins.
18+
- Plugins can add their own additional configuration options.
19+
- Bundle configuration options and plugins into default presets for your users.
20+
- You and your users can compose presets with preset extension.
21+
- Allow your users to share configuration across multiple modes (e.g. CLI and library).
22+
- Powerful middleware system to make your library extensible.
23+
- Users don't need to put plugins in a particular order, thanks to the ordering system.
24+
- View the available options and resolved values of a preset with the `graphile`
25+
CLI
26+
([available to sponsors](https://github.com/graphile/crystal/blob/main/utils/graphile/README.md)).
1727

18-
You can specify a `graphile.config.ts` file; but if that uses `export default`
19-
and your TypeScript is configured to export ESM then you'll get an error telling
20-
you that you cannot `require` an ES Module:
28+
## Different Users
2129

22-
```js
23-
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /path/to/graphile.config.ts
24-
require() of ES modules is not supported.
25-
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.
26-
Instead change the requiring code to use import(), or remove "type": "module" from /path/to/package.json.
27-
```
30+
As a user of Graphile Config, you may not need to understand everything. There
31+
are three common levels of usage, in order of the amount of knowledge required:
2832

29-
Or, in newer versions, an error saying unknown file extension:
33+
1. Library consumers ⚙️
34+
2. Plugin authors 🔌
35+
3. Library authors 📚
3036

31-
```js
32-
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /path/to/graphile.config.ts
33-
```
34-
35-
To solve this, use the experimental loaders API to add support for TS ESM via
36-
the `ts-node/esm` loader:
37-
38-
```js
39-
export NODE_OPTIONS="$NODE_OPTIONS --loader ts-node/esm"
40-
```
41-
42-
Then run your command again.
37+
Each section in the Graphile Config docs will indicate the intended audience.
38+
Feel free to learn only what you need, or learn it all!
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
---
2+
sidebar_position: 4
3+
title: "Library Authors"
4+
---
5+
6+
# Library Authors
7+
8+
Graphile Config is currently only used by Graphile projects. Should you find it
9+
useful for other projects, please reach out via GitHub issues and we can discuss
10+
what is necessary to make this more universal. Should you decide to not heed
11+
this advice, please at least make sure that the scopes you add are named to
12+
reduce the likelihood of future conflicts with features we may wish to add.
13+
14+
## Naming scopes
15+
16+
By convention, scopes are camelCase strings. Scopes should be descriptive enough
17+
to reduce the chance of either conflicts across libraries or conflicts with
18+
future additions to Graphile Config.
19+
20+
<details>
21+
22+
<summary>Click for specifically reserved scope names</summary>
23+
24+
The following strings are reserved by Graphile Config, and should not be used as
25+
preset scopes or plugin scopes:
26+
27+
- Anything beginning with an underscore (`_`)
28+
- after
29+
- appendPlugins (to avoid confusion with PostGraphile v4 plugins)
30+
- before
31+
- callback
32+
- description
33+
- default (to enable compatibility with the various ESM emulations)
34+
- disablePlugins
35+
- experimental
36+
- export
37+
- exports
38+
- extend
39+
- extends
40+
- functionality
41+
- id
42+
- import
43+
- imports
44+
- include
45+
- includes
46+
- label
47+
- name
48+
- plugin
49+
- plugins
50+
- prependPlugins (to avoid confusion with PostGraphile v4 plugins)
51+
- provides
52+
- skipPlugins (to avoid confusion with PostGraphile v4 plugins)
53+
- title
54+
55+
</details>
56+
57+
## Middleware
58+
59+
(This section was primarily written by Benjie for Benjie... so you may want to
60+
skip it.)
61+
62+
If you need to create a middleware system for your library, you might follow
63+
something along these lines (replacing `libraryName` with the name of your
64+
library):
65+
66+
```ts title="src/interfaces.ts"
67+
// Define the middlewares that you support, their event type and their return type
68+
export interface MyMiddleware {
69+
someAction(event: SomeActionEvent): PromiseOrDirect<SomeActionResult>;
70+
}
71+
interface SomeActionEvent {
72+
someParameter: number;
73+
/*
74+
* Use a per-middleware-method interface to define the various pieces of
75+
* data relevant to this event. **ALWAYS** use the event as an abstraction
76+
* so that new information can be added in future without causing any
77+
* knock-on consequences. Note that these parameters of the event may be
78+
* mutated. The values here can be anything, they don't need to be simple
79+
* values.
80+
*/
81+
}
82+
// Middleware wraps a function call; this represents whatever the function returns
83+
type SomeActionResult = number;
84+
85+
export type PromiseOrDirect<T> = Promise<T> | T;
86+
```
87+
88+
```ts title="src/index.ts"
89+
import type { MiddlewareHandlers } from "graphile-config";
90+
91+
// Extend Plugin with support for registering handlers for the middleware activities:
92+
declare global {
93+
namespace GraphileConfig {
94+
interface Plugin {
95+
libraryName?: {
96+
middleware?: MiddlewareHandlers<MyMiddleware>;
97+
};
98+
}
99+
}
100+
}
101+
```
102+
103+
```ts title="src/getMiddleware.ts"
104+
import { Middleware, orderedApply, resolvePreset } from "graphile-config";
105+
106+
export function getMiddleware(resolvedPreset: GraphileConfig.ResolvedPreset) {
107+
// Create your middleware instance. The generic describes the events supported
108+
const middleware = new Middleware<MyMiddleware>();
109+
// Now apply the relevant middlewares registered by each plugin (if any) to the
110+
// Middleware instance
111+
orderedApply(
112+
resolvedPreset.plugins,
113+
(plugin) => plugin.libraryName?.middleware,
114+
(name, fn, _plugin) => {
115+
middleware.register(name, fn as any);
116+
},
117+
);
118+
}
119+
```
120+
121+
```ts title="src/main.ts"
122+
// Get the user's Graphile Config from somewhere, e.g.
123+
import config from "./graphile.config.js";
124+
125+
// Resolve the above config, recursively applying all the presets it extends from
126+
const resolvedPreset = resolvePreset(config);
127+
128+
// Get the middleware for this preset
129+
const middleware = getMiddleware(resolvedPreset);
130+
131+
// Then in the relevant place in your code, call the middleware around the
132+
// relevant functionality
133+
const result = await middleware.run(
134+
"someAction",
135+
{ someParameter: 42 }, // < `event` object
136+
async (event) => {
137+
// Note: `event` will be the same object as above, but its contents may
138+
// have been modified by middlewares.
139+
const { someParameter } = event;
140+
141+
// Call the underlying method to perform the action.
142+
return await someAction(someParameter);
143+
},
144+
);
145+
// The value of `result` should match the return value of `someAction(...)`
146+
// (unless a middleware tweaked or replaced it, of course!)
147+
148+
// This is the thing that your middleware wraps. It can do anything, it's just
149+
// an arbitrary JavaScript function.
150+
function someAction(someParameter: number): PromiseOrDirect<SomeActionResult> {
151+
// Do something here...
152+
if (Math.random() < 0.5) {
153+
return someParameter;
154+
} else {
155+
return sleep(200).then(() => someParameter);
156+
}
157+
}
158+
```

0 commit comments

Comments
 (0)