fix!: improve collection / global slugs type-safety in various places (#8311)

**BREAKING:**
Improves type-safety of collection / global slugs by using `CollectionSlug` / `UploadCollectionSlug` and `GlobalSlug` types instead of `string` in these places:
Adds `UploadCollectionSlug` and `TypedUploadCollection` utility types

This also changes how we suggest to add an upload collection to a cloud-storage adapter:
Before:
```ts
azureStorage({
  collections: {
    [Media.slug]: true,
  },
}) 
``` 

After:
```ts
azureStorage({
  collections: {
    media: true,
  },
}) 
```
This commit is contained in:
Sasha
2024-11-15 21:33:26 +02:00
committed by GitHub
parent a5cae077cc
commit 810c29b189
26 changed files with 92 additions and 65 deletions

View File

@@ -684,7 +684,7 @@ import { s3Adapter } from '@payloadcms/plugin-cloud-storage/s3'
plugins: [
cloudStorage({
collections: {
[mediaSlug]: {
media: {
adapter: s3Adapter({
bucket: process.env.S3_BUCKET,
config: {
@@ -707,7 +707,7 @@ plugins: [
plugins: [
s3Storage({
collections: {
[mediaSlug]: true,
media: true,
},
bucket: process.env.S3_BUCKET,
config: {

View File

@@ -43,8 +43,8 @@ export default buildConfig({
enabled: true, // Optional, defaults to true
// Specify which collections should use Vercel Blob
collections: {
[Media.slug]: true,
[MediaWithPrefix.slug]: {
media: true,
'media-with-prefix': {
prefix: 'my-prefix',
},
},
@@ -90,8 +90,8 @@ export default buildConfig({
plugins: [
s3Storage({
collections: {
[mediaSlug]: true,
[mediaWithPrefixSlug]: {
media: true,
'media-with-prefix': {
prefix,
},
},
@@ -137,8 +137,8 @@ export default buildConfig({
plugins: [
azureStorage({
collections: {
[mediaSlug]: true,
[mediaWithPrefixSlug]: {
media: true,
'media-with-prefix': {
prefix,
},
},
@@ -186,8 +186,8 @@ export default buildConfig({
plugins: [
gcsStorage({
collections: {
[mediaSlug]: true,
[mediaWithPrefixSlug]: {
media: true,
'media-with-prefix': {
prefix,
},
},
@@ -233,7 +233,7 @@ export default buildConfig({
plugins: [
uploadthingStorage({
collections: {
[mediaSlug]: true,
media: true,
},
options: {
token: process.env.UPLOADTHING_TOKEN,

View File

@@ -71,7 +71,7 @@ const vercelBlobStorageReplacement: StorageAdapterReplacement = {
configReplacement: [
' vercelBlobStorage({',
' collections: {',
' [Media.slug]: true,',
' media: true,',
' },',
" token: process.env.BLOB_READ_WRITE_TOKEN || '',",
' }),',

View File

@@ -1,4 +1,11 @@
import type { CollectionConfig, Config, FileData, PayloadRequest, TypeWithID } from 'payload'
import type {
CollectionConfig,
Config,
FileData,
PayloadRequest,
TypeWithID,
UploadCollectionSlug,
} from 'payload'
export interface File {
buffer: Buffer
@@ -94,7 +101,7 @@ export interface PluginOptions {
/**
* Caching configuration per-collection
*/
collections?: Record<string, CollectionCachingConfig>
collections?: Partial<Record<UploadCollectionSlug, CollectionCachingConfig>>
/** Caching in seconds override for all collections
* @default 86400 (24 hours)
*/

View File

@@ -1,6 +1,6 @@
import type { DeepRequired } from 'ts-essentials'
import type { Payload } from '../index.js'
import type { CollectionSlug, GlobalSlug, Payload } from '../index.js'
import type { PayloadRequest, Where } from '../types/index.js'
export type Permission = {
@@ -54,10 +54,10 @@ export type DocumentPermissions = CollectionPermission | GlobalPermission
export type Permissions = {
canAccessAdmin: boolean
collections: {
[collectionSlug: string]: CollectionPermission
[collectionSlug: CollectionSlug]: CollectionPermission
}
globals?: {
[globalSlug: string]: GlobalPermission
[globalSlug: GlobalSlug]: GlobalPermission
}
}

View File

@@ -520,11 +520,12 @@ export type SanitizedJoins = {
export interface SanitizedCollectionConfig
extends Omit<
DeepRequired<CollectionConfig>,
'auth' | 'endpoints' | 'fields' | 'upload' | 'versions'
'auth' | 'endpoints' | 'fields' | 'slug' | 'upload' | 'versions'
> {
auth: Auth
endpoints: Endpoint[] | false
fields: Field[]
slug: CollectionSlug
/**
* Object of collections to join 'Join Fields object keyed by collection
*/

View File

@@ -1,4 +1,5 @@
import type { TypeWithID } from '../collections/config/types.js'
import type { CollectionSlug, GlobalSlug } from '../index.js'
import type {
Document,
JoinQuery,
@@ -185,7 +186,7 @@ export type RollbackTransaction = (id: number | Promise<number | string> | strin
export type CommitTransaction = (id: number | Promise<number | string> | string) => Promise<void>
export type QueryDraftsArgs = {
collection: string
collection: CollectionSlug
joins?: JoinQuery
limit?: number
locale?: string
@@ -200,7 +201,7 @@ export type QueryDraftsArgs = {
export type QueryDrafts = <T = TypeWithID>(args: QueryDraftsArgs) => Promise<PaginatedDocs<T>>
export type FindOneArgs = {
collection: string
collection: CollectionSlug
joins?: JoinQuery
locale?: string
req: PayloadRequest
@@ -211,7 +212,7 @@ export type FindOneArgs = {
export type FindOne = <T extends TypeWithID>(args: FindOneArgs) => Promise<null | T>
export type FindArgs = {
collection: string
collection: CollectionSlug
joins?: JoinQuery
/** Setting limit to 1 is equal to the previous Model.findOne(). Setting limit to 0 disables the limit */
limit?: number
@@ -230,7 +231,7 @@ export type FindArgs = {
export type Find = <T = TypeWithID>(args: FindArgs) => Promise<PaginatedDocs<T>>
export type CountArgs = {
collection: string
collection: CollectionSlug
locale?: string
req: PayloadRequest
where?: Where
@@ -263,7 +264,7 @@ type BaseVersionArgs = {
}
export type FindVersionsArgs = {
collection: string
collection: CollectionSlug
} & BaseVersionArgs
export type FindVersions = <T = TypeWithID>(
@@ -271,7 +272,7 @@ export type FindVersions = <T = TypeWithID>(
) => Promise<PaginatedDocs<TypeWithVersion<T>>>
export type FindGlobalVersionsArgs = {
global: string
global: GlobalSlug
} & BaseVersionArgs
export type FindGlobalArgs = {
@@ -283,7 +284,7 @@ export type FindGlobalArgs = {
}
export type UpdateGlobalVersionArgs<T = TypeWithID> = {
global: string
global: GlobalSlug
locale?: string
/**
* Additional database adapter specific options to pass to the query
@@ -340,7 +341,7 @@ export type FindGlobalVersions = <T = TypeWithID>(
) => Promise<PaginatedDocs<TypeWithVersion<T>>>
export type DeleteVersionsArgs = {
collection: string
collection: CollectionSlug
locale?: string
req: PayloadRequest
sort?: {
@@ -351,7 +352,7 @@ export type DeleteVersionsArgs = {
export type CreateVersionArgs<T = TypeWithID> = {
autosave: boolean
collectionSlug: string
collectionSlug: CollectionSlug
createdAt: string
/** ID of the parent document for which the version should be created for */
parent: number | string
@@ -370,7 +371,7 @@ export type CreateVersion = <T extends TypeWithID = TypeWithID>(
export type CreateGlobalVersionArgs<T = TypeWithID> = {
autosave: boolean
createdAt: string
globalSlug: string
globalSlug: GlobalSlug
/** ID of the parent document for which the version should be created for */
parent: number | string
publishedLocale?: string
@@ -388,7 +389,7 @@ export type CreateGlobalVersion = <T extends TypeWithID = TypeWithID>(
export type DeleteVersions = (args: DeleteVersionsArgs) => Promise<void>
export type UpdateVersionArgs<T = TypeWithID> = {
collection: string
collection: CollectionSlug
locale?: string
/**
* Additional database adapter specific options to pass to the query
@@ -413,7 +414,7 @@ export type UpdateVersion = <T extends TypeWithID = TypeWithID>(
) => Promise<TypeWithVersion<T>>
export type CreateArgs = {
collection: string
collection: CollectionSlug
data: Record<string, unknown>
draft?: boolean
locale?: string
@@ -424,7 +425,7 @@ export type CreateArgs = {
export type Create = (args: CreateArgs) => Promise<Document>
export type UpdateOneArgs = {
collection: string
collection: CollectionSlug
data: Record<string, unknown>
draft?: boolean
joins?: JoinQuery
@@ -449,7 +450,7 @@ export type UpdateOneArgs = {
export type UpdateOne = (args: UpdateOneArgs) => Promise<Document>
export type UpsertArgs = {
collection: string
collection: CollectionSlug
data: Record<string, unknown>
joins?: JoinQuery
locale?: string
@@ -461,7 +462,7 @@ export type UpsertArgs = {
export type Upsert = (args: UpsertArgs) => Promise<Document>
export type DeleteOneArgs = {
collection: string
collection: CollectionSlug
joins?: JoinQuery
req: PayloadRequest
select?: SelectType
@@ -471,7 +472,7 @@ export type DeleteOneArgs = {
export type DeleteOne = (args: DeleteOneArgs) => Promise<Document>
export type DeleteManyArgs = {
collection: string
collection: CollectionSlug
joins?: JoinQuery
req: PayloadRequest
where: Where

View File

@@ -189,9 +189,10 @@ export type GlobalConfig = {
}
export interface SanitizedGlobalConfig
extends Omit<DeepRequired<GlobalConfig>, 'endpoints' | 'fields' | 'versions'> {
extends Omit<DeepRequired<GlobalConfig>, 'endpoints' | 'fields' | 'slug' | 'versions'> {
endpoints: Endpoint[] | false
fields: Field[]
slug: GlobalSlug
versions: SanitizedGlobalVersions
}

View File

@@ -27,6 +27,8 @@ import type {
} from './collections/config/types.js'
export type { FieldState } from './admin/forms/Form.js'
export type * from './admin/types.js'
import type { NonNever } from 'ts-essentials'
import type { Options as CountOptions } from './collections/operations/local/count.js'
import type { Options as CreateOptions } from './collections/operations/local/create.js'
import type {
@@ -166,6 +168,16 @@ type ResolveGlobalSelectType<T> = 'globalsSelect' extends keyof T
// Applying helper types to GeneratedTypes
export type TypedCollection = ResolveCollectionType<GeneratedTypes>
export type TypedUploadCollection = NonNever<{
[K in keyof TypedCollection]:
| 'filename'
| 'filesize'
| 'mimeType'
| 'url' extends keyof TypedCollection[K]
? TypedCollection[K]
: never
}>
export type TypedCollectionSelect = ResolveCollectionSelectType<GeneratedTypes>
export type TypedCollectionJoins = ResolveCollectionJoinsType<GeneratedTypes>
@@ -180,6 +192,8 @@ export type StringKeyOf<T> = Extract<keyof T, string>
// Define the types for slugs using the appropriate collections and globals
export type CollectionSlug = StringKeyOf<TypedCollection>
export type UploadCollectionSlug = StringKeyOf<TypedUploadCollection>
type ResolveDbType<T> = 'db' extends keyof T
? T['db']
: // @ts-expect-error
@@ -228,7 +242,7 @@ export class BasePayload {
authStrategies: AuthStrategy[]
collections: {
[slug: string]: Collection
[slug: CollectionSlug]: Collection
} = {}
config: SanitizedConfig

View File

@@ -5,6 +5,7 @@ import type {
ImageSize,
PayloadRequest,
TypeWithID,
UploadCollectionSlug,
} from 'payload'
export interface File {
@@ -79,7 +80,7 @@ export interface CollectionOptions {
}
export interface PluginOptions {
collections: Record<string, CollectionOptions>
collections: Partial<Record<UploadCollectionSlug, CollectionOptions>>
/**
* Whether or not to enable the plugin
*

View File

@@ -1,3 +1,5 @@
import type { CollectionSlug } from 'payload'
export type Breadcrumb = {
doc: string
label: string
@@ -22,7 +24,7 @@ export type NestedDocsPluginConfig = {
/**
* The slugs of the collections this plugin should extend. If you need different configs for different collections, this plugin can be added to your config more than once having different collections.
*/
collections: string[]
collections: CollectionSlug[]
generateLabel?: GenerateLabel
generateURL?: GenerateURL
/**

View File

@@ -1,4 +1,4 @@
import type { Payload, Config as PayloadConfig, PayloadRequest } from 'payload'
import type { CollectionSlug, Payload, Config as PayloadConfig, PayloadRequest } from 'payload'
import type Stripe from 'stripe'
export type StripeWebhookHandler<T = any> = (args: {
@@ -20,7 +20,7 @@ export type FieldSyncConfig = {
}
export type SyncConfig = {
collection: string
collection: CollectionSlug
fields: FieldSyncConfig[]
stripeResourceType: 'customers' | 'products' // TODO: get this from Stripe types
stripeResourceTypeSingular: 'customer' | 'product' // TODO: there must be a better way to do this

View File

@@ -25,8 +25,8 @@ export default buildConfig({
plugins: [
azureStorage({
collections: {
[mediaSlug]: true,
[mediaWithPrefixSlug]: {
media: true,
'media-with-prefix': {
prefix,
},
},

View File

@@ -4,7 +4,7 @@ import type {
CollectionOptions,
GeneratedAdapter,
} from '@payloadcms/plugin-cloud-storage/types'
import type { Config, Plugin } from 'payload'
import type { Config, Plugin, UploadCollectionSlug } from 'payload'
import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'
@@ -30,7 +30,7 @@ export type AzureStorageOptions = {
/**
* Collection options to apply the Azure Blob adapter to.
*/
collections: Record<string, Omit<CollectionOptions, 'adapter'> | true>
collections: Partial<Record<UploadCollectionSlug, Omit<CollectionOptions, 'adapter'> | true>>
/**
* Azure Blob storage connection string

View File

@@ -25,8 +25,8 @@ export default buildConfig({
plugins: [
gcsStorage({
collections: {
[mediaSlug]: true,
[mediaWithPrefixSlug]: {
media: true,
'media-with-prefix': {
prefix,
},
},

View File

@@ -5,7 +5,7 @@ import type {
CollectionOptions,
GeneratedAdapter,
} from '@payloadcms/plugin-cloud-storage/types'
import type { Config, Plugin } from 'payload'
import type { Config, Plugin, UploadCollectionSlug } from 'payload'
import { Storage } from '@google-cloud/storage'
import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'
@@ -25,7 +25,7 @@ export interface GcsStorageOptions {
/**
* Collection options to apply the S3 adapter to.
*/
collections: Record<string, Omit<CollectionOptions, 'adapter'> | true>
collections: Partial<Record<UploadCollectionSlug, Omit<CollectionOptions, 'adapter'> | true>>
/**
* Whether or not to enable the plugin
*

View File

@@ -26,8 +26,8 @@ export default buildConfig({
plugins: [
s3Storage({
collections: {
[mediaSlug]: true,
[mediaWithPrefixSlug]: {
media: true,
'media-with-prefix': {
prefix,
},
},

View File

@@ -4,7 +4,7 @@ import type {
CollectionOptions,
GeneratedAdapter,
} from '@payloadcms/plugin-cloud-storage/types'
import type { Config, Plugin } from 'payload'
import type { Config, Plugin, UploadCollectionSlug } from 'payload'
import * as AWS from '@aws-sdk/client-s3'
import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'
@@ -31,7 +31,7 @@ export type S3StorageOptions = {
/**
* Collection options to apply the S3 adapter to.
*/
collections: Record<string, Omit<CollectionOptions, 'adapter'> | true>
collections: Partial<Record<UploadCollectionSlug, Omit<CollectionOptions, 'adapter'> | true>>
/**
* AWS S3 client configuration. Highly dependent on your AWS setup.
*

View File

@@ -20,7 +20,7 @@ export default buildConfig({
plugins: [
uploadthingStorage({
collections: {
[mediaSlug]: true,
media: true,
},
options: {
token: process.env.UPLOADTHING_TOKEN,

View File

@@ -4,7 +4,7 @@ import type {
CollectionOptions,
GeneratedAdapter,
} from '@payloadcms/plugin-cloud-storage/types'
import type { Config, Field, Plugin } from 'payload'
import type { Config, Field, Plugin, UploadCollectionSlug } from 'payload'
import type { UTApiOptions } from 'uploadthing/types'
import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'
@@ -19,7 +19,7 @@ export type UploadthingStorageOptions = {
/**
* Collection options to apply the adapter to.
*/
collections: Record<string, Omit<CollectionOptions, 'adapter'> | true>
collections: Partial<Record<UploadCollectionSlug, Omit<CollectionOptions, 'adapter'> | true>>
/**
* Whether or not to enable the plugin

View File

@@ -28,8 +28,8 @@ export default buildConfig({
enabled: true, // Optional, defaults to true
// Specify which collections should use Vercel Blob
collections: {
[Media.slug]: true,
[MediaWithPrefix.slug]: {
media: true,
'media-with-prefix': {
prefix: 'my-prefix',
},
},

View File

@@ -4,7 +4,7 @@ import type {
CollectionOptions,
GeneratedAdapter,
} from '@payloadcms/plugin-cloud-storage/types'
import type { Config, Plugin } from 'payload'
import type { Config, Plugin, UploadCollectionSlug } from 'payload'
import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'
@@ -39,7 +39,7 @@ export type VercelBlobStorageOptions = {
/**
* Collections to apply the Vercel Blob adapter to
*/
collections: Record<string, Omit<CollectionOptions, 'adapter'> | true>
collections: Partial<Record<UploadCollectionSlug, Omit<CollectionOptions, 'adapter'> | true>>
/**
* Whether or not to enable the plugin

View File

@@ -6,7 +6,7 @@ import type { ListDrawerContextProps } from './Provider.js'
export type ListDrawerProps = {
readonly allowCreate?: boolean
readonly collectionSlugs: string[]
readonly collectionSlugs: SanitizedCollectionConfig['slug'][]
readonly drawerSlug?: string
readonly enableRowSelections?: boolean
readonly filterOptions?: FilterOptionsResult
@@ -21,9 +21,9 @@ export type ListTogglerProps = {
} & HTMLAttributes<HTMLButtonElement>
export type UseListDrawer = (args: {
collectionSlugs?: string[]
collectionSlugs?: SanitizedCollectionConfig['slug'][]
filterOptions?: FilterOptionsResult
selectedCollection?: string
selectedCollection?: SanitizedCollectionConfig['slug']
uploads?: boolean // finds all collections with upload: true
}) => [
React.FC<

View File

@@ -26,7 +26,7 @@ export default buildConfig({
plugins: [
vercelBlobStorage({
collections: {
[Media.slug]: true,
media: true,
},
token: process.env.BLOB_READ_WRITE_TOKEN || '',
}),

View File

@@ -30,7 +30,7 @@ export default buildConfig({
plugins: [
vercelBlobStorage({
collections: {
[Media.slug]: true,
media: true,
},
token: process.env.BLOB_READ_WRITE_TOKEN || '',
}),

View File

@@ -32,7 +32,7 @@ export default buildConfig({
plugins: [
vercelBlobStorage({
collections: {
[Media.slug]: true,
slug: true,
},
token: process.env.BLOB_READ_WRITE_TOKEN || '',
}),