-
-
Notifications
You must be signed in to change notification settings - Fork 614
Extend documentation with a guide for plan resolvers (#244) #2985
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,245 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sidebar_position: 5.5 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: "Best practices" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Best practices for plan resolvers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Plan resolvers are **declarative**: they build a graph of steps at plan-time, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| and Gra*fast* executes that graph later in batches. Keeping this mental model in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mind leads to cleaner, faster plans. This page collects the most important | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recommendations. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## Extract arguments deeply | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| When accessing nested argument values, prefer extracting the leaf value directly | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rather than extracting an intermediate object and then pulling values from it. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| This gives Gra*fast* more information about what you actually need, which | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| enables better optimization. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```graphql | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| input UserFilter { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| author: String | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| publishedAfter: Int | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type Query { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bookCount(search: String, filter: UserFilter): Int! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### Don't: shallow extraction then transform | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function bookCount_plan($parent, fieldArgs) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const $filter = fieldArgs.getRaw("filter"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ✘ Creates an unnecessary intermediate lambda step | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const $author = lambda($filter, (f) => f?.author); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ... | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### Do: deep extraction directly | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function bookCount_plan($parent, fieldArgs) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ✔ One step, directly optimizable | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const $author = fieldArgs.getRaw(["filter", "author"]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const $publishedAfter = fieldArgs.getRaw(["filter", "publishedAfter"]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ... | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| You can also use the `$`-prefixed shortcut for the same result: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function bookCount_plan($parent, fieldArgs) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { $search, $filter } = fieldArgs; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { $author, $publishedAfter } = $filter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ... | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Both `.getRaw()` with a path array and the `$`-prefixed destructuring give | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Gra*fast* direct visibility into exactly which leaf values you need, allowing it | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| to skip unnecessary work and optimize the plan more aggressively. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+13
to
+65
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a major concern before the "eliminate eval" logic came in. It's no longer as important, but probably worth keeping as a best practice anyway - it may become more significant again in future optimizations. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## Prefer custom steps over `lambda` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [`lambda()`](../standard-steps/lambda.md) is an escape hatch — it | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| processes values **one at a time** rather than in batches. This is fine for | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| trivial synchronous transforms (string concatenation, simple math), but for | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| anything more complex you should create a custom step class. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to jump to custom class;
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### Why custom steps are better | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | | `lambda` | Custom step | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |---|---|---| | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | Batching | No — called once per value | Yes — `execute()` receives the full batch | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | Deduplication | Only if callback is the same reference | Full control via `deduplicate()` | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | Optimization | None | Can implement `optimize()` | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | Side effects | Not supported (use `sideEffect()`) | Full control via `hasSideEffects` | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+76
to
+81
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added I've also separated out the side effects point.
Suggested change
This should generally be avoided for the reasons above. ::: |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### When `lambda` is appropriate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Concatenating strings: `lambda([$first, $last], ([f, l]) => \`${f} ${l}\`, true)` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Simple math: `lambda($n, (n) => n + 1, true)` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Trivial data mapping that doesn't benefit from batching | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### When to create a custom step | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - The transform involves I/O or async work — use | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [`loadOne()`](../standard-steps/loadOne.md) / | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [`loadMany()`](../standard-steps/loadMany.md) instead | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - You want deduplication (e.g. multiple fields perform the same transform) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - The logic is non-trivial or would benefit from batching | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+89
to
+95
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### Example: custom step | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { UnbatchedStep } from "grafast"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export class FullNameStep extends UnbatchedStep<string> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static $$export = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| moduleName: "my-app", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exportName: "FullNameStep", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isSyncAndSafe = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constructor($firstName: ExecutableStep<string>, $lastName: ExecutableStep<string>) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| super(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.addDependency($firstName); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.addDependency($lastName); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Steps with identical dependencies are candidates for deduplication | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| deduplicate(peers: FullNameStep[]): FullNameStep[] { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return peers; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| unbatchedExecute(_extra: UnbatchedExecutionExtra, firstName: string, lastName: string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return `${firstName} ${lastName}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function fullName($first: ExecutableStep<string>, $last: ExecutableStep<string>) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new FullNameStep($first, $last); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+99
to
+128
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's not use an UnbatchedStep as an example, because... it's explicitly unbatched. It's very rare a user would ever need an unbatched step (they could just use This would demonstrate the value of building your own step class with helper methods and custom execution. Ref: |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## Define lambda callbacks at file scope | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| If you do use `lambda`, **always define the callback at file/module scope** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (or import it from another file) rather than inline. Gra*fast* deduplicates | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lambda steps by comparing the callback reference — inline arrow functions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| create a new reference on every call, defeating deduplication. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+130
to
+135
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Encourage this for all functions; in particular:
Less importantly:
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### Don't: inline callback | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const objects = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| User: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| plans: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fullName($user) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const $firstName = $user.get("firstName"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const $lastName = $user.get("lastName"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ✘ New function reference every time — cannot be deduplicated | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return lambda([$firstName, $lastName], ([f, l]) => `${f} ${l}`, true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### Do: file-scoped callback | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ✔ Defined once at module scope — same reference every time | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function fullname([firstName, lastName]: [string, string]): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return `${firstName} ${lastName}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const objects = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| User: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| plans: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fullName($user) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const $firstName = $user.get("firstName"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const $lastName = $user.get("lastName"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return lambda([$firstName, $lastName], fullname, true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## Don't use `try`/`catch` in plan resolvers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Plan resolvers run at **plan-time**, not execution-time. They build a | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| declarative graph of steps — think of them like React component render | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| functions where steps are like hooks. Using `try`/`catch` introduces imperative | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| control flow that doesn't fit this model. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+177
to
+180
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### Why it doesn't work | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Plan resolvers don't execute your data-fetching logic — they only | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| **describe** it. A `try` block around step creation doesn't catch runtime | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data errors because those errors happen later, during execution. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Wrapping step creation in `try`/`catch` can mask plan-time programming | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| errors that should be fixed, not caught. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - It suggests a misunderstanding of the plan/execute separation. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really like this last bullet point. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### Don't: try/catch around steps | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ✘ This try/catch is meaningless — runtime errors happen during execution, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // not during planning | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function post_author_plan($post) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const $authorId = $post.get("authorId"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return loadOne($authorId, batchGetAuthorById); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return constant(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### Do: use flow control steps | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think most users should only use flow control steps in rare occasions.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Gra*fast* provides declarative flow control for handling errors and null values | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| at execution-time: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { loadOne, trap, inhibitOnNull, TRAP_ERROR } from "grafast"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function post_author_plan($post) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const $authorId = $post.get("authorId"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Guard against null authorId — skip the load entirely | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const $guardedId = inhibitOnNull($authorId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Load the author; if it errors, convert to null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const $author = loadOne($guardedId, batchGetAuthorById); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return trap($author, TRAP_ERROR); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Be more explicit about what we're doing here:
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| The key flow control steps are: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - [`inhibitOnNull()`](../standard-steps/inhibitOnNull.mdx) — suppresses | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| downstream work when a value is `null` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - [`assertNotNull()`](../standard-steps/assertNotNull.mdx) — turns | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `null` into a `SafeError` visible to clients | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - [`trap()`](../standard-steps/trap.mdx) — recovers inhibited or errored | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| values back into ordinary data (e.g. `null` or an empty list) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+232
to
+233
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| See [Thinking in plans: Flow control](../flow.mdx#flow-control) for more | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| details on when and how to use these. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## Summary | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | Recommendation | Why | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |---|---| | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | [Extract arguments deeply](#extract-arguments-deeply) | Fewer intermediate steps, better optimization | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | [Prefer custom steps over `lambda`](#prefer-custom-steps-over-lambda) | Batching, deduplication, optimization | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | [File-scoped lambda callbacks](#define-lambda-callbacks-at-file-scope) | Enables deduplication | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | [No `try`/`catch`](#dont-use-trycatch-in-plan-resolvers) | Plan resolvers are declarative; use flow control steps | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -25,6 +25,15 @@ as steps to be populated at execution time for each request. | |||||||||
|
|
||||||||||
| ::: | ||||||||||
|
|
||||||||||
| :::tip[Best practices] | ||||||||||
|
|
||||||||||
| For recommendations on writing efficient and correct plan resolvers — | ||||||||||
| including deep argument extraction, when to use custom steps vs `lambda`, | ||||||||||
| and how to handle errors declaratively — see the | ||||||||||
|
Comment on lines
+31
to
+32
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| [Best practices](./best-practices.md) guide. | ||||||||||
|
|
||||||||||
| ::: | ||||||||||
|
|
||||||||||
| ## Field plan resolvers | ||||||||||
|
|
||||||||||
| ```ts | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -80,6 +80,22 @@ not do this unless you are certain! | |
|
|
||
| ::: | ||
|
|
||
| ## When to use something else | ||
|
|
||
| Before reaching for `lambda`, consider whether a better tool exists: | ||
|
|
||
| - **I/O or async work** → use [`loadOne()`](./loadOne.md) or | ||
| [`loadMany()`](./loadMany.md) which support batching | ||
| - **Non-trivial transforms that appear in multiple fields** → create a | ||
| [custom step class](../step-classes.mdx) with `deduplicate()` support | ||
| - **Side effects** → use [`sideEffect()`](/grafast/standard-steps/sideEffect) | ||
|
|
||
| `lambda` is best reserved for trivial, synchronous, pure transforms such as | ||
| string concatenation or simple arithmetic. | ||
|
|
||
| See [Plan resolver best practices](../plan-resolvers/best-practices.md) for | ||
| more guidance. | ||
|
|
||
|
Comment on lines
+83
to
+98
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We already have two notes on this page warning about lambda not batching; perhaps this should be folded into those? |
||
| ## Warning: no batching! | ||
|
|
||
| **`lambda` is an escape hatch** that breaks you out of Gra*fast*'s batching; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not super keen on this; I'd rather handle it with a lint rule later. The main reason is that just not having a name is not sufficient to know that it was inline; for example we recommend
EXPORTABLE(...)usage everywhere; however:This'll output
''since there's no name for this function.