feat: filename compound index (#7651)

Allow a compound index to be used for upload collections via a
`filenameCompoundIndex` field. Previously, `filename` was always treated
as unique.

Usage:

```ts
{
  slug: 'upload-field',
   upload: {
     // Slugs to include in compound index
     filenameCompoundIndex: ['filename', 'alt'],
  },
}
```
This commit is contained in:
Elliot DeNolf
2024-08-13 13:55:10 -04:00
committed by GitHub
parent 6c0f99082b
commit 5fc9f76406
11 changed files with 186 additions and 48 deletions

View File

@@ -97,6 +97,7 @@ _An asterisk denotes that an option is required._
| **`displayPreview`** | Enable displaying preview of the uploaded file in Upload fields related to this Collection. Can be locally overridden by `displayPreview` option in Upload field. [More](/docs/fields/upload#config-options). | | **`displayPreview`** | Enable displaying preview of the uploaded file in Upload fields related to this Collection. Can be locally overridden by `displayPreview` option in Upload field. [More](/docs/fields/upload#config-options). |
| **`externalFileHeaderFilter`** | Accepts existing headers and returns the headers after filtering or modifying. | | **`externalFileHeaderFilter`** | Accepts existing headers and returns the headers after filtering or modifying. |
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. | | **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. |
| **`filenameCompoundIndex`** | Field slugs to use for a compount index instead of the default filename index.
| **`focalPoint`** | Set to `false` to disable the focal point selection tool in the [Admin Panel](../admin/overview). The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) | | **`focalPoint`** | Set to `false` to disable the focal point selection tool in the [Admin Panel](../admin/overview). The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) |
| **`formatOptions`** | An object with `format` and `options` that are used with the Sharp image library to format the upload file. [More](https://sharp.pixelplumbing.com/api-output#toformat) | | **`formatOptions`** | An object with `format` and `options` that are used with the Sharp image library to format the upload file. [More](https://sharp.pixelplumbing.com/api-output#toformat) |
| **`handlers`** | Array of Request handlers to execute when fetching a file, if a handler returns a Response it will be sent to the client. Otherwise Payload will retrieve and send back the file. | | **`handlers`** | Array of Request handlers to execute when fetching a file, if a handler returns a Response it will be sent to the client. Otherwise Payload will retrieve and send back the file. |

View File

@@ -21,6 +21,18 @@ const buildCollectionSchema = (
}, },
}) })
if (Array.isArray(collection.upload.filenameCompoundIndex)) {
const indexDefinition: Record<string, 1> = collection.upload.filenameCompoundIndex.reduce(
(acc, index) => {
acc[index] = 1
return acc
},
{},
)
schema.index(indexDefinition, { unique: true })
}
if (config.indexSortableFields && collection.timestamps !== false) { if (config.indexSortableFields && collection.timestamps !== false) {
schema.index({ updatedAt: 1 }) schema.index({ updatedAt: 1 })
schema.index({ createdAt: 1 }) schema.index({ createdAt: 1 })

View File

@@ -1,9 +1,11 @@
import type { Init, SanitizedCollectionConfig } from 'payload' import type { Init, SanitizedCollectionConfig } from 'payload'
import { createTableName } from '@payloadcms/drizzle' import { createTableName } from '@payloadcms/drizzle'
import { uniqueIndex } from 'drizzle-orm/pg-core'
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload' import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload'
import toSnakeCase from 'to-snake-case' import toSnakeCase from 'to-snake-case'
import type { BaseExtraConfig } from './schema/build.js'
import type { PostgresAdapter } from './types.js' import type { PostgresAdapter } from './types.js'
import { buildTable } from './schema/build.js' import { buildTable } from './schema/build.js'
@@ -34,8 +36,22 @@ export const init: Init = function init(this: PostgresAdapter) {
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => { this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug)) const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
const baseExtraConfig: BaseExtraConfig = {}
if (collection.upload.filenameCompoundIndex) {
const indexName = `${tableName}_filename_compound_idx`
baseExtraConfig.filename_compound_index = (cols) => {
const colsConstraint = collection.upload.filenameCompoundIndex.map((f) => {
return cols[f]
})
return uniqueIndex(indexName).on(colsConstraint[0], ...colsConstraint.slice(1))
}
}
buildTable({ buildTable({
adapter: this, adapter: this,
baseExtraConfig,
disableNotNull: !!collection?.versions?.drafts, disableNotNull: !!collection?.versions?.drafts,
disableUnique: false, disableUnique: false,
fields: collection.fields, fields: collection.fields,

View File

@@ -38,6 +38,10 @@ export type RelationMap = Map<string, { localized: boolean; target: string; type
type Args = { type Args = {
adapter: PostgresAdapter adapter: PostgresAdapter
baseColumns?: Record<string, PgColumnBuilder> baseColumns?: Record<string, PgColumnBuilder>
/**
* After table is created, run these functions to add extra config to the table
* ie. indexes, multiple columns, etc
*/
baseExtraConfig?: BaseExtraConfig baseExtraConfig?: BaseExtraConfig
buildNumbers?: boolean buildNumbers?: boolean
buildRelationships?: boolean buildRelationships?: boolean

View File

@@ -1,11 +1,12 @@
/* eslint-disable no-param-reassign */
import type { DrizzleAdapter } from '@payloadcms/drizzle/types' import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { Init, SanitizedCollectionConfig } from 'payload' import type { Init, SanitizedCollectionConfig } from 'payload'
import { createTableName } from '@payloadcms/drizzle' import { createTableName } from '@payloadcms/drizzle'
import { uniqueIndex } from 'drizzle-orm/sqlite-core'
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload' import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload'
import toSnakeCase from 'to-snake-case' import toSnakeCase from 'to-snake-case'
import type { BaseExtraConfig } from './schema/build.js'
import type { SQLiteAdapter } from './types.js' import type { SQLiteAdapter } from './types.js'
import { buildTable } from './schema/build.js' import { buildTable } from './schema/build.js'
@@ -37,6 +38,19 @@ export const init: Init = function init(this: SQLiteAdapter) {
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => { this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug)) const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
const baseExtraConfig: BaseExtraConfig = {}
if (collection.upload.filenameCompoundIndex) {
const indexName = `${tableName}_filename_compound_idx`
baseExtraConfig.filename_compound_index = (cols) => {
const colsConstraint = collection.upload.filenameCompoundIndex.map((f) => {
return cols[f]
})
return uniqueIndex(indexName).on(colsConstraint[0], ...colsConstraint.slice(1))
}
}
buildTable({ buildTable({
adapter: this, adapter: this,
disableNotNull: !!collection?.versions?.drafts, disableNotNull: !!collection?.versions?.drafts,

View File

@@ -40,6 +40,10 @@ export type RelationMap = Map<string, { localized: boolean; target: string; type
type Args = { type Args = {
adapter: SQLiteAdapter adapter: SQLiteAdapter
baseColumns?: Record<string, SQLiteColumnBuilder> baseColumns?: Record<string, SQLiteColumnBuilder>
/**
* After table is created, run these functions to add extra config to the table
* ie. indexes, multiple columns, etc
*/
baseExtraConfig?: BaseExtraConfig baseExtraConfig?: BaseExtraConfig
buildNumbers?: boolean buildNumbers?: boolean
buildRelationships?: boolean buildRelationships?: boolean

View File

@@ -111,7 +111,14 @@ export const getBaseUploadFields = ({ collection, config }: Options): Field[] =>
}, },
index: true, index: true,
label: ({ t }) => t('upload:fileName'), label: ({ t }) => t('upload:fileName'),
unique: true, }
// Only set unique: true if the collection does not have a compound index
if (
collection.upload === true ||
(typeof collection.upload === 'object' && !collection.upload.filenameCompoundIndex)
) {
filename.unique = true
} }
const url: Field = { const url: Field = {

View File

@@ -118,6 +118,10 @@ export type UploadConfig = {
* @default undefined * @default undefined
*/ */
externalFileHeaderFilter?: (headers: Record<string, string>) => Record<string, string> externalFileHeaderFilter?: (headers: Record<string, string>) => Record<string, string>
/**
* Field slugs to use for a compount index instead of the default filename index.
*/
filenameCompoundIndex?: string[]
/** /**
* Require files to be uploaded when creating a document. * Require files to be uploaded when creating a document.
* @default true * @default true

View File

@@ -59,7 +59,7 @@ async function main() {
${chalk.white.bold(filtered.map((p) => p.name).join('\n'))} ${chalk.white.bold(filtered.map((p) => p.name).join('\n'))}
`) `)
//execSync('pnpm build:all --output-logs=errors-only', { stdio: 'inherit' }) execSync('pnpm build:all --output-logs=errors-only', { stdio: 'inherit' })
header(`\n 📦 Packing all packages to ${dest}...`) header(`\n 📦 Packing all packages to ${dest}...`)

View File

@@ -95,6 +95,36 @@ export default buildConfigWithDefaults({
staticDir: path.resolve(dirname, './media-gif'), staticDir: path.resolve(dirname, './media-gif'),
}, },
}, },
{
slug: 'filename-compound-index',
fields: [
{
name: 'alt',
type: 'text',
admin: {
description: 'Alt text to be used for compound index',
},
},
],
upload: {
filenameCompoundIndex: ['filename', 'alt'],
imageSizes: [
{
name: 'small',
formatOptions: { format: 'gif', options: { quality: 90 } },
height: 100,
width: 100,
},
{
name: 'large',
formatOptions: { format: 'gif', options: { quality: 90 } },
height: 1000,
width: 1000,
},
],
mimeTypes: ['image/*'],
},
},
{ {
slug: 'no-image-sizes', slug: 'no-image-sizes',
fields: [], fields: [],
@@ -787,6 +817,14 @@ export default buildConfigWithDefaults({
imageWithoutPreview3: uploadedImageWithoutPreview, imageWithoutPreview3: uploadedImageWithoutPreview,
}, },
}) })
await payload.create({
collection: 'filename-compound-index',
data: {
alt: 'alt-1',
},
file: imageFile,
})
}, },
serverURL: undefined, serverURL: undefined,
upload: { upload: {

View File

@@ -14,6 +14,7 @@ export interface Config {
relation: Relation; relation: Relation;
audio: Audio; audio: Audio;
'gif-resize': GifResize; 'gif-resize': GifResize;
'filename-compound-index': FilenameCompoundIndex;
'no-image-sizes': NoImageSize; 'no-image-sizes': NoImageSize;
'object-fit': ObjectFit; 'object-fit': ObjectFit;
'with-meta-data': WithMetaDatum; 'with-meta-data': WithMetaDatum;
@@ -46,7 +47,7 @@ export interface Config {
'payload-migrations': PayloadMigration; 'payload-migrations': PayloadMigration;
}; };
db: { db: {
defaultIDType: string; defaultIDType: number;
}; };
globals: {}; globals: {};
locale: null; locale: null;
@@ -77,9 +78,9 @@ export interface UserAuthOperations {
* via the `definition` "relation". * via the `definition` "relation".
*/ */
export interface Relation { export interface Relation {
id: string; id: number;
image?: string | Media | null; image?: number | Media | null;
versionedImage?: string | Version | null; versionedImage?: number | Version | null;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
@@ -88,7 +89,7 @@ export interface Relation {
* via the `definition` "media". * via the `definition` "media".
*/ */
export interface Media { export interface Media {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -228,7 +229,7 @@ export interface Media {
* via the `definition` "versions". * via the `definition` "versions".
*/ */
export interface Version { export interface Version {
id: string; id: number;
title?: string | null; title?: string | null;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
@@ -248,8 +249,8 @@ export interface Version {
* via the `definition` "audio". * via the `definition` "audio".
*/ */
export interface Audio { export interface Audio {
id: string; id: number;
audio?: string | Media | null; audio?: number | Media | null;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
@@ -258,7 +259,44 @@ export interface Audio {
* via the `definition` "gif-resize". * via the `definition` "gif-resize".
*/ */
export interface GifResize { export interface GifResize {
id: string; id: number;
updatedAt: string;
createdAt: string;
url?: string | null;
thumbnailURL?: string | null;
filename?: string | null;
mimeType?: string | null;
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
sizes?: {
small?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
large?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "filename-compound-index".
*/
export interface FilenameCompoundIndex {
id: number;
alt?: string | null;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -294,7 +332,7 @@ export interface GifResize {
* via the `definition` "no-image-sizes". * via the `definition` "no-image-sizes".
*/ */
export interface NoImageSize { export interface NoImageSize {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -312,7 +350,7 @@ export interface NoImageSize {
* via the `definition` "object-fit". * via the `definition` "object-fit".
*/ */
export interface ObjectFit { export interface ObjectFit {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -364,7 +402,7 @@ export interface ObjectFit {
* via the `definition` "with-meta-data". * via the `definition` "with-meta-data".
*/ */
export interface WithMetaDatum { export interface WithMetaDatum {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -392,7 +430,7 @@ export interface WithMetaDatum {
* via the `definition` "without-meta-data". * via the `definition` "without-meta-data".
*/ */
export interface WithoutMetaDatum { export interface WithoutMetaDatum {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -420,7 +458,7 @@ export interface WithoutMetaDatum {
* via the `definition` "with-only-jpeg-meta-data". * via the `definition` "with-only-jpeg-meta-data".
*/ */
export interface WithOnlyJpegMetaDatum { export interface WithOnlyJpegMetaDatum {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -448,7 +486,7 @@ export interface WithOnlyJpegMetaDatum {
* via the `definition` "crop-only". * via the `definition` "crop-only".
*/ */
export interface CropOnly { export interface CropOnly {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -492,7 +530,7 @@ export interface CropOnly {
* via the `definition` "focal-only". * via the `definition` "focal-only".
*/ */
export interface FocalOnly { export interface FocalOnly {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -536,7 +574,7 @@ export interface FocalOnly {
* via the `definition` "focal-no-sizes". * via the `definition` "focal-no-sizes".
*/ */
export interface FocalNoSize { export interface FocalNoSize {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -554,7 +592,7 @@ export interface FocalNoSize {
* via the `definition` "animated-type-media". * via the `definition` "animated-type-media".
*/ */
export interface AnimatedTypeMedia { export interface AnimatedTypeMedia {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -606,7 +644,7 @@ export interface AnimatedTypeMedia {
* via the `definition` "enlarge". * via the `definition` "enlarge".
*/ */
export interface Enlarge { export interface Enlarge {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -666,7 +704,7 @@ export interface Enlarge {
* via the `definition` "reduce". * via the `definition` "reduce".
*/ */
export interface Reduce { export interface Reduce {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -718,7 +756,7 @@ export interface Reduce {
* via the `definition` "media-trim". * via the `definition` "media-trim".
*/ */
export interface MediaTrim { export interface MediaTrim {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -762,7 +800,7 @@ export interface MediaTrim {
* via the `definition` "custom-file-name-media". * via the `definition` "custom-file-name-media".
*/ */
export interface CustomFileNameMedia { export interface CustomFileNameMedia {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -790,7 +828,7 @@ export interface CustomFileNameMedia {
* via the `definition` "unstored-media". * via the `definition` "unstored-media".
*/ */
export interface UnstoredMedia { export interface UnstoredMedia {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -808,7 +846,7 @@ export interface UnstoredMedia {
* via the `definition` "externally-served-media". * via the `definition` "externally-served-media".
*/ */
export interface ExternallyServedMedia { export interface ExternallyServedMedia {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -826,8 +864,8 @@ export interface ExternallyServedMedia {
* via the `definition` "uploads-1". * via the `definition` "uploads-1".
*/ */
export interface Uploads1 { export interface Uploads1 {
id: string; id: number;
media?: string | Uploads2 | null; media?: number | Uploads2 | null;
richText?: { richText?: {
root: { root: {
type: string; type: string;
@@ -860,7 +898,7 @@ export interface Uploads1 {
* via the `definition` "uploads-2". * via the `definition` "uploads-2".
*/ */
export interface Uploads2 { export interface Uploads2 {
id: string; id: number;
title?: string | null; title?: string | null;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
@@ -879,7 +917,7 @@ export interface Uploads2 {
* via the `definition` "admin-thumbnail-function". * via the `definition` "admin-thumbnail-function".
*/ */
export interface AdminThumbnailFunction { export interface AdminThumbnailFunction {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -897,7 +935,7 @@ export interface AdminThumbnailFunction {
* via the `definition` "admin-thumbnail-size". * via the `definition` "admin-thumbnail-size".
*/ */
export interface AdminThumbnailSize { export interface AdminThumbnailSize {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -933,7 +971,7 @@ export interface AdminThumbnailSize {
* via the `definition` "optional-file". * via the `definition` "optional-file".
*/ */
export interface OptionalFile { export interface OptionalFile {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -951,7 +989,7 @@ export interface OptionalFile {
* via the `definition` "required-file". * via the `definition` "required-file".
*/ */
export interface RequiredFile { export interface RequiredFile {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string | null; url?: string | null;
@@ -969,7 +1007,7 @@ export interface RequiredFile {
* via the `definition` "custom-upload-field". * via the `definition` "custom-upload-field".
*/ */
export interface CustomUploadField { export interface CustomUploadField {
id: string; id: number;
alt?: string | null; alt?: string | null;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
@@ -988,7 +1026,7 @@ export interface CustomUploadField {
* via the `definition` "media-with-relation-preview". * via the `definition` "media-with-relation-preview".
*/ */
export interface MediaWithRelationPreview { export interface MediaWithRelationPreview {
id: string; id: number;
title?: string | null; title?: string | null;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
@@ -1007,7 +1045,7 @@ export interface MediaWithRelationPreview {
* via the `definition` "media-without-relation-preview". * via the `definition` "media-without-relation-preview".
*/ */
export interface MediaWithoutRelationPreview { export interface MediaWithoutRelationPreview {
id: string; id: number;
title?: string | null; title?: string | null;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
@@ -1026,13 +1064,13 @@ export interface MediaWithoutRelationPreview {
* via the `definition` "relation-preview". * via the `definition` "relation-preview".
*/ */
export interface RelationPreview { export interface RelationPreview {
id: string; id: number;
imageWithPreview1?: string | MediaWithRelationPreview | null; imageWithPreview1?: number | MediaWithRelationPreview | null;
imageWithPreview2?: string | MediaWithRelationPreview | null; imageWithPreview2?: number | MediaWithRelationPreview | null;
imageWithoutPreview1?: string | MediaWithRelationPreview | null; imageWithoutPreview1?: number | MediaWithRelationPreview | null;
imageWithoutPreview2?: string | MediaWithoutRelationPreview | null; imageWithoutPreview2?: number | MediaWithoutRelationPreview | null;
imageWithPreview3?: string | MediaWithoutRelationPreview | null; imageWithPreview3?: number | MediaWithoutRelationPreview | null;
imageWithoutPreview3?: string | MediaWithoutRelationPreview | null; imageWithoutPreview3?: number | MediaWithoutRelationPreview | null;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
@@ -1041,7 +1079,7 @@ export interface RelationPreview {
* via the `definition` "users". * via the `definition` "users".
*/ */
export interface User { export interface User {
id: string; id: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
email: string; email: string;
@@ -1058,10 +1096,10 @@ export interface User {
* via the `definition` "payload-preferences". * via the `definition` "payload-preferences".
*/ */
export interface PayloadPreference { export interface PayloadPreference {
id: string; id: number;
user: { user: {
relationTo: 'users'; relationTo: 'users';
value: string | User; value: number | User;
}; };
key?: string | null; key?: string | null;
value?: value?:
@@ -1081,7 +1119,7 @@ export interface PayloadPreference {
* via the `definition` "payload-migrations". * via the `definition` "payload-migrations".
*/ */
export interface PayloadMigration { export interface PayloadMigration {
id: string; id: number;
name?: string | null; name?: string | null;
batch?: number | null; batch?: number | null;
updatedAt: string; updatedAt: string;