Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
401 changes: 328 additions & 73 deletions docs/plugins/import-export.mdx

Large diffs are not rendered by default.

121 changes: 104 additions & 17 deletions packages/plugin-import-export/src/export/batchProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*/
import type { PayloadRequest, SelectType, Sort, TypedUser, Where } from 'payload'

import type { ExportAfterHook, ExportBeforeHook } from '../types.js'

import { type BatchProcessorOptions } from '../utilities/useBatchProcessor.js'

/**
Expand Down Expand Up @@ -46,6 +48,11 @@ export interface ExportProcessOptions<TDoc = unknown> {
* The export format - affects column tracking for CSV
*/
format: 'csv' | 'json'
/** Lifecycle hooks for this export operation */
hooks?: {
after?: ExportAfterHook
before?: ExportBeforeHook
}
/**
* Maximum number of documents to export
*/
Expand All @@ -58,6 +65,8 @@ export interface ExportProcessOptions<TDoc = unknown> {
* Starting page for pagination (default: 1)
*/
startPage?: number
/** Total number of docs available (used to compute totalBatches for hooks) */
totalDocs?: number
/**
* Transform function to apply to each document
*/
Expand Down Expand Up @@ -98,7 +107,7 @@ export interface ExportResult {
* format: 'csv',
* maxDocs: 1000,
* req,
* transformDoc: (doc) => flattenObject({ doc }),
* transformDoc: (doc) => flattenObject({ data: doc }),
* })
* ```
*/
Expand All @@ -115,7 +124,22 @@ export function createExportBatchProcessor(options: ExportBatchProcessorOptions
const processExport = async <TDoc>(
processOptions: ExportProcessOptions<TDoc>,
): Promise<ExportResult> => {
const { findArgs, format, maxDocs, req, startPage = 1, transformDoc } = processOptions
const {
findArgs,
format,
hooks,
maxDocs,
req,
startPage = 1,
totalDocs,
transformDoc,
} = processOptions

const effectiveDocs =
totalDocs !== undefined
? Math.min(totalDocs, maxDocs === Number.POSITIVE_INFINITY ? totalDocs : maxDocs)
: 0
const totalBatches = effectiveDocs > 0 ? Math.ceil(effectiveDocs / batchSize) : 1

const docs: Record<string, unknown>[] = []
const columnsSet = new Set<string>()
Expand Down Expand Up @@ -144,13 +168,28 @@ export function createExportBatchProcessor(options: ExportBatchProcessorOptions
)
}

for (const doc of result.docs) {
const transformedDoc = transformDoc(doc as TDoc)
docs.push(transformedDoc)
const batchNumber = currentPage - startPage + 1
const originalDocs = result.docs as Record<string, unknown>[]
const batchData = result.docs.map((doc) => transformDoc(doc as TDoc))

const dataToWrite =
hooks?.before && batchData.length > 0
? await hooks.before({
batchNumber,
data: batchData,
format,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
originalData: originalDocs as any,
req,
totalBatches,
})
: batchData

for (const row of dataToWrite) {
docs.push(row)

// Track columns for CSV format
if (format === 'csv') {
for (const key of Object.keys(transformedDoc)) {
for (const key of Object.keys(row)) {
if (!columnsSet.has(key)) {
columnsSet.add(key)
columns.push(key)
Expand All @@ -159,6 +198,17 @@ export function createExportBatchProcessor(options: ExportBatchProcessorOptions
}
}

if (hooks?.after && dataToWrite.length > 0) {
await hooks.after({
batchNumber,
data: dataToWrite,
format,
originalData: originalDocs,
req,
totalBatches,
})
}

fetched += result.docs.length
hasNextPage = result.hasNextPage && fetched < maxDocs
currentPage++
Expand Down Expand Up @@ -187,7 +237,22 @@ export function createExportBatchProcessor(options: ExportBatchProcessorOptions
async function* streamExport<TDoc>(
processOptions: ExportProcessOptions<TDoc>,
): AsyncGenerator<{ columns: string[]; docs: Record<string, unknown>[] }> {
const { findArgs, format, maxDocs, req, startPage = 1, transformDoc } = processOptions
const {
findArgs,
format,
hooks,
maxDocs,
req,
startPage = 1,
totalDocs,
transformDoc,
} = processOptions

const effectiveDocs =
totalDocs !== undefined
? Math.min(totalDocs, maxDocs === Number.POSITIVE_INFINITY ? totalDocs : maxDocs)
: 0
const totalBatches = effectiveDocs > 0 ? Math.ceil(effectiveDocs / batchSize) : 1

const columnsSet = new Set<string>()
const columns: string[] = []
Expand Down Expand Up @@ -215,15 +280,26 @@ export function createExportBatchProcessor(options: ExportBatchProcessorOptions
)
}

const batchDocs: Record<string, unknown>[] = []

for (const doc of result.docs) {
const transformedDoc = transformDoc(doc as TDoc)
batchDocs.push(transformedDoc)

// Track columns for CSV format
const batchNumber = currentPage - startPage + 1
const originalDocs = result.docs as Record<string, unknown>[]
const batchData = result.docs.map((doc) => transformDoc(doc as TDoc))

const dataToWrite =
hooks?.before && batchData.length > 0
? await hooks.before({
batchNumber,
data: batchData,
format,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
originalData: originalDocs as any,
req,
totalBatches,
})
: batchData

for (const row of dataToWrite) {
if (format === 'csv') {
for (const key of Object.keys(transformedDoc)) {
for (const key of Object.keys(row)) {
if (!columnsSet.has(key)) {
columnsSet.add(key)
columns.push(key)
Expand All @@ -232,7 +308,18 @@ export function createExportBatchProcessor(options: ExportBatchProcessorOptions
}
}

yield { columns: [...columns], docs: batchDocs }
yield { columns: [...columns], docs: dataToWrite }

if (hooks?.after && dataToWrite.length > 0) {
await hooks.after({
batchNumber,
data: dataToWrite,
format,
originalData: originalDocs,
req,
totalBatches,
})
}

fetched += result.docs.length
hasNextPage = result.hasNextPage && fetched < maxDocs
Expand Down
Loading
Loading