Fix JSON circular reference serialization and use PayloadCMS generated types

- Replace duplicate type definitions with PayloadCMS generated types
- Fix workflow context serialization with safeSerialize() method
- Resolve type mismatches (id: string vs number)
- Update all imports to use PayloadWorkflow type
- Ensure workflow runs are created successfully without serialization errors

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-01 18:02:21 +02:00
parent 964b11c0c9
commit 6397250045
13 changed files with 477 additions and 75 deletions

View File

@@ -0,0 +1,122 @@
import { NextResponse } from 'next/server'
import { getPayload } from 'payload'
import config from '../../payload.config'
export async function GET() {
console.log('Starting workflow trigger test...')
// Get payload instance
const payload = await getPayload({ config })
try {
// Create a test user
const user = await payload.create({
collection: 'users',
data: {
email: `test-${Date.now()}@example.com`,
password: 'password123'
}
})
console.log('Created test user:', user.id)
// Create a workflow with collection trigger
const workflow = await payload.create({
collection: 'workflows',
data: {
name: 'Test Post Creation Workflow',
description: 'Triggers when a post is created',
triggers: [
{
type: 'collection-trigger',
collectionSlug: 'posts',
operation: 'create'
}
],
steps: [
{
name: 'log-post',
taskSlug: 'http-request-step',
input: JSON.stringify({
url: 'https://httpbin.org/post',
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: {
message: 'Post created',
postId: '$.trigger.doc.id',
postTitle: '$.trigger.doc.title'
}
})
}
]
},
user: user.id
})
console.log('Created workflow:', workflow.id, workflow.name)
console.log('Workflow triggers:', JSON.stringify(workflow.triggers, null, 2))
// Create a post to trigger the workflow
console.log('Creating post to trigger workflow...')
const post = await payload.create({
collection: 'posts',
data: {
title: 'Test Post',
content: 'This should trigger the workflow',
_status: 'published'
},
user: user.id
})
console.log('Created post:', post.id)
// Wait a bit for workflow to execute
await new Promise(resolve => setTimeout(resolve, 3000))
// Check for workflow runs
const runs = await payload.find({
collection: 'workflow-runs',
where: {
workflow: {
equals: workflow.id
}
}
})
console.log('Workflow runs found:', runs.totalDocs)
const result = {
success: runs.totalDocs > 0,
workflowId: workflow.id,
postId: post.id,
runsFound: runs.totalDocs,
runs: runs.docs.map(r => ({
id: r.id,
status: r.status,
triggeredBy: r.triggeredBy,
startedAt: r.startedAt,
completedAt: r.completedAt,
error: r.error
}))
}
if (runs.totalDocs > 0) {
console.log('✅ SUCCESS: Workflow was triggered!')
console.log('Run status:', runs.docs[0].status)
console.log('Run context:', JSON.stringify(runs.docs[0].context, null, 2))
} else {
console.log('❌ FAILURE: Workflow was not triggered')
}
return NextResponse.json(result)
} catch (error) {
console.error('Test failed:', error)
return NextResponse.json({
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
}, { status: 500 })
}
}

View File

@@ -217,7 +217,7 @@ export interface Workflow {
/**
* Collection that triggers the workflow
*/
collection?: 'posts' | null;
collectionSlug?: 'posts' | null;
/**
* Collection operation that triggers the workflow
*/
@@ -242,6 +242,10 @@ export interface Workflow {
* Timezone for cron execution (e.g., "America/New_York", "Europe/London"). Defaults to UTC.
*/
timezone?: string | null;
/**
* JSONPath expression that must evaluate to true for this trigger to execute the workflow (e.g., "$.doc.status == 'published'")
*/
condition?: string | null;
id?: string | null;
}[]
| null;
@@ -262,6 +266,10 @@ export interface Workflow {
* Step names that must complete before this step can run
*/
dependencies?: string[] | null;
/**
* JSONPath expression that must evaluate to true for this step to execute (e.g., "$.trigger.doc.status == 'published'")
*/
condition?: string | null;
id?: string | null;
}[]
| null;
@@ -584,13 +592,14 @@ export interface WorkflowsSelect<T extends boolean = true> {
| T
| {
type?: T;
collection?: T;
collectionSlug?: T;
operation?: T;
webhookPath?: T;
global?: T;
globalOperation?: T;
cronExpression?: T;
timezone?: T;
condition?: T;
id?: T;
};
steps?:
@@ -600,6 +609,7 @@ export interface WorkflowsSelect<T extends boolean = true> {
name?: T;
input?: T;
dependencies?: T;
condition?: T;
id?: T;
};
updatedAt?: T;
@@ -741,7 +751,7 @@ export interface TaskCreateDocument {
/**
* The collection slug to create a document in
*/
collection: string;
collectionSlug: string;
/**
* The document data to create
*/

132
dev/simple-trigger.spec.ts Normal file
View File

@@ -0,0 +1,132 @@
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import type { Payload } from 'payload'
import { getPayload } from 'payload'
import config from './payload.config'
describe('Workflow Trigger Test', () => {
let payload: Payload
beforeAll(async () => {
payload = await getPayload({ config: await config })
}, 60000)
afterAll(async () => {
if (!payload) return
try {
// Clear test data
const workflows = await payload.find({
collection: 'workflows',
limit: 100
})
for (const workflow of workflows.docs) {
await payload.delete({
collection: 'workflows',
id: workflow.id
})
}
const runs = await payload.find({
collection: 'workflow-runs',
limit: 100
})
for (const run of runs.docs) {
await payload.delete({
collection: 'workflow-runs',
id: run.id
})
}
const posts = await payload.find({
collection: 'posts',
limit: 100
})
for (const post of posts.docs) {
await payload.delete({
collection: 'posts',
id: post.id
})
}
} catch (error) {
console.warn('Cleanup failed:', error)
}
}, 30000)
it('should create a workflow run when a post is created', async () => {
// Create a workflow with collection trigger
const workflow = await payload.create({
collection: 'workflows',
data: {
name: 'Test Post Creation Workflow',
description: 'Triggers when a post is created',
triggers: [
{
type: 'collection-trigger',
collectionSlug: 'posts',
operation: 'create'
}
],
steps: [
{
name: 'log-post',
step: 'http-request-step',
input: {
url: 'https://httpbin.org/post',
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: {
message: 'Post created',
postId: '$.trigger.doc.id',
postTitle: '$.trigger.doc.content'
}
}
}
]
}
})
expect(workflow).toBeDefined()
expect(workflow.id).toBeDefined()
// Create a post to trigger the workflow
const post = await payload.create({
collection: 'posts',
data: {
content: 'This should trigger the workflow'
}
})
expect(post).toBeDefined()
expect(post.id).toBeDefined()
// Wait a bit for workflow to execute
await new Promise(resolve => setTimeout(resolve, 3000))
// Check for workflow runs
const runs = await payload.find({
collection: 'workflow-runs',
where: {
workflow: {
equals: workflow.id
}
},
limit: 10
})
expect(runs.totalDocs).toBeGreaterThan(0)
expect(runs.docs[0].workflow).toBe(typeof workflow.id === 'object' ? workflow.id.toString() : workflow.id)
console.log('✅ Workflow run created successfully!')
console.log(`Run status: ${runs.docs[0].status}`)
console.log(`Run ID: ${runs.docs[0].id}`)
if (runs.docs[0].status === 'failed' && runs.docs[0].error) {
console.log(`Error: ${runs.docs[0].error}`)
}
}, 30000)
})

104
dev/test-trigger.ts Normal file
View File

@@ -0,0 +1,104 @@
import type { Payload } from 'payload'
import { getPayload } from 'payload'
import config from './payload.config'
async function testWorkflowTrigger() {
console.log('Starting workflow trigger test...')
// Get payload instance
const payload = await getPayload({ config })
try {
// Create a test user
const user = await payload.create({
collection: 'users',
data: {
email: 'test@example.com',
password: 'password123'
}
})
console.log('Created test user:', user.id)
// Create a workflow with collection trigger
const workflow = await payload.create({
collection: 'workflows',
data: {
name: 'Test Post Creation Workflow',
description: 'Triggers when a post is created',
triggers: [
{
type: 'collection-trigger',
collectionSlug: 'posts',
operation: 'create'
}
],
steps: [
{
name: 'log-post',
step: 'http-request-step',
input: {
url: 'https://httpbin.org/post',
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: {
message: 'Post created',
postId: '$.trigger.doc.id',
postTitle: '$.trigger.doc.title'
}
}
}
]
},
user: user.id
})
console.log('Created workflow:', workflow.id)
// Create a post to trigger the workflow
console.log('Creating post to trigger workflow...')
const post = await payload.create({
collection: 'posts',
data: {
title: 'Test Post',
content: 'This should trigger the workflow',
_status: 'published'
},
user: user.id
})
console.log('Created post:', post.id)
// Wait a bit for workflow to execute
await new Promise(resolve => setTimeout(resolve, 2000))
// Check for workflow runs
const runs = await payload.find({
collection: 'workflow-runs',
where: {
workflow: {
equals: workflow.id
}
}
})
console.log('Workflow runs found:', runs.totalDocs)
if (runs.totalDocs > 0) {
console.log('✅ SUCCESS: Workflow was triggered!')
console.log('Run status:', runs.docs[0].status)
console.log('Run context:', JSON.stringify(runs.docs[0].context, null, 2))
} else {
console.log('❌ FAILURE: Workflow was not triggered')
}
} catch (error) {
console.error('Test failed:', error)
} finally {
await payload.shutdown()
}
}
testWorkflowTrigger().catch(console.error)