feat(core): Add workflow publication outbox database table (no-changelog) (#26936)

Co-authored-by: Tomi Turtiainen <10324676+tomi@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
mfsiega 2026-06-01 13:19:36 +02:00 committed by GitHub
parent 6edd4cea36
commit 4cdd079385
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 91 additions and 0 deletions

View File

@ -42,6 +42,8 @@ import { WebhookEntity } from './webhook-entity';
import { WorkflowDependency } from './workflow-dependency-entity';
import { WorkflowEntity } from './workflow-entity';
import { WorkflowHistory } from './workflow-history';
import { WorkflowPublicationOutbox } from './workflow-publication-outbox';
import type { WorkflowPublicationOutboxStatus } from './workflow-publication-outbox';
import { WorkflowPublishHistory } from './workflow-publish-history';
import { WorkflowPublishedVersion } from './workflow-published-version';
import { WorkflowStatistics } from './workflow-statistics';
@ -83,6 +85,8 @@ export {
FolderTagMapping,
AuthProviderSyncHistory,
WorkflowHistory,
WorkflowPublicationOutbox,
type WorkflowPublicationOutboxStatus,
WorkflowPublishedVersion,
WorkflowPublishHistory,
ExecutionData,
@ -129,6 +133,7 @@ export const entities = {
FolderTagMapping,
AuthProviderSyncHistory,
WorkflowHistory,
WorkflowPublicationOutbox,
WorkflowPublishedVersion,
WorkflowPublishHistory,
ExecutionData,

View File

@ -0,0 +1,28 @@
import { Column, Entity, PrimaryGeneratedColumn } from '@n8n/typeorm';
import { WithTimestamps } from './abstract-entity';
export type WorkflowPublicationOutboxStatus =
| 'pending'
| 'in_progress'
| 'completed'
| 'partial_success'
| 'failed';
@Entity({ name: 'workflow_publication_outbox' })
export class WorkflowPublicationOutbox extends WithTimestamps {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'varchar', length: 36 })
workflowId: string;
@Column({ type: 'varchar', length: 36 })
publishedVersionId: string;
@Column({ type: 'varchar', length: 20 })
status: WorkflowPublicationOutboxStatus;
@Column({ type: 'text', nullable: true })
errorMessage: string | null;
}

View File

@ -0,0 +1,42 @@
import type { MigrationContext, ReversibleMigration } from '../migration-types';
/**
* Creates the workflow_publication_outbox table for the transactional outbox
* pattern. Each row represents a pending (or in-progress / completed)
* publication request that the outbox consumer will process asynchronously.
*/
export class CreateWorkflowPublicationOutboxTable1784000000020 implements ReversibleMigration {
async up({ schemaBuilder: { createTable, column } }: MigrationContext) {
await createTable('workflow_publication_outbox').withColumns(
column('id').int.primary.autoGenerate2,
// No foreign keys on workflowId or publishedVersionId: this is a
// transient queue table. If a workflow or history version is
// deleted while a publication is in-flight, the outbox consumer
// will encounter the orphaned record and fail gracefully. Using
// FKs would either silently delete the record (CASCADE) or block
// workflow deletion (RESTRICT), neither of which is desirable.
column('workflowId')
.varchar(36)
.notNull.comment('References workflow_entity.id.'),
column('publishedVersionId')
.varchar(36)
.notNull.comment('References workflow_history.versionId.'),
column('status')
.varchar(20)
.notNull.withEnumCheck([
'pending',
'in_progress',
'completed',
'partial_success',
'failed',
]),
column('errorMessage').text.comment(
'Error details for surfacing failed publications to the user.',
),
).withTimestamps;
}
async down({ schemaBuilder: { dropTable } }: MigrationContext) {
await dropTable('workflow_publication_outbox');
}
}

View File

@ -195,6 +195,7 @@ import { UseSlugAsPrimaryKeyInMcpRegistryServer1784000000016 } from '../common/1
import { AddLastUsedAtToApiKey1784000000017 } from '../common/1784000000017-AddLastUsedAtToApiKey';
import { CreateAgentFilesTable1784000000018 } from '../common/1784000000018-CreateAgentFilesTable';
import { AddCustomTelemetryTagsToProject1784000000019 } from '../common/1784000000019-AddCustomTelemetryTagsToProject';
import { CreateWorkflowPublicationOutboxTable1784000000020 } from '../common/1784000000020-CreateWorkflowPublicationOutboxTable';
import type { Migration } from '../migration-types';
export const postgresMigrations: Migration[] = [
@ -395,4 +396,5 @@ export const postgresMigrations: Migration[] = [
AddLastUsedAtToApiKey1784000000017,
CreateAgentFilesTable1784000000018,
AddCustomTelemetryTagsToProject1784000000019,
CreateWorkflowPublicationOutboxTable1784000000020,
];

View File

@ -188,6 +188,7 @@ import { UseSlugAsPrimaryKeyInMcpRegistryServer1784000000016 } from '../common/1
import { AddLastUsedAtToApiKey1784000000017 } from '../common/1784000000017-AddLastUsedAtToApiKey';
import { CreateAgentFilesTable1784000000018 } from '../common/1784000000018-CreateAgentFilesTable';
import { AddCustomTelemetryTagsToProject1784000000019 } from '../common/1784000000019-AddCustomTelemetryTagsToProject';
import { CreateWorkflowPublicationOutboxTable1784000000020 } from '../common/1784000000020-CreateWorkflowPublicationOutboxTable';
import type { Migration } from '../migration-types';
const sqliteMigrations: Migration[] = [
@ -381,6 +382,7 @@ const sqliteMigrations: Migration[] = [
AddLastUsedAtToApiKey1784000000017,
CreateAgentFilesTable1784000000018,
AddCustomTelemetryTagsToProject1784000000019,
CreateWorkflowPublicationOutboxTable1784000000020,
];
export { sqliteMigrations };

View File

@ -47,6 +47,7 @@ export { WorkflowTagMappingRepository } from './workflow-tag-mapping.repository'
export { SharedWorkflowRepository } from './shared-workflow.repository';
export { SharedCredentialsRepository } from './shared-credentials.repository';
export { WorkflowRepository } from './workflow.repository';
export { WorkflowPublicationOutboxRepository } from './workflow-publication-outbox.repository';
export { WorkflowPublishedVersionRepository } from './workflow-published-version.repository';
export { WorkflowPublishHistoryRepository } from './workflow-publish-history.repository';
export {

View File

@ -0,0 +1,11 @@
import { Service } from '@n8n/di';
import { DataSource, Repository } from '@n8n/typeorm';
import { WorkflowPublicationOutbox } from '../entities/workflow-publication-outbox';
@Service()
export class WorkflowPublicationOutboxRepository extends Repository<WorkflowPublicationOutbox> {
constructor(dataSource: DataSource) {
super(WorkflowPublicationOutbox, dataSource.manager);
}
}