From 4cdd07938534bebec45944d935d75f999088fa72 Mon Sep 17 00:00:00 2001 From: mfsiega <93014743+mfsiega@users.noreply.github.com> Date: Mon, 1 Jun 2026 13:19:36 +0200 Subject: [PATCH] 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 --- packages/@n8n/db/src/entities/index.ts | 5 +++ .../entities/workflow-publication-outbox.ts | 28 +++++++++++++ ...20-CreateWorkflowPublicationOutboxTable.ts | 42 +++++++++++++++++++ .../db/src/migrations/postgresdb/index.ts | 2 + .../@n8n/db/src/migrations/sqlite/index.ts | 2 + packages/@n8n/db/src/repositories/index.ts | 1 + .../workflow-publication-outbox.repository.ts | 11 +++++ 7 files changed, 91 insertions(+) create mode 100644 packages/@n8n/db/src/entities/workflow-publication-outbox.ts create mode 100644 packages/@n8n/db/src/migrations/common/1784000000020-CreateWorkflowPublicationOutboxTable.ts create mode 100644 packages/@n8n/db/src/repositories/workflow-publication-outbox.repository.ts diff --git a/packages/@n8n/db/src/entities/index.ts b/packages/@n8n/db/src/entities/index.ts index ba3286c45b3..cfe0b556599 100644 --- a/packages/@n8n/db/src/entities/index.ts +++ b/packages/@n8n/db/src/entities/index.ts @@ -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, diff --git a/packages/@n8n/db/src/entities/workflow-publication-outbox.ts b/packages/@n8n/db/src/entities/workflow-publication-outbox.ts new file mode 100644 index 00000000000..2c20a6cfa51 --- /dev/null +++ b/packages/@n8n/db/src/entities/workflow-publication-outbox.ts @@ -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; +} diff --git a/packages/@n8n/db/src/migrations/common/1784000000020-CreateWorkflowPublicationOutboxTable.ts b/packages/@n8n/db/src/migrations/common/1784000000020-CreateWorkflowPublicationOutboxTable.ts new file mode 100644 index 00000000000..5df4a11ab25 --- /dev/null +++ b/packages/@n8n/db/src/migrations/common/1784000000020-CreateWorkflowPublicationOutboxTable.ts @@ -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'); + } +} diff --git a/packages/@n8n/db/src/migrations/postgresdb/index.ts b/packages/@n8n/db/src/migrations/postgresdb/index.ts index ec59f5141fd..79ad2e0b054 100644 --- a/packages/@n8n/db/src/migrations/postgresdb/index.ts +++ b/packages/@n8n/db/src/migrations/postgresdb/index.ts @@ -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, ]; diff --git a/packages/@n8n/db/src/migrations/sqlite/index.ts b/packages/@n8n/db/src/migrations/sqlite/index.ts index 18999cf6a59..54394401cba 100644 --- a/packages/@n8n/db/src/migrations/sqlite/index.ts +++ b/packages/@n8n/db/src/migrations/sqlite/index.ts @@ -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 }; diff --git a/packages/@n8n/db/src/repositories/index.ts b/packages/@n8n/db/src/repositories/index.ts index c17f877d18b..ab778c43e3b 100644 --- a/packages/@n8n/db/src/repositories/index.ts +++ b/packages/@n8n/db/src/repositories/index.ts @@ -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 { diff --git a/packages/@n8n/db/src/repositories/workflow-publication-outbox.repository.ts b/packages/@n8n/db/src/repositories/workflow-publication-outbox.repository.ts new file mode 100644 index 00000000000..c66610e0909 --- /dev/null +++ b/packages/@n8n/db/src/repositories/workflow-publication-outbox.repository.ts @@ -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 { + constructor(dataSource: DataSource) { + super(WorkflowPublicationOutbox, dataSource.manager); + } +}