mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-19 16:55:02 +02:00
103 lines
3.3 KiB
TypeScript
103 lines
3.3 KiB
TypeScript
import { Logger } from '@n8n/backend-common';
|
|
import {
|
|
WorkflowPublishedVersionRepository,
|
|
type PublishedWorkflowDataForExecution,
|
|
type WorkflowEntity,
|
|
type WorkflowHistory,
|
|
} from '@n8n/db';
|
|
import { Service } from '@n8n/di';
|
|
import { ensureError } from '@n8n/utils/errors/ensure-error';
|
|
|
|
import { CacheService } from '@/services/cache/cache.service';
|
|
|
|
export type { PublishedWorkflowDataForExecution };
|
|
|
|
export type PublishedWorkflowData = {
|
|
workflow: WorkflowEntity;
|
|
publishedVersion: WorkflowHistory;
|
|
};
|
|
|
|
const NO_EXPIRY = 0;
|
|
|
|
const cacheKey = (workflowId: string) => `workflow-published-data:${workflowId}`;
|
|
|
|
@Service()
|
|
export class WorkflowPublishedDataService {
|
|
constructor(
|
|
private readonly logger: Logger,
|
|
private readonly workflowPublishedVersionRepository: WorkflowPublishedVersionRepository,
|
|
private readonly cacheService: CacheService,
|
|
) {
|
|
this.logger = this.logger.scoped('poll-trigger');
|
|
}
|
|
|
|
/**
|
|
* Resolves a workflow's published version from the database: the workflow
|
|
* entity and the `WorkflowHistory` row that the `workflow_published_version`
|
|
* mapping currently points at, or `null` when there is no published version.
|
|
*/
|
|
async getPublishedWorkflowData(workflowId: string): Promise<PublishedWorkflowData | null> {
|
|
const record =
|
|
await this.workflowPublishedVersionRepository.getPublishedVersionWithRelations(workflowId);
|
|
|
|
if (!record?.publishedVersion || !record.workflow) {
|
|
return null;
|
|
}
|
|
|
|
return { workflow: record.workflow, publishedVersion: record.publishedVersion };
|
|
}
|
|
|
|
async getPublishedWorkflowDataForExecution(
|
|
workflowId: string,
|
|
): Promise<PublishedWorkflowDataForExecution | null> {
|
|
return await this.workflowPublishedVersionRepository.getPublishedVersionForExecution(
|
|
workflowId,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get the published workflow data from the cache, falling back to the database if not found.
|
|
* The cache is not refreshed here on a miss. Only the publication applier should refresh the
|
|
* cache to ensure it never disagrees with the database. The cached value is a small execution
|
|
* payload, not a full WorkflowEntity with relations.
|
|
*/
|
|
async getCachedPublishedWorkflowDataForExecution(
|
|
workflowId: string,
|
|
): Promise<PublishedWorkflowDataForExecution | null> {
|
|
try {
|
|
const cached = await this.cacheService.get<PublishedWorkflowDataForExecution>(
|
|
cacheKey(workflowId),
|
|
);
|
|
if (cached) return cached;
|
|
} catch (error) {
|
|
this.logger.warn('Failed to read published-version cache; falling back to the database', {
|
|
workflowId,
|
|
error: ensureError(error).message,
|
|
});
|
|
}
|
|
return await this.getPublishedWorkflowDataForExecution(workflowId);
|
|
}
|
|
|
|
/**
|
|
* Drops the cached entry.
|
|
*/
|
|
async invalidateCache(workflowId: string): Promise<void> {
|
|
await this.cacheService.delete(cacheKey(workflowId));
|
|
}
|
|
|
|
/**
|
|
* Repopulates the cached entry from current database state.
|
|
* If there is no published version, the cache entry is deleted.
|
|
* This should only be called by the publication applier to ensure the cache never disagrees with the database.
|
|
*/
|
|
async refreshCache(workflowId: string): Promise<void> {
|
|
const key = cacheKey(workflowId);
|
|
const data = await this.getPublishedWorkflowDataForExecution(workflowId);
|
|
if (data) {
|
|
await this.cacheService.set(key, data, NO_EXPIRY);
|
|
} else {
|
|
await this.cacheService.delete(key);
|
|
}
|
|
}
|
|
}
|