refactor(core): Move shared cache infrastructure to backend services (no-changelog) (#39851)

This commit is contained in:
Charlie Kolb 2026-09-29 12:55:05 +00:00 committed by GitHub
parent bf6ba26a7f
commit 499d64181a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
145 changed files with 207 additions and 214 deletions

View File

@ -93,6 +93,10 @@ const config: KnipConfig = {
'@types/psl',
],
}),
'packages/@n8n/backend-services': pkg({
// ioredis-mock is loaded with vi.importActual, which knip does not link.
ignoreDependencies: ['ioredis-mock'],
}),
'packages/@n8n/instance-ai': pkg({
// psl is loaded with a dynamic import, which knip does not link to @types.
ignoreDependencies: ['@types/psl'],

View File

@ -29,9 +29,11 @@ and infrastructure services that need the persistence layer or that only
- `EventService`: the shared, typed event bus.
- `UrlService`: instance and webhook URLs.
- `CacheService`: application cache access.
- `RedisClientService`: shared Redis connections and support code.
The next PRs move `CacheService`, `RedisClientService`, `ProtectedResourceRegistry`,
`RoleService`, the finder services and the scope checks here, one area at a time.
The next PRs move `ProtectedResourceRegistry`, `RoleService`, the finder services
and the scope checks here, one area at a time.
### Register event payloads

View File

@ -34,8 +34,6 @@ export default defineConfig(
{
files: ['./test/**/*.ts', './src/**/__tests__/**/*.ts'],
rules: {
'n8n-local-rules/misplaced-n8n-typeorm-import': 'off',
'n8n-local-rules/no-type-unsafe-event-emitter': 'off',
// `vi.importActual<typeof import('x')>('x')` needs inline import types.
'@typescript-eslint/consistent-type-imports': ['error', { disallowTypeAnnotations: false }],
},

View File

@ -30,15 +30,4 @@ export default defineConfig({
],
'n8n-local-rules/no-type-unsafe-event-emitter': 'error',
},
overrides: [
{
files: ['./test/**/*.ts', './src/**/__tests__/**/*.ts'],
// An override that names a jsPlugin rule must re-declare the plugin.
jsPlugins: ['@n8n/eslint-config/plugin'],
rules: {
'n8n-local-rules/misplaced-n8n-typeorm-import': 'off',
'n8n-local-rules/no-type-unsafe-event-emitter': 'off',
},
},
],
});

View File

@ -25,14 +25,25 @@
"dependencies": {
"@n8n/backend-common": "workspace:*",
"@n8n/config": "workspace:*",
"@n8n/di": "workspace:*"
"@n8n/constants": "workspace:*",
"@n8n/decorators": "workspace:*",
"@n8n/di": "workspace:*",
"@n8n/errors": "workspace:*",
"@n8n/utils": "workspace:*",
"cache-manager": "catalog:",
"ioredis": "5.3.2",
"n8n-workflow": "workspace:*"
},
"devDependencies": {
"@n8n/eslint-config": "workspace:*",
"@n8n/oxlint-config": "workspace:*",
"@n8n/typescript-config": "workspace:*",
"@n8n/vitest-config": "workspace:*",
"@types/lodash": "catalog:",
"@vitest/coverage-v8": "catalog:",
"eslint": "catalog:",
"ioredis-mock": "^8.8.1",
"lodash": "catalog:",
"oxlint": "catalog:",
"oxlint-tsgolint": "catalog:",
"typescript": "catalog:typescript",

View File

@ -1,2 +1,6 @@
export { EventService, type EventMap } from './events/event.service';
export { UncacheableValueError } from './errors/cache-errors/uncacheable-value.error';
export { CacheService } from './services/cache/cache.service';
export { RedisClientService } from './services/redis-client.service';
export type { RedisClientType } from './services/redis.types';
export { UrlService } from './services/url.service';

View File

@ -1,10 +1,10 @@
import { Logger } from '@n8n/backend-common';
import { mockInstance } from '@n8n/backend-test-utils';
import { GlobalConfig } from '@n8n/config';
import type { Logger } from '@n8n/backend-common';
import type { GlobalConfig } from '@n8n/config';
import Redis from 'ioredis';
import type { Mock } from 'vitest';
import { mock } from 'vitest-mock-extended';
import { RedisClientService } from '@/services/redis-client.service';
import { RedisClientService } from '../redis-client.service';
type EventHandler = (...args: unknown[]) => void;
@ -27,10 +27,10 @@ vi.mock('ioredis', () => {
});
describe('RedisClientService', () => {
const logger = mockInstance(Logger);
const logger = mock<Logger>();
logger.scoped.mockReturnValue(logger);
const globalConfig = mockInstance(GlobalConfig, {
const globalConfig = mock<GlobalConfig>({
queue: {
bull: {
redis: {

View File

@ -1,7 +1,7 @@
import { Container } from '@n8n/di';
import { mock } from 'vitest-mock-extended';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '../cache.service';
const cacheService = Container.get(CacheService);
const store = mock<NonNullable<CacheService['cache']>['store']>({ isCacheable: () => true });

View File

@ -3,11 +3,12 @@ import { Container } from '@n8n/di';
import { sleep } from '@n8n/utils/sleep';
import random from 'lodash/random';
import config from '@/config';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '../cache.service';
vi.mock('ioredis', () => {
const Redis = require('ioredis-mock');
vi.mock('ioredis', async () => {
const { default: Redis } = await vi.importActual<{
default: new (...args: unknown[]) => unknown;
}>('ioredis-mock');
return {
// Must be a function expression (not method shorthand) so it is
@ -33,7 +34,6 @@ for (const backend of ['memory', 'redis'] as const) {
afterEach(async () => {
await cacheService.reset();
config.load(config.default);
});
describe('init', () => {

View File

@ -2,18 +2,14 @@ import { TypedEmitter } from '@n8n/backend-common';
import { GlobalConfig } from '@n8n/config';
import { Time } from '@n8n/constants';
import { Container, Service } from '@n8n/di';
import { isRecord } from '@n8n/utils/is-record';
import { caching } from 'cache-manager';
import { jsonStringify, UserError } from 'n8n-workflow';
import { UncacheableValueError } from '@/errors/cache-errors/uncacheable-value.error';
import { REDIS_TTL_KEY_MISSING } from '@/services/cache/cache.constants';
import type {
TaggedRedisCache,
TaggedMemoryCache,
MaybeHash,
Hash,
} from '@/services/cache/cache.types';
import { isObject } from '@/utils';
import { UncacheableValueError } from '../../errors/cache-errors/uncacheable-value.error';
import { REDIS_TTL_KEY_MISSING } from './cache.constants';
import type { TaggedRedisCache, TaggedMemoryCache, MaybeHash, Hash } from './cache.types';
type CacheEvents = {
'metrics.cache.hit': never;
@ -29,7 +25,8 @@ export class CacheService extends TypedEmitter<CacheEvents> {
super();
}
private cache: TaggedRedisCache | TaggedMemoryCache;
// Set by `init()`, which every public method awaits before use.
private cache!: TaggedRedisCache | TaggedMemoryCache;
async init() {
const { backend } = this.globalConfig.cache;
@ -54,7 +51,7 @@ export class CacheService extends TypedEmitter<CacheEvents> {
extraOptions: { keyPrefix: prefix },
});
const { redisStoreUsingClient } = await import('@/services/cache/redis.cache-manager.js');
const { redisStoreUsingClient } = await import('./redis.cache-manager.js');
const redisStore = redisStoreUsingClient(redisClient, {
ttl: this.globalConfig.cache.redis.ttl,
});
@ -379,7 +376,7 @@ export class CacheService extends TypedEmitter<CacheEvents> {
}
const isEmptyArray = Array.isArray(value) && value.length === 0;
const isEmptyObject = isObject(value) && Object.keys(value).length === 0;
const isEmptyObject = isRecord(value) && Object.keys(value).length === 0;
if (isEmptyArray || isEmptyObject) {
// Redis adapters may return [] or {} for missing string or hash keys after restart.
const keyExists = await this.doesRedisKeyExist(key);

View File

@ -1,6 +1,6 @@
import type { MemoryCache } from 'cache-manager';
import type { RedisCache } from '@/services/cache/redis.cache-manager';
import type { RedisCache } from './redis.cache-manager';
export type TaggedRedisCache = RedisCache & { kind: 'redis' };

View File

@ -5,7 +5,7 @@ import { Service } from '@n8n/di';
import ioRedis from 'ioredis';
import type { Cluster, ClusterOptions, RedisOptions } from 'ioredis';
import type { RedisClientType } from '../scaling/redis/redis.types';
import type { RedisClientType } from './redis.types';
type RedisEventMap = {
'connection-lost': number;

View File

@ -1,23 +0,0 @@
import { mkdirSync, mkdtempSync, writeFileSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
// Same instance setup as packages/cli/test/setup-test-folder.ts, so a moved test finds it.
process.env.N8N_ENCRYPTION_KEY = 'test_key';
const baseDir = join(tmpdir(), 'n8n-tests/');
mkdirSync(baseDir, { recursive: true });
const testDir = mkdtempSync(baseDir);
mkdirSync(join(testDir, '.n8n'));
process.env.N8N_USER_FOLDER = testDir;
process.env.N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS = 'true';
writeFileSync(
join(testDir, '.n8n/config'),
JSON.stringify({ encryptionKey: 'test_key', instanceId: '123' }),
{
encoding: 'utf-8',
mode: 0o600,
},
);

View File

@ -5,7 +5,6 @@ export default mergeConfig(createVitestConfigWithDecorators(), {
test: {
// One fork per file: tests register mocks in the shared `@n8n/di` Container.
pool: 'forks',
setupFiles: ['./test/setup-test-folder.ts'],
// Vitest's default exclude does not cover dist; stale compiled tests there fail as CJS.
exclude: [...configDefaults.exclude, '**/dist/**'],
},

View File

@ -111,7 +111,6 @@
"concurrently": "^8.2.0",
"esbuild": "catalog:",
"form-data": "catalog:",
"ioredis-mock": "^8.8.1",
"minifaker": "1.34.1",
"mjml": "^4.15.3",
"n8n-containers": "workspace:*",
@ -205,7 +204,6 @@
"ai": "catalog:",
"bcryptjs": "2.4.3",
"bull": "4.16.4",
"cache-manager": "catalog:",
"change-case": "catalog:",
"chat": "catalog:",
"class-transformer": "0.5.1",

View File

@ -1,6 +1,6 @@
import { Service } from '@n8n/di';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
@Service()
export class ActivationErrorsService {

View File

@ -1,7 +1,7 @@
import type { Mocked } from 'vitest';
import { mock } from 'vitest-mock-extended';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import { CollaborationState } from '../collaboration.state';

View File

@ -5,7 +5,7 @@ import { Service } from '@n8n/di';
import type { Workflow } from 'n8n-workflow';
import { jsonParse } from 'n8n-workflow';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
// clientId -> "userId|lastSeen"
type WorkflowCacheHash = Record<string, string>;

View File

@ -17,7 +17,7 @@ import type { RedisResponsePublisher } from '@/modules/engine-v2/response-channe
import { NodeTypes } from '@/node-types';
import { OtelService } from '@/modules/otel/otel.service';
import { PostHogClient } from '@/posthog';
import { RedisClientService } from '@/services/redis-client.service';
import { RedisClientService } from '@n8n/backend-services';
import { ShutdownService } from '@/shutdown/shutdown.service';
import { TaskRunnerModule } from '@/task-runners/task-runner-module';

View File

@ -12,7 +12,7 @@ import { PubSubRegistry } from '@/scaling/pubsub/pubsub.registry';
import { Subscriber } from '@/scaling/pubsub/subscriber.service';
import { SystemTaskRunner } from '@/scheduling/system-tasks/system-task-runner';
import { JwtService } from '@/services/jwt.service';
import { RedisClientService } from '@/services/redis-client.service';
import { RedisClientService } from '@n8n/backend-services';
import { WebhookServer } from '@/webhooks/webhook-server';
import { BaseCommand } from '../base-command';

View File

@ -33,7 +33,7 @@ import { WorkerServer } from '@/scaling/worker-server';
import { WorkerStatusService } from '@/scaling/worker-status.service.ee';
import { SystemTaskRunner } from '@/scheduling/system-tasks/system-task-runner';
import { JwtService } from '@/services/jwt.service';
import { RedisClientService } from '@/services/redis-client.service';
import { RedisClientService } from '@n8n/backend-services';
import { ShutdownService } from '@/shutdown/shutdown.service';
import { TaskRunnerModule } from '@/task-runners/task-runner-module';

View File

@ -32,7 +32,7 @@ import { MfaService } from '@/mfa/mfa.service';
import { LogStreamingDestinationService } from '@/modules/log-streaming.ee/log-streaming-destination.service';
import { Push } from '@/push';
import { WorkflowScheduledJobOwner } from '@/scheduling/workflow-scheduled-job-owner';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { FrontendService } from '@/services/frontend.service';
import { PasswordUtility } from '@/services/password.utility';
import { WorkflowStaticDataService } from '@/workflows/workflow-static-data.service';

View File

@ -1,4 +1,3 @@
import type { EventService } from '@n8n/backend-services';
import { createTeamProject, linkUserToProject, testDb } from '@n8n/backend-test-utils';
import { UNLIMITED_LICENSE_QUOTA } from '@n8n/constants';
import { VariablesRepository } from '@n8n/db';
@ -7,7 +6,7 @@ import type { AssignableProjectRole } from '@n8n/permissions';
import type { Mock } from 'vitest';
import { mock } from 'vitest-mock-extended';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService, type EventService } from '@n8n/backend-services';
import { ProjectService } from '@/services/project.service.ee';
import { createAdmin, createMember } from '@test-integration/db/users';
import { createProjectVariable, createVariable } from '@test-integration/db/variables';

View File

@ -4,7 +4,6 @@ import {
NEW_VARIABLE_KEY_REGEX,
} from '@n8n/api-types';
import { LicenseState } from '@n8n/backend-common';
import { EventService } from '@n8n/backend-services';
import { UNLIMITED_LICENSE_QUOTA } from '@n8n/constants';
import type { User, Variables } from '@n8n/db';
import { generateNanoId, VariablesRepository } from '@n8n/db';
@ -15,7 +14,7 @@ import { FeatureNotLicensedError } from '@/errors/feature-not-licensed.error';
import { ForbiddenError, NotFoundError } from '@n8n/errors';
import { VariableCountLimitReachedError } from '@/errors/variable-count-limit-reached.error';
import { VariableValidationError } from '@/errors/variable-validation.error';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService, EventService } from '@n8n/backend-services';
import { ProjectService } from '@/services/project.service.ee';
const projectVariableScopes: Partial<Record<Scope, Scope>> = {

View File

@ -4,7 +4,7 @@ import type { WorkflowRepository } from '@n8n/db';
import { mock } from 'vitest-mock-extended';
import promClient from 'prom-client';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import { PrometheusActiveWorkflowMetricsService } from '../active-workflow-metrics.service';

View File

@ -6,7 +6,7 @@ import promClient from 'prom-client';
import { PrometheusCacheMetricsService } from '../cache-metrics.service';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
vi.mock('prom-client');

View File

@ -1,6 +1,6 @@
import { mock } from 'vitest-mock-extended';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import { CachedMetricQuery } from '../cached-metric-query';

View File

@ -9,7 +9,7 @@ import { mock } from 'vitest-mock-extended';
import { PrometheusSchedulerMetricsService } from '../scheduler-metrics.service';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
vi.mock('prom-client');

View File

@ -5,7 +5,7 @@ import type { InstanceSettings } from 'n8n-core';
import promClient from 'prom-client';
import { mock } from 'vitest-mock-extended';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import { PrometheusWorkflowInfoMetricsService } from '../workflow-info-metrics.service';

View File

@ -1,4 +1,3 @@
import type { EventService } from '@n8n/backend-services';
import type { Mock } from 'vitest';
import { mockInstance } from '@n8n/backend-test-utils';
import type { WorkflowsConfig } from '@n8n/config';
@ -10,7 +9,7 @@ import promClient from 'prom-client';
import { PrometheusWorkflowPublicationMetricsService } from '../workflow-publication-metrics.service';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService, EventService } from '@n8n/backend-services';
vi.mock('prom-client');

View File

@ -4,7 +4,7 @@ import type { LicenseMetricsRepository } from '@n8n/db';
import { mock } from 'vitest-mock-extended';
import promClient from 'prom-client';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import { PrometheusWorkflowStatisticsMetricsService } from '../workflow-statistics-metrics.service';

View File

@ -4,7 +4,7 @@ import { WorkflowRepository } from '@n8n/db';
import { Service } from '@n8n/di';
import promClient from 'prom-client';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import type { PrometheusMetricsCollector } from './base';
import { CachedMetricQuery } from './cached-metric-query';

View File

@ -2,7 +2,7 @@ import { PrometheusMetricsConfig } from '@n8n/config';
import { Service } from '@n8n/di';
import promClient from 'prom-client';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import type { PrometheusMetricsCollector } from './base';

View File

@ -1,6 +1,6 @@
import type { JsonValue } from 'n8n-workflow';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
type CachedMetricQueryOpts<T extends JsonValue> = {
cacheService: CacheService;

View File

@ -6,7 +6,7 @@ import type { MisfireCount, SchedulerMetrics } from '@n8n/scheduler';
import { InstanceSettings } from 'n8n-core';
import promClient from 'prom-client';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import type { PrometheusMetricsCollector } from './base';
import { CachedMetricQuery } from './cached-metric-query';

View File

@ -5,7 +5,7 @@ import { Service } from '@n8n/di';
import { InstanceSettings } from 'n8n-core';
import promClient from 'prom-client';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import type { PrometheusMetricsCollector } from './base';
import { CachedMetricQuery } from './cached-metric-query';

View File

@ -1,4 +1,3 @@
import { EventService } from '@n8n/backend-services';
import { PrometheusMetricsConfig, WorkflowsConfig } from '@n8n/config';
import { Time } from '@n8n/constants';
import { WorkflowPublicationOutboxRepository, WorkflowPublicationOutboxStatus } from '@n8n/db';
@ -6,7 +5,7 @@ import { Service } from '@n8n/di';
import { InstanceSettings } from 'n8n-core';
import promClient from 'prom-client';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService, EventService } from '@n8n/backend-services';
import type { PrometheusMetricsCollector } from './base';
import { CachedMetricQuery } from './cached-metric-query';

View File

@ -4,7 +4,7 @@ import { LicenseMetricsRepository } from '@n8n/db';
import { Service } from '@n8n/di';
import promClient from 'prom-client';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import type { PrometheusMetricsCollector } from './base';
import { CachedMetricQuery } from './cached-metric-query';

View File

@ -5,7 +5,7 @@ import type { Cipher } from 'n8n-core';
import type { Mocked } from 'vitest';
import { mock } from 'vitest-mock-extended';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import { MFA_ENFORCE_SETTING } from '../constants';
import { MFA_CACHE_KEY, MfaService } from '../mfa.service';

View File

@ -6,7 +6,7 @@ import { v4 as uuid } from 'uuid';
import { InvalidMfaCodeError } from '@/errors/response-errors/invalid-mfa-code.error';
import { InvalidMfaRecoveryCodeError } from '@/errors/response-errors/invalid-mfa-recovery-code-error';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { MFA_ENFORCE_SETTING } from './constants';
import { TOTPService } from './totp.service';

View File

@ -3,7 +3,7 @@ import type { GlobalConfig } from '@n8n/config';
import { Container } from '@n8n/di';
import { mock } from 'vitest-mock-extended';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { CallbackStore } from '../callback-store';

View File

@ -9,7 +9,7 @@ import type { CredentialsService } from '@/credentials/credentials.service';
import type { CredentialsFinderService } from '@/credentials/credentials-finder.service';
import type { CredentialsOverwrites } from '@/credentials-overwrites';
import { BadRequestError } from '@n8n/errors';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import type { ProjectService } from '@/services/project.service.ee';
import type { UrlService } from '@n8n/backend-services';

View File

@ -2,7 +2,7 @@
import type { UserRepository } from '@n8n/db';
import { mock } from 'vitest-mock-extended';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import { BadRequestError } from '@n8n/errors';
import type { ProjectService } from '@/services/project.service.ee';

View File

@ -5,7 +5,7 @@ import type { Cipher } from 'n8n-core';
import { mock } from 'vitest-mock-extended';
import type { CredentialsService } from '@/credentials/credentials.service';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import type { UrlService } from '@n8n/backend-services';
import type { AgentIntegrationManagementService } from '../../agent-integration-management.service';

View File

@ -14,7 +14,7 @@ import { isRecord } from '@n8n/utils/is-record';
import type { Attachment, Author, CardElement, Chat, Message, Thread } from 'chat';
import { UserError, type Logger } from 'n8n-workflow';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { AgentChatAttachmentService } from '../agent-chat-attachment.service';
import { AgentMessageQueueService } from '../agent-message-queue.service';

View File

@ -2,7 +2,7 @@ import { createHash, randomBytes } from 'node:crypto';
import { LockNamespace, type LockService } from '@n8n/backend-common';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
export interface CallbackPayload {
actionId: string;

View File

@ -20,7 +20,7 @@ import { CredentialsFinderService } from '@/credentials/credentials-finder.servi
import { CredentialsService } from '@/credentials/credentials.service';
import { CredentialsOverwrites } from '@/credentials-overwrites';
import { BadRequestError, NotFoundError } from '@n8n/errors';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { SlackMethodsService } from './slack-methods.service';
import {

View File

@ -5,7 +5,7 @@ import { Service } from '@n8n/di';
import { randomBytes } from 'node:crypto';
import { BadRequestError, ForbiddenError, NotFoundError } from '@n8n/errors';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { ProjectService } from '@/services/project.service.ee';
import { SlackMethodsService } from './slack-methods.service';

View File

@ -13,8 +13,7 @@ import { jsonParse } from 'n8n-workflow';
import { CredentialsService } from '@/credentials/credentials.service';
import { BadRequestError } from '@n8n/errors';
import { CacheService } from '@/services/cache/cache.service';
import { UrlService } from '@n8n/backend-services';
import { CacheService, UrlService } from '@n8n/backend-services';
import { getAgentOrThrow } from '../../../utils/get-agent-or-throw';
import {

View File

@ -5,7 +5,7 @@ import type { ErrorReporter } from 'n8n-core';
import type { Mocked } from 'vitest';
import { mock } from 'vitest-mock-extended';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import { N8N_VERSION } from '../../../constants';
import { MigrationRegistry } from '../breaking-changes.migration-registry.service';

View File

@ -13,7 +13,7 @@ import { Container, Service } from '@n8n/di';
import { In } from '@n8n/typeorm';
import { ErrorReporter } from 'n8n-core';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { MigrationRegistry } from './breaking-changes.migration-registry.service';
import { RuleRegistry } from './breaking-changes.rule-registry.service';

View File

@ -7,7 +7,7 @@ import {
ChatHubExecutionStore,
type ChatHubExecutionContext,
} from '@/modules/chat-hub/chat-hub-execution-store.service';
import type { RedisClientService } from '@/services/redis-client.service';
import type { RedisClientService } from '@n8n/backend-services';
const EXECUTION_ID = '12345678';
const SESSION_ID = 'bbbbbbbb-2222-4000-8000-000000000002';

View File

@ -4,7 +4,7 @@ import type { InstanceSettings } from 'n8n-core';
import { mock } from 'vitest-mock-extended';
import { ChatStreamStateService } from '@/modules/chat-hub/chat-stream-state.service';
import type { RedisClientService } from '@/services/redis-client.service';
import type { RedisClientService } from '@n8n/backend-services';
describe('ChatStreamStateService', () => {
const logger = mock<Logger>();

View File

@ -7,7 +7,7 @@ import { Service } from '@n8n/di';
import type { Cluster, Redis } from 'ioredis';
import { InstanceSettings } from 'n8n-core';
import { RedisClientService } from '@/services/redis-client.service';
import { RedisClientService } from '@n8n/backend-services';
import type { NonStreamingResponseMode } from './chat-hub.types';

View File

@ -7,7 +7,7 @@ import { Service } from '@n8n/di';
import type { Cluster, Redis } from 'ioredis';
import { InstanceSettings } from 'n8n-core';
import { RedisClientService } from '@/services/redis-client.service';
import { RedisClientService } from '@n8n/backend-services';
/**
* Stream state for an active chat session

View File

@ -10,7 +10,7 @@ import { SsrfProtectionConfig } from '@n8n/config';
import { mock } from 'vitest-mock-extended';
import type { IncomingHttpHeaders } from 'node:http';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import { IdentifierValidationError } from '../identifier-interface';
import { OAuth2TokenIntrospectionIdentifier } from '../oauth2-introspection-identifier';

View File

@ -4,7 +4,7 @@ import { Time } from '@n8n/constants';
import { mock } from 'vitest-mock-extended';
import type { IHttpRequestOptions } from 'n8n-workflow';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import { IdentifierValidationError } from '../identifier-interface';
import { OAuth2TokenIntrospectionIdentifier } from '../oauth2-introspection-identifier';

View File

@ -3,7 +3,7 @@ import { mockLogger } from '@n8n/backend-test-utils';
import { mock } from 'vitest-mock-extended';
import { z } from 'zod';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import { IdentifierValidationError } from '../identifier-interface';
import { OAuth2MetadataHttpClient } from '../oauth2-metadata-http-client';

View File

@ -12,7 +12,7 @@ import type { JSONWebKeySet, JWTPayload, KeyObject } from 'jose';
import { mock } from 'vitest-mock-extended';
import type { IncomingHttpHeaders } from 'node:http';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import { IdentifierValidationError } from '../identifier-interface';
import { OAuth2MetadataHttpClient } from '../oauth2-metadata-http-client';

View File

@ -6,7 +6,7 @@ import type { JSONWebKeySet, JWTPayload, KeyObject } from 'jose';
import type { IHttpRequestOptions } from 'n8n-workflow';
import { mock } from 'vitest-mock-extended';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import { IdentifierValidationError } from '../identifier-interface';
import { OAuth2MetadataHttpClient } from '../oauth2-metadata-http-client';

View File

@ -8,7 +8,7 @@ import { IdentifierValidationError, ITokenIdentifier } from './identifier-interf
import { OAuth2MetadataHttpClient } from './oauth2-metadata-http-client';
import { assertAudience, OAuth2OptionsSchema, sha256 } from './oauth2-utils';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
// Cap at 5 minutes to ensure periodic revalidation
const MAX_TOKEN_CACHE_TIMEOUT = 5 * Time.minutes.toMilliseconds;

View File

@ -7,7 +7,7 @@ import type { z } from 'zod';
import { IdentifierValidationError } from './identifier-interface';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
const REQUEST_TIMEOUT = 10 * Time.seconds.toMilliseconds;
const METADATA_CACHE_TIMEOUT = 1 * Time.hours.toMilliseconds; // 1 hour

View File

@ -5,7 +5,7 @@ import type { JSONWebKeySet, JWTPayload } from 'jose';
import type { ICredentialContext } from 'n8n-workflow';
import { z } from 'zod';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { IdentifierValidationError, ITokenIdentifier } from './identifier-interface';
import { OAuth2MetadataHttpClient } from './oauth2-metadata-http-client';

View File

@ -1,6 +1,6 @@
import { mock } from 'vitest-mock-extended';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import { AuthorizeIntentService, type AuthorizeIntent } from '../authorize-intent.service';

View File

@ -2,7 +2,7 @@ import { Service } from '@n8n/di';
import { randomBytes } from 'node:crypto';
import { MAX_CSRF_AGE } from '@/oauth/types';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
/**
* A pending dynamic-credential authorization, captured at credential-gate time and

View File

@ -5,7 +5,7 @@ import { mock } from 'vitest-mock-extended';
import { EngineDataPlaneProxyService } from '@/services/engine-data-plane-proxy.service';
import { EngineV2WebhookResponder } from '@/services/engine-v2-webhook-responder.service';
import { RedisClientService } from '@/services/redis-client.service';
import { RedisClientService } from '@n8n/backend-services';
import { EngineControlPlaneServer } from '../engine-control-plane-server';
import { EngineDataPlaneClient } from '../engine-data-plane-client';

View File

@ -6,7 +6,7 @@ export type RedisExecutionResponseChannelNameGenerator = (executionId: string) =
/** Both planes must derive the same channel name, so they share this helper. */
async function createChannelNameGenerator(): Promise<RedisExecutionResponseChannelNameGenerator> {
const { RedisClientService } = await import('@/services/redis-client.service.js');
const { RedisClientService } = await import('@n8n/backend-services');
const prefix = Container.get(RedisClientService).toValidPrefix(
Container.get(GlobalConfig).redis.prefix,
);
@ -17,7 +17,7 @@ async function createChannelNameGenerator(): Promise<RedisExecutionResponseChann
/** Creates the data plane end of the Redis response channel. */
export async function createRedisExecutionResponseSender(logger: Logger) {
const { RedisClientService } = await import('@/services/redis-client.service.js');
const { RedisClientService } = await import('@n8n/backend-services');
const { RedisExecutionResponseSender } = await import('./redis-execution-response-sender.js');
return new RedisExecutionResponseSender(
@ -29,7 +29,7 @@ export async function createRedisExecutionResponseSender(logger: Logger) {
/** Creates and starts the control plane end of the Redis response channel. */
export async function startRedisExecutionResponseReceiver(logger: Logger) {
const { RedisClientService } = await import('@/services/redis-client.service.js');
const { RedisClientService } = await import('@n8n/backend-services');
const { RedisExecutionResponseReceiver } = await import('./redis-execution-response-receiver.js');
const receiver = new RedisExecutionResponseReceiver(

View File

@ -16,7 +16,7 @@ import { ActiveWorkflowManager } from '@/active-workflow-manager';
import { CollaborationService } from '@/collaboration/collaboration.service';
import { License } from '@/license';
import { Push } from '@/push';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { Telemetry } from '@/telemetry';
import { createMember, createOwner } from '@test-integration/db/users';

View File

@ -6,7 +6,7 @@ import { Redis } from 'ioredis';
import type { InstanceSettings } from 'n8n-core';
import { mock } from 'vitest-mock-extended';
import type { RedisClientService } from '@/services/redis-client.service';
import type { RedisClientService } from '@n8n/backend-services';
import { InstanceRegistryService } from '../instance-registry.service';
import { REDIS_KEY_PATTERNS } from '../instance-registry.types';

View File

@ -5,7 +5,7 @@ import type { Redis as SingleNodeClient } from 'ioredis';
import { jsonStringify } from 'n8n-workflow';
import { mock } from 'vitest-mock-extended';
import type { RedisClientService } from '@/services/redis-client.service';
import type { RedisClientService } from '@n8n/backend-services';
import { REDIS_KEY_PATTERNS, REGISTRY_CONSTANTS } from '../../instance-registry.types';
import {

View File

@ -6,7 +6,7 @@ import type { Cluster, Redis } from 'ioredis';
import { ensureError } from '@n8n/utils/errors/ensure-error';
import { jsonParse, jsonStringify } from 'n8n-workflow';
import { RedisClientService } from '@/services/redis-client.service';
import { RedisClientService } from '@n8n/backend-services';
import { REDIS_KEY_PATTERNS, REGISTRY_CONSTANTS } from '../instance-registry.types';
import type { InstanceStorage } from './instance-storage.interface';

View File

@ -9,7 +9,7 @@ import { mock } from 'vitest-mock-extended';
import type { CollaborationService } from '@/collaboration/collaboration.service';
import { BadRequestError } from '@n8n/errors';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import type { WorkflowFinderService } from '@/workflows/workflow-finder.service';
import { UpdateWorkflowsAvailabilityDto } from '../dto/update-workflows-availability.dto';

View File

@ -18,7 +18,7 @@ import {
import { CollaborationService } from '@/collaboration/collaboration.service';
import { BadRequestError } from '@n8n/errors';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { removeDefaultValues } from '@/workflow-helpers';
import { WorkflowFinderService } from '@/workflows/workflow-finder.service';

View File

@ -6,7 +6,7 @@ import type { CryptoKey } from 'jose';
import { CompactEncrypt, compactDecrypt, exportJWK, generateKeyPair, importJWK } from 'jose';
import { Cipher, InstanceSettings } from 'n8n-core';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { OAuthJweKeyService } from '../oauth-jwe-key.service';
import {

View File

@ -7,7 +7,7 @@ import { exportJWK, generateKeyPair } from 'jose';
import type { JWK } from 'jose';
import { Cipher } from 'n8n-core';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { OAuthJweKeyService } from '../oauth-jwe-key.service';
import { JWE_KEY_ALGORITHMS, JWE_PRIVATE_KEY_TYPE } from '../oauth-jwe.constants';

View File

@ -7,7 +7,7 @@ import { exportJWK, generateKeyPair, importJWK } from 'jose';
import { Cipher } from 'n8n-core';
import { jsonParse, UnexpectedError } from 'n8n-workflow';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import {
JWE_KEY_ALGORITHMS,

View File

@ -19,7 +19,7 @@ import { setupTestServer } from '@test-integration/utils';
import { AuthService } from '@/auth/auth.service';
import { AUTH_COOKIE_NAME } from '@/constants';
import { OAuthTokenService } from '@/modules/oauth-server/oauth-token.service';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { ProtectedResourceRegistry } from '@/services/protected-resource.registry';
import { UrlService } from '@n8n/backend-services';

View File

@ -16,7 +16,7 @@ import { createMember, createOwner } from '@test-integration/db/users';
import { setupTestServer } from '@test-integration/utils';
import { OAuthTokenService } from '@/modules/oauth-server/oauth-token.service';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { ProtectedResourceRegistry } from '@/services/protected-resource.registry';
import { UrlService } from '@n8n/backend-services';
import { TestWebhookRegistrationsService } from '@/webhooks/test-webhook-registrations.service';

View File

@ -16,7 +16,7 @@ import { createOwner, createMember } from '@test-integration/db/users';
import { setupTestServer } from '@test-integration/utils';
import { OAuthTokenService } from '@/modules/oauth-server/oauth-token.service';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { ProtectedResourceRegistry } from '@/services/protected-resource.registry';
import { UrlService } from '@n8n/backend-services';

View File

@ -17,7 +17,7 @@ import { setupTestServer } from '@test-integration/utils';
import { OAuthClientRepository } from '../database/repositories/oauth-client.repository';
import { OAuthTokenService } from '@/modules/oauth-server/oauth-token.service';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { ProtectedResourceRegistry } from '@/services/protected-resource.registry';
import { UrlService } from '@n8n/backend-services';
import { TestWebhookRegistrationsService } from '@/webhooks/test-webhook-registrations.service';

View File

@ -12,7 +12,7 @@ import {
} from 'n8n-workflow';
import { randomUUID } from 'node:crypto';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { UrlService } from '@n8n/backend-services';
import { createOwner, createMember } from '@test-integration/db/users';
import { setupTestServer } from '@test-integration/utils';

View File

@ -12,7 +12,7 @@ import { setupTestServer } from '@test-integration/utils';
import { OAuthClientRepository } from '@/modules/oauth-server/database/repositories/oauth-client.repository';
import { OAuthTokenService } from '@/modules/oauth-server/oauth-token.service';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { ProtectedResourceRegistry } from '@/services/protected-resource.registry';
import { UrlService } from '@n8n/backend-services';
import { TestWebhookRegistrationsService } from '@/webhooks/test-webhook-registrations.service';

View File

@ -11,7 +11,7 @@ import { setupTestServer } from '@test-integration/utils';
import { MCP_TRIGGER_NODE_TYPE } from '@/constants';
import { OAuthTokenService } from '@/modules/oauth-server/oauth-token.service';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { UrlService } from '@n8n/backend-services';
import { TestWebhookRegistrationsService } from '@/webhooks/test-webhook-registrations.service';

View File

@ -16,7 +16,7 @@ import { setupTestServer } from '@test-integration/utils';
import { MCP_TRIGGER_NODE_TYPE } from '@/constants';
import { OAuthTokenService } from '@/modules/oauth-server/oauth-token.service';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { UrlService } from '@n8n/backend-services';
import { JwtService } from '@/services/jwt.service';

View File

@ -11,7 +11,7 @@ import { setupTestServer } from '@test-integration/utils';
import { MCP_TRIGGER_NODE_TYPE } from '@/constants';
import { OAuthTokenService } from '@/modules/oauth-server/oauth-token.service';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { ProtectedResourceRegistry } from '@/services/protected-resource.registry';
import { UrlService } from '@n8n/backend-services';

View File

@ -16,7 +16,7 @@ import { createOwner, createMember } from '@test-integration/db/users';
import { setupTestServer } from '@test-integration/utils';
import { OAuthTokenService } from '@/modules/oauth-server/oauth-token.service';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { ProtectedResourceRegistry } from '@/services/protected-resource.registry';
import { UrlService } from '@n8n/backend-services';
import { TestWebhookRegistrationsService } from '@/webhooks/test-webhook-registrations.service';

View File

@ -17,7 +17,7 @@ import { setupTestServer } from '@test-integration/utils';
import { OAuthServerService } from '@/modules/oauth-server/oauth-server.service';
import { OAuthTokenService } from '@/modules/oauth-server/oauth-token.service';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
import { ProtectedResourceRegistry } from '@/services/protected-resource.registry';
import { UrlService } from '@n8n/backend-services';

View File

@ -8,11 +8,10 @@ import { UserError, type N8nOAuth2FlowResult, type N8nOAuth2RefreshResult } from
import { createHash, randomBytes } from 'node:crypto';
import pkceChallenge from 'pkce-challenge';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService, UrlService } from '@n8n/backend-services';
import { OAuthTokenVerifierProxy } from '@/services/oauth-token-verifier-proxy.service';
import type { N8nOAuth2Flow } from '@/services/oauth2-flow-proxy.service';
import { ProtectedResourceRegistry } from '@/services/protected-resource.registry';
import { UrlService } from '@n8n/backend-services';
import { OAuthServerService } from './oauth-server.service';

View File

@ -8,7 +8,7 @@ import { UserError } from 'n8n-workflow';
import { SELF_SEND_COMMANDS } from '@/scaling/constants';
import type { Publisher } from '@/scaling/pubsub/publisher.service';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import { InstanceRedactionEnforcementService } from '../instance-redaction-enforcement.service';

View File

@ -7,7 +7,7 @@ import { Service } from '@n8n/di';
import { UserError } from 'n8n-workflow';
import { Publisher } from '@/scaling/pubsub/publisher.service';
import { CacheService } from '@/services/cache/cache.service';
import { CacheService } from '@n8n/backend-services';
const KEY = 'redaction.enforcement';

View File

@ -15,7 +15,7 @@ import type { IdentityProviderInstance, ServiceProviderInstance } from 'samlify'
import { BadRequestError, ForbiddenError } from '@n8n/errors';
import type { ProvisioningService } from '@/modules/provisioning.ee/provisioning.service.ee';
import { Publisher } from '@/scaling/pubsub/publisher.service';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import type { UrlService } from '@n8n/backend-services';
import * as ssoHelpers from '@/sso.ee/sso-helpers';

View File

@ -20,8 +20,7 @@ import type {
import { AuthError, BadRequestError } from '@n8n/errors';
import { buildSamlClaimsContext } from '@/modules/provisioning.ee/claims-context.builder';
import { ProvisioningService } from '@/modules/provisioning.ee/provisioning.service.ee';
import { CacheService } from '@/services/cache/cache.service';
import { UrlService } from '@n8n/backend-services';
import { CacheService, UrlService } from '@n8n/backend-services';
import {
getSamlLoginLabel,
isSamlLicensedAndEnabled,

View File

@ -7,7 +7,7 @@ import { mock } from 'vitest-mock-extended';
import { ConflictError, NotFoundError } from '@n8n/errors';
import type { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';
import type { NodeTypes } from '@/node-types';
import type { CacheService } from '@/services/cache/cache.service';
import type { CacheService } from '@n8n/backend-services';
import { CREDENTIAL_TYPES_KIND } from '../constants';
import type { TypeAvailabilityPolicyAttachmentRepository } from '../database/repositories/type-availability-policy-attachment.repository';

View File

@ -1,6 +1,6 @@
import type { NodeTypeAvailabilityScope } from '@n8n/api-types';
import { Logger } from '@n8n/backend-common';
import { EventService } from '@n8n/backend-services';
import { CacheService, EventService } from '@n8n/backend-services';
import { Time } from '@n8n/constants';
import { TransactionRunner, type OperationContext } from '@n8n/db';
import { Service } from '@n8n/di';
@ -10,7 +10,6 @@ import { OperationalError, UserError } from 'n8n-workflow';
import { ConflictError, NotFoundError } from '@n8n/errors';
import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';
import { NodeTypes } from '@/node-types';
import { CacheService } from '@/services/cache/cache.service';
import { TypeAvailabilityPolicyAttachmentRepository } from './database/repositories/type-availability-policy-attachment.repository';
import { TypeAvailabilityPolicyScopeRepository } from './database/repositories/type-availability-policy-scope.repository';

View File

@ -1,7 +1,7 @@
import type { LockService, Logger } from '@n8n/backend-common';
import { OutboundHttp, type SsrfProtectionService } from '@n8n/backend-network';
import { type LocalServer, startServer } from '@n8n/backend-network/testing';
import type { EventService, UrlService } from '@n8n/backend-services';
import type { CacheService, EventService, UrlService } from '@n8n/backend-services';
import type { GlobalConfig, SsrfProtectionConfig } from '@n8n/config';
import type { CredentialsRepository } from '@n8n/db';
import { mock } from 'vitest-mock-extended';
@ -16,7 +16,6 @@ import type { ExternalHooks } from '@/external-hooks';
import type { OAuthBrowserBindingService } from '@/oauth/oauth-browser-binding.service';
import type { OAuthJweServiceProxy } from '@/oauth/oauth-jwe-service.proxy';
import { OauthService, type OAuth1CredentialData } from '@/oauth/oauth.service';
import type { CacheService } from '@/services/cache/cache.service';
interface Received {
method?: string;

View File

@ -1,6 +1,6 @@
import { LockAcquisitionTimeoutError, LockService, Logger } from '@n8n/backend-common';
import { OutboundHttp, SsrfProtectionService, type HttpRequestClient } from '@n8n/backend-network';
import { EventService, UrlService } from '@n8n/backend-services';
import { CacheService, EventService, UrlService } from '@n8n/backend-services';
import { mockInstance } from '@n8n/backend-test-utils';
import type { OAuth2CredentialData } from '@n8n/client-oauth2';
import { AuthError as OAuth2AuthError } from '@n8n/client-oauth2';
@ -34,7 +34,6 @@ import {
type OAuth1CredentialData,
} from '@/oauth/oauth.service';
import type { OAuthRequest } from '@/requests';
import { CacheService } from '@/services/cache/cache.service';
import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data';
vi.mock('@/workflow-execute-additional-data');

Some files were not shown because too many files have changed in this diff Show More