fix: findByID adjust type to null if disableErrors: true is passed (#8282)

Fixes https://github.com/payloadcms/payload/issues/8280

Now, the result type of this operation:
```ts
const post = await payload.findByID({
  collection: "posts",
  id,
  disableErrors: true
})
```
is `Post | null` instead of `Post` when `disableErrors: true` is passed

Adds test for the `disableErrors` property and docs.
This commit is contained in:
Sasha
2024-09-18 19:39:58 +03:00
committed by GitHub
parent 9821aeb67a
commit 37e1adfa5c
4 changed files with 42 additions and 23 deletions

View File

@@ -77,18 +77,19 @@ Both options function in exactly the same way outside of one having HMR support
You can specify more options within the Local API vs. REST or GraphQL due to the server-only context that they are executed in. You can specify more options within the Local API vs. REST or GraphQL due to the server-only context that they are executed in.
| Local Option | Description | | Local Option | Description |
| ------------------ | ------------ | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `collection` | Required for Collection operations. Specifies the Collection slug to operate against. | | `collection` | Required for Collection operations. Specifies the Collection slug to operate against. |
| `data` | The data to use within the operation. Required for `create`, `update`. | | `data` | The data to use within the operation. Required for `create`, `update`. |
| `depth` | [Control auto-population](../queries/depth) of nested relationship and upload fields. | | `depth` | [Control auto-population](../queries/depth) of nested relationship and upload fields. |
| `locale` | Specify [locale](/docs/configuration/localization) for any returned documents. | | `locale` | Specify [locale](/docs/configuration/localization) for any returned documents. |
| `fallbackLocale` | Specify a [fallback locale](/docs/configuration/localization) to use for any returned documents. | | `fallbackLocale` | Specify a [fallback locale](/docs/configuration/localization) to use for any returned documents. |
| `overrideAccess` | Skip access control. By default, this property is set to true within all Local API operations. | | `overrideAccess` | Skip access control. By default, this property is set to true within all Local API operations. |
| `user` | If you set `overrideAccess` to `false`, you can pass a user to use against the access control checks. | | `user` | If you set `overrideAccess` to `false`, you can pass a user to use against the access control checks. |
| `showHiddenFields` | Opt-in to receiving hidden fields. By default, they are hidden from returned documents in accordance to your config. | | `showHiddenFields` | Opt-in to receiving hidden fields. By default, they are hidden from returned documents in accordance to your config. |
| `pagination` | Set to false to return all documents and avoid querying for document counts. | | `pagination` | Set to false to return all documents and avoid querying for document counts. |
| `context` | [Context](/docs/hooks/context), which will then be passed to `context` and `req.context`, which can be read by hooks. Useful if you want to pass additional information to the hooks which shouldn't be necessarily part of the document, for example a `triggerBeforeChange` option which can be read by the BeforeChange hook to determine if it should run or not. | | `context` | [Context](/docs/hooks/context), which will then be passed to `context` and `req.context`, which can be read by hooks. Useful if you want to pass additional information to the hooks which shouldn't be necessarily part of the document, for example a `triggerBeforeChange` option which can be read by the BeforeChange hook to determine if it should run or not. |
| `disableErrors` | When set to `true`, errors will not be thrown. Instead, the `findByID` operation will return `null`, and the `find` operation will return an empty documents array. |
_There are more options available on an operation by operation basis outlined below._ _There are more options available on an operation by operation basis outlined below._

View File

@@ -6,7 +6,7 @@ import { APIError } from '../../../errors/index.js'
import { createLocalReq } from '../../../utilities/createLocalReq.js' import { createLocalReq } from '../../../utilities/createLocalReq.js'
import { findByIDOperation } from '../findByID.js' import { findByIDOperation } from '../findByID.js'
export type Options<TSlug extends CollectionSlug> = { export type Options<TSlug extends CollectionSlug = CollectionSlug> = {
collection: TSlug collection: TSlug
/** /**
* context, which will then be passed to req.context, which can be read by hooks * context, which will then be passed to req.context, which can be read by hooks
@@ -26,10 +26,14 @@ export type Options<TSlug extends CollectionSlug> = {
user?: Document user?: Document
} }
export default async function findByIDLocal<TSlug extends CollectionSlug>( export default async function findByIDLocal<TOptions extends Options>(
payload: Payload, payload: Payload,
options: Options<TSlug>, options: TOptions,
): Promise<DataFromCollectionSlug<TSlug>> { ): Promise<
TOptions['disableErrors'] extends true
? DataFromCollectionSlug<TOptions['collection']> | null
: DataFromCollectionSlug<TOptions['collection']>
> {
const { const {
id, id,
collection: collectionSlug, collection: collectionSlug,
@@ -50,7 +54,7 @@ export default async function findByIDLocal<TSlug extends CollectionSlug>(
) )
} }
return findByIDOperation<TSlug>({ return findByIDOperation<TOptions['collection']>({
id, id,
collection, collection,
currentDepth, currentDepth,

View File

@@ -217,11 +217,15 @@ export class BasePayload {
* @param options * @param options
* @returns document with specified ID * @returns document with specified ID
*/ */
findByID = async <TSlug extends CollectionSlug>( findByID = async <TOptions extends FindByIDOptions>(
options: FindByIDOptions<TSlug>, options: TOptions,
): Promise<DataFromCollectionSlug<TSlug>> => { ): Promise<
TOptions['disableErrors'] extends true
? DataFromCollectionSlug<TOptions['collection']> | null
: DataFromCollectionSlug<TOptions['collection']>
> => {
const { findByID } = localOperations const { findByID } = localOperations
return findByID<TSlug>(this, options) return findByID<TOptions>(this, options)
} }
findGlobal = async <TSlug extends GlobalSlug>( findGlobal = async <TSlug extends GlobalSlug>(

View File

@@ -1,7 +1,6 @@
import type { Payload } from 'payload' import { randomBytes, randomUUID } from 'crypto'
import { randomBytes } from 'crypto'
import path from 'path' import path from 'path'
import { NotFound, type Payload } from 'payload'
import { fileURLToPath } from 'url' import { fileURLToPath } from 'url'
import type { NextRESTClient } from '../helpers/NextRESTClient.js' import type { NextRESTClient } from '../helpers/NextRESTClient.js'
@@ -1519,6 +1518,17 @@ describe('collections-rest', () => {
expect(result.errors[0].message).toStrictEqual('Something went wrong.') expect(result.errors[0].message).toStrictEqual('Something went wrong.')
}) })
}) })
describe('Local', () => {
it('findByID should throw NotFound if the doc was not found, if disableErrors: true then return null', async () => {
const post = await createPost()
const id = typeof post.id === 'string' ? randomUUID() : 999
await expect(payload.findByID({ collection: 'posts', id })).rejects.toBeInstanceOf(NotFound)
await expect(
payload.findByID({ collection: 'posts', id, disableErrors: true }),
).resolves.toBeNull()
})
})
}) })
async function createPost(overrides?: Partial<Post>) { async function createPost(overrides?: Partial<Post>) {