diff --git a/grafast/website/grafast/step-library/standard-steps/loadMany.md b/grafast/website/grafast/step-library/standard-steps/loadMany.md index 555403de6..6c2170e1d 100644 --- a/grafast/website/grafast/step-library/standard-steps/loadMany.md +++ b/grafast/website/grafast/step-library/standard-steps/loadMany.md @@ -161,7 +161,7 @@ function callback( For optimal results, we strongly recommend that the callback function is defined in a common location so that it can be reused over and over again, rather than -defined inline. This will allow `LoadManyStep` to optimize calls to this function. +defined inline. This will allow the underlying steps to optimize calls to this function. ::: @@ -209,8 +209,6 @@ const friendshipsByUserIdCallback = (ids, { attributes }) => { }; ``` -[dataloader]: https://github.com/graphql/dataloader - ### Unary step usage (a step that only ever represents one value, e.g. simple derivatives of `context()`, `fieldArgs`, or `constant()`) @@ -307,3 +305,5 @@ required - please track this issue: https://github.com/graphile/crystal/issues/2170 ::: + +[dataloader]: https://github.com/graphql/dataloader diff --git a/grafast/website/grafast/step-library/standard-steps/loadOne.md b/grafast/website/grafast/step-library/standard-steps/loadOne.md index 838fab4f7..714a3442b 100644 --- a/grafast/website/grafast/step-library/standard-steps/loadOne.md +++ b/grafast/website/grafast/step-library/standard-steps/loadOne.md @@ -122,63 +122,81 @@ loadOne($spec, [$unaryStep,] [ioEquivalence,] callback) which records to load (the _specifier step_), the last is the callback function called with these specs responsible for loading them. -The callback function is called with two arguments, the first is a list of the -values from the _specifier step_ and the second is options that may affect the -fetching of the records. +```ts +// Basic usage: +const $record = loadOne($spec, callback); + +// Advanced usage: +const $record = loadOne($spec, $unaryStep, ioEquivalence, callback); +const $record = loadOne($spec, $unaryStep, callback); +const $record = loadOne($spec, ioEquivalence, callback); +``` + +Where: + +- `$spec` is any step +- `$unaryStep` is any _unary_ step - see [Unary step usage](#unary-step-usage) below +- `ioEquivalence` is either `null`, a string, an array of strings, or a string-string object map - see [ioEquivalence usage](#ioequivalence-usage) below +- and `callback` is a callback function responsible for fetching the data. + +### Callback + +The `callback` function is called with two arguments, the first is +a list of the values from the _specifier step_ `$spec` and the second is options that +may affect the fetching of the records. + +```ts +function callback( + specs: ReadonlyArray, + options: { + unary: unknown; + attributes: ReadonlyArray; + params: Record; + }, +): PromiseOrDirect>; +``` :::tip For optimal results, we strongly recommend that the callback function is defined in a common location so that it can be reused over and over again, rather than -defined inline. This will allow `LoadOneStep` to optimize calls to this function. +defined inline. This will allow the underlying steps to optimize calls to this function. ::: -Optionally a penultimate argument (2nd of 3 arguments, or 3rd of 4 arguments) -can indicate the input/output equivalence - this can be: +Within this definition of `callback`: -- `null` to indicate no input/output equivalence -- a string to indicate that the same named property on the output is equivalent - to the entire input plan -- if the step is a `list()` (or similar) plan, an array containing a list of - keys (or null for no relation) on the output that are equivalent to the same - entry in the input -- if the step is a `object()` (or similar) plan, an object that maps between - the attributes of the object and the key(s) in the output that are equivalent - to the given entry on the input +- `specs` is the runtime values of each value that `$spec` represented +- `options` is an object containing: + - `unary`: the runtime value that `$unaryStep` (if any) represented + - `attributes`: the list of keys that have been accessed via + `$record.get('')` for each of the records in `$records` + - `params`: the params set via `$records.setParam('', )` -```ts title="Example for a list step" -const $member = loadOne( - list([$organizationId, $userId]), - ["organization_id", "user_id"], - batchGetMemberByOrganizationIdAndUserId, -); +`specs` is deduplicated using strict equality; so it is best to keep `$spec` +simple - typically it should only represent a single scalar value - which is +why `$unaryStep` exists. -// - batchGetMemberByOrganizationIdAndUserId will be called with a list of -// 2-tuples, the first value in each tuple being the organizationId and the -// second the userId. -// - Due to the io equivalence (2nd argument): -// - `$member.get("organization_id")` will return `$organizationId` directly -// - `$member.get("user_id")` will return `$userId` directly -``` +`options.unary` is very useful to keep specs simple (so that fetch +deduplication can work optimally) whilst passing in global values that you may +need such as a database or API client. -```ts title="Example for an object step" -const $member = loadOne( - object({ oid: $organizationId, uid: $userId }), - { oid: "organization_id", uid: "user_id" }, - batchGetMemberByOrganizationIdAndUserId, -); +`options.attributes` is useful for optimizing your fetch - e.g. if the user +only ever requested `$record.get('id')` and `$record.get('avatarUrl')` then +there's no need to fetch all the other attributes from your datasource. -// - batchGetMemberByOrganizationIdAndUserId will be called with a list of -// objects; each object will have the key `oid` set to an organization id, -// and the key `uid` set to the user ID. -// - Due to the io equivalence (2nd argument): -// - `$member.get("organization_id")` will return the step used for `oid` -// (i.e. `$organizationId`) directly -// - Similarly `$member.get("user_id")` will return `$userId` directly -``` +`options.params` can be used to pass additional context to your callback +function, perhaps options like "should we include archived records" or "should +we expand 'customer' into a full object rather than just returning the +identifier". -#### Example callback +### Basic usage + +```ts +const $userId = $post.get("author_id"); +const $user = loadOne($userId, batchGetUserById); +// OR: const $user = loadOne($userId, 'id', batchGetUserById); +``` An example of the callback function might be: @@ -199,7 +217,15 @@ async function batchGetUserById(ids, { attributes }) { } ``` -### Advanced usage +### Unary step usage + +(a step that only ever represents one value, e.g. simple derivatives of `context()`, `fieldArgs`, or `constant()`) + +In addition to the forms seen in "Basic usage" above, you can pass a second +step to `loadOne`. This second step must be a [**unary +step**](../../step-classes.md#addUnaryDependency), meaning that it must represent +exactly one value across the entire request (not a batch of values like most +steps). ```ts const $userId = $post.get("author_id"); @@ -208,11 +234,7 @@ const $user = loadOne($userId, $dbClient, "id", batchGetUserFromDbById); // OR: const $user = loadOne($userId, $dbClient, batchGetUserFromDbById); ``` -In addition to the forms seen in "Basic usage" above, you can pass a second -step to `loadOne`. This second step must be a [**unary -step**](../../step-classes.md#addUnaryDependency), meaning that it must represent -exactly one value across the entire request (not a batch of values like most -steps). Since we know it will have exactly one value, we can pass it into the +Since we know it will have exactly one value, we can pass it into the callback as a single value and our callback will be able to use it directly without having to perform any manual grouping. @@ -220,8 +242,6 @@ This unary dependency is useful for fixed values (for example, those from GraphQL field arguments) and values on the GraphQL context such as clients to various APIs and other data sources. -#### Example callback (advanced) - An example of the callback function might be: ```ts @@ -237,7 +257,52 @@ async function batchGetUserFromDbById(ids, { attributes, unary }) { } ``` -## Multiple steps +### ioEquivalence usage + +The `ioEquivalence` optional parameter can accept the following values: + +- `null` to indicate no input/output equivalence +- a string to indicate that the same named property on the output is equivalent + to the entire input plan +- if the step is a `list()` (or similar) plan, an array containing a list of + keys (or null for no relation) on the output that are equivalent to the same + entry in the input +- if the step is a `object()` (or similar) plan, an object that maps between + the attributes of the object and the key(s) in the output that are equivalent + to the given entry on the input + +```ts title="Example for a list step" +const $member = loadOne( + list([$organizationId, $userId]), + ["organization_id", "user_id"], + batchGetMemberByOrganizationIdAndUserId, +); + +// - batchGetMemberByOrganizationIdAndUserId will be called with a list of +// 2-tuples, the first value in each tuple being the organizationId and the +// second the userId. +// - Due to the io equivalence (2nd argument): +// - `$member.get("organization_id")` will return `$organizationId` directly +// - `$member.get("user_id")` will return `$userId` directly +``` + +```ts title="Example for an object step" +const $member = loadOne( + object({ oid: $organizationId, uid: $userId }), + { oid: "organization_id", uid: "user_id" }, + batchGetMemberByOrganizationIdAndUserId, +); + +// - batchGetMemberByOrganizationIdAndUserId will be called with a list of +// objects; each object will have the key `oid` set to an organization id, +// and the key `uid` set to the user ID. +// - Due to the io equivalence (2nd argument): +// - `$member.get("organization_id")` will return the step used for `oid` +// (i.e. `$organizationId`) directly +// - Similarly `$member.get("user_id")` will return `$userId` directly +``` + +### Passing multiple steps The [`list()`](./list) or [`object()`](./object) step can be used if you need to pass the value of more than one step into your callback: @@ -271,4 +336,13 @@ async function getLast4FromStripeIfAdmin(tuples) { This technique can also be used with the unary step in advanced usage. +:::tip Performance impact from using list/object + +Using `list()` / `object()` like this will likely reduce the effectiveness of +`loadMany`'s built in deduplication; to address this a stable object/list is +required - please track this issue: +https://github.com/graphile/crystal/issues/2170 + +::: + [dataloader]: https://github.com/graphql/dataloader