Skip to content

Commit

Permalink
feat: depth generic, fully type safe result types for relationships
Browse files Browse the repository at this point in the history
  • Loading branch information
r1tsuu committed Dec 6, 2024
1 parent 1ab3be6 commit a245bf7
Show file tree
Hide file tree
Showing 28 changed files with 642 additions and 179 deletions.
11 changes: 6 additions & 5 deletions docs/configuration/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,12 @@ export default buildConfig({

The following options are available:

| Option | Description |
| --------------- | --------------------- |
| **`autoGenerate`** | By default, Payload will auto-generate TypeScript interfaces for all collections and globals that your config defines. Opt out by setting `typescript.autoGenerate: false`. [More details](../typescript/overview). |
| **`declare`** | By default, Payload adds a `declare` block to your generated types, which makes sure that Payload uses your generated types for all Local API methods. Opt out by setting `typescript.declare: false`. |
| **`outputFile`** | Control the output path and filename of Payload's auto-generated types by defining the `typescript.outputFile` property to a full, absolute path. |
| Option | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`autoGenerate`** | By default, Payload will auto-generate TypeScript interfaces for all collections and globals that your config defines. Opt out by setting `typescript.autoGenerate: false`. [More details](../typescript/overview). |
| **`declare`** | By default, Payload adds a `declare` block to your generated types, which makes sure that Payload uses your generated types for all Local API methods. Opt out by setting `typescript.declare: false`. |
| **`outputFile`** | Control the output path and filename of Payload's auto-generated types by defining the `typescript.outputFile` property to a full, absolute path. |
| **`typeSafeDepth`** | Enable better result types for relationships depending on `depth`, disabled by default. [More Details](../queries/depth#type-safe-relationship-types-with-depth). |

## Config Location

Expand Down
16 changes: 16 additions & 0 deletions docs/queries/depth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,19 @@ To set a max depth for a field, use the `maxDepth` property in your field config
]
}
```

## Type safe relationship types with depth

The `typescript.typeSafeDepth` config option allows you to have better result types with relationships for your operations.
For example:
```ts
const post = await payload.findByID({
collection: 'posts',
id,
depth: 1
})

const category = post.category
```
You may notice that `post.category` is typed as `Category | string` (or `Category | number` with number IDs) which isn't quite right since we passed `depth: 1`.
With `typescript.typeSafeDepth: true` this type will be resolved as just `Category` and in case of `depth: 0` - `string`.
17 changes: 11 additions & 6 deletions packages/payload/src/auth/operations/local/login.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
import type {
AllowedDepth,
AuthOperationsFromCollectionSlug,
CollectionSlug,
DataFromCollectionSlug,
DefaultDepth,
Payload,
RequestContext,
} from '../../../index.js'
import type { PayloadRequest } from '../../../types/index.js'
import type { ApplyDepth, PayloadRequest } from '../../../types/index.js'
import type { Result } from '../login.js'

import { APIError } from '../../../errors/index.js'
import { createLocalReq } from '../../../utilities/createLocalReq.js'
import { loginOperation } from '../login.js'

export type Options<TSlug extends CollectionSlug> = {
export type Options<TSlug extends CollectionSlug, TDepth extends AllowedDepth = DefaultDepth> = {
collection: TSlug
context?: RequestContext
data: AuthOperationsFromCollectionSlug<TSlug>['login']
depth?: number
depth?: TDepth
fallbackLocale?: string
locale?: string
overrideAccess?: boolean
req?: PayloadRequest
showHiddenFields?: boolean
}

export async function localLogin<TSlug extends CollectionSlug>(
export async function localLogin<
TSlug extends CollectionSlug,
TDepth extends AllowedDepth = DefaultDepth,
>(
payload: Payload,
options: Options<TSlug>,
): Promise<{ user: DataFromCollectionSlug<TSlug> } & Result> {
options: Options<TSlug, TDepth>,
): Promise<{ user: ApplyDepth<DataFromCollectionSlug<TSlug>, TDepth> } & Result> {
const {
collection: collectionSlug,
data,
Expand Down
11 changes: 9 additions & 2 deletions packages/payload/src/collections/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ import type {
UploadField,
} from '../../fields/config/types.js'
import type {
AllowedDepth,
CollectionSlug,
DefaultDepth,
JsonObject,
RequestContext,
TypedAuthOperations,
Expand All @@ -48,6 +50,7 @@ import type {
TypedLocale,
} from '../../index.js'
import type {
ApplyDepth,
PayloadRequest,
SelectType,
Sort,
Expand Down Expand Up @@ -562,8 +565,12 @@ export type Collection = {
}
}

export type BulkOperationResult<TSlug extends CollectionSlug, TSelect extends SelectType> = {
docs: TransformCollectionWithSelect<TSlug, TSelect>[]
export type BulkOperationResult<
TSlug extends CollectionSlug,
TSelect extends SelectType,
TDepth extends AllowedDepth = DefaultDepth,
> = {
docs: ApplyDepth<TransformCollectionWithSelect<TSlug, TSelect>[], TDepth>
errors: {
id: DataFromCollectionSlug<TSlug>['id']
message: string
Expand Down
7 changes: 4 additions & 3 deletions packages/payload/src/collections/operations/delete.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import httpStatus from 'http-status'

import type { AccessResult } from '../../config/types.js'
import type { CollectionSlug } from '../../index.js'
import type { AllowedDepth, CollectionSlug, DefaultDepth } from '../../index.js'
import type { PayloadRequest, PopulateType, SelectType, Where } from '../../types/index.js'
import type {
BeforeOperationHook,
Expand Down Expand Up @@ -41,9 +41,10 @@ export type Arguments = {
export const deleteOperation = async <
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
TDepth extends AllowedDepth = DefaultDepth,
>(
incomingArgs: Arguments,
): Promise<BulkOperationResult<TSlug, TSelect>> => {
): Promise<BulkOperationResult<TSlug, TSelect, TDepth>> => {
let args = incomingArgs

try {
Expand Down Expand Up @@ -291,7 +292,7 @@ export const deleteOperation = async <
await commitTransaction(req)
}

return result
return result as BulkOperationResult<TSlug, TSelect, TDepth>
} catch (error: unknown) {
await killTransaction(args.req)
throw error
Expand Down
23 changes: 18 additions & 5 deletions packages/payload/src/collections/operations/local/create.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import type { CollectionSlug, Payload, RequestContext, TypedLocale } from '../../../index.js'
import type {
AllowedDepth,
CollectionSlug,
DefaultDepth,
Payload,
RequestContext,
TypedLocale,
} from '../../../index.js'
import type {
ApplyDepth,
Document,
PayloadRequest,
PopulateType,
Expand All @@ -17,14 +25,18 @@ import { getFileByPath } from '../../../uploads/getFileByPath.js'
import { createLocalReq } from '../../../utilities/createLocalReq.js'
import { createOperation } from '../create.js'

export type Options<TSlug extends CollectionSlug, TSelect extends SelectType> = {
export type Options<
TSlug extends CollectionSlug,
TSelect extends SelectType,
TDepth extends AllowedDepth = DefaultDepth,
> = {
collection: TSlug
/**
* context, which will then be passed to req.context, which can be read by hooks
*/
context?: RequestContext
data: RequiredDataFromCollectionSlug<TSlug>
depth?: number
depth?: TDepth
disableTransaction?: boolean
disableVerificationEmail?: boolean
draft?: boolean
Expand All @@ -45,10 +57,11 @@ export type Options<TSlug extends CollectionSlug, TSelect extends SelectType> =
export default async function createLocal<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
TDepth extends AllowedDepth = DefaultDepth,
>(
payload: Payload,
options: Options<TSlug, TSelect>,
): Promise<TransformCollectionWithSelect<TSlug, TSelect>> {
options: Options<TSlug, TSelect, TDepth>,
): Promise<ApplyDepth<TransformCollectionWithSelect<TSlug, TSelect>, TDepth>> {
const {
collection: collectionSlug,
data,
Expand Down
55 changes: 40 additions & 15 deletions packages/payload/src/collections/operations/local/delete.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import type { CollectionSlug, Payload, RequestContext, TypedLocale } from '../../../index.js'
import type {
AllowedDepth,
CollectionSlug,
DefaultDepth,
Payload,
RequestContext,
TypedLocale,
} from '../../../index.js'
import type {
ApplyDepth,
Document,
PayloadRequest,
PopulateType,
Expand All @@ -14,13 +22,17 @@ import { createLocalReq } from '../../../utilities/createLocalReq.js'
import { deleteOperation } from '../delete.js'
import { deleteByIDOperation } from '../deleteByID.js'

export type BaseOptions<TSlug extends CollectionSlug, TSelect extends SelectType> = {
export type BaseOptions<
TSlug extends CollectionSlug,
TSelect extends SelectType,
TDepth extends AllowedDepth = DefaultDepth,
> = {
collection: TSlug
/**
* context, which will then be passed to req.context, which can be read by hooks
*/
context?: RequestContext
depth?: number
depth?: TDepth
disableTransaction?: boolean
fallbackLocale?: false | TypedLocale
locale?: TypedLocale
Expand All @@ -36,52 +48,65 @@ export type BaseOptions<TSlug extends CollectionSlug, TSelect extends SelectType
export type ByIDOptions<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
TDepth extends AllowedDepth = DefaultDepth,
> = {
id: number | string
where?: never
} & BaseOptions<TSlug, TSelect>
} & BaseOptions<TSlug, TSelect, TDepth>

export type ManyOptions<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
TDepth extends AllowedDepth = DefaultDepth,
> = {
id?: never
where: Where
} & BaseOptions<TSlug, TSelect>
} & BaseOptions<TSlug, TSelect, TDepth>

export type Options<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
> = ByIDOptions<TSlug, TSelect> | ManyOptions<TSlug, TSelect>
TDepth extends AllowedDepth = DefaultDepth,
> = ByIDOptions<TSlug, TSelect, TDepth> | ManyOptions<TSlug, TSelect, TDepth>

async function deleteLocal<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
TDepth extends AllowedDepth = DefaultDepth,
>(
payload: Payload,
options: ByIDOptions<TSlug, TSelect>,
): Promise<TransformCollectionWithSelect<TSlug, TSelect>>
options: ByIDOptions<TSlug, TSelect, TDepth>,
): Promise<ApplyDepth<TransformCollectionWithSelect<TSlug, TSelect>, TDepth>>
async function deleteLocal<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
TDepth extends AllowedDepth = DefaultDepth,
>(
payload: Payload,
options: ManyOptions<TSlug, TSelect>,
): Promise<BulkOperationResult<TSlug, TSelect>>
options: ManyOptions<TSlug, TSelect, TDepth>,
): Promise<BulkOperationResult<TSlug, TSelect, TDepth>>
async function deleteLocal<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
TDepth extends AllowedDepth = DefaultDepth,
>(
payload: Payload,
options: Options<TSlug, TSelect>,
): Promise<BulkOperationResult<TSlug, TSelect> | TransformCollectionWithSelect<TSlug, TSelect>>
options: Options<TSlug, TSelect, TDepth>,
): Promise<
| ApplyDepth<TransformCollectionWithSelect<TSlug, TSelect>, TDepth>
| BulkOperationResult<TSlug, TSelect, TDepth>
>
async function deleteLocal<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
TDepth extends AllowedDepth = DefaultDepth,
>(
payload: Payload,
options: Options<TSlug, TSelect>,
): Promise<BulkOperationResult<TSlug, TSelect> | TransformCollectionWithSelect<TSlug, TSelect>> {
options: Options<TSlug, TSelect, TDepth>,
): Promise<
| ApplyDepth<TransformCollectionWithSelect<TSlug, TSelect>, TDepth>
| BulkOperationResult<TSlug, TSelect, TDepth>
> {
const {
id,
collection: collectionSlug,
Expand Down Expand Up @@ -120,7 +145,7 @@ async function deleteLocal<
if (options.id) {
return deleteByIDOperation<TSlug, TSelect>(args)
}
return deleteOperation<TSlug, TSelect>(args)
return deleteOperation<TSlug, TSelect, TDepth>(args)
}

export default deleteLocal
16 changes: 11 additions & 5 deletions packages/payload/src/collections/operations/local/duplicate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { CollectionSlug, TypedLocale } from '../../..//index.js'
import type { AllowedDepth, CollectionSlug, DefaultDepth, TypedLocale } from '../../..//index.js'
import type { Payload, RequestContext } from '../../../index.js'
import type {
ApplyDepth,
Document,
PayloadRequest,
PopulateType,
Expand All @@ -13,13 +14,17 @@ import { APIError } from '../../../errors/index.js'
import { createLocalReq } from '../../../utilities/createLocalReq.js'
import { duplicateOperation } from '../duplicate.js'

export type Options<TSlug extends CollectionSlug, TSelect extends SelectType> = {
export type Options<
TSlug extends CollectionSlug,
TSelect extends SelectType,
TDepth extends AllowedDepth = DefaultDepth,
> = {
collection: TSlug
/**
* context, which will then be passed to req.context, which can be read by hooks
*/
context?: RequestContext
depth?: number
depth?: TDepth
disableTransaction?: boolean
draft?: boolean
fallbackLocale?: false | TypedLocale
Expand All @@ -36,10 +41,11 @@ export type Options<TSlug extends CollectionSlug, TSelect extends SelectType> =
export async function duplicate<
TSlug extends CollectionSlug,
TSelect extends SelectFromCollectionSlug<TSlug>,
TDepth extends AllowedDepth = DefaultDepth,
>(
payload: Payload,
options: Options<TSlug, TSelect>,
): Promise<TransformCollectionWithSelect<TSlug, TSelect>> {
options: Options<TSlug, TSelect, TDepth>,
): Promise<ApplyDepth<TransformCollectionWithSelect<TSlug, TSelect>, TDepth>> {
const {
id,
collection: collectionSlug,
Expand Down
Loading

0 comments on commit a245bf7

Please sign in to comment.