refactor(core): Separate license state from license service (#15097)

This commit is contained in:
Iván Ovejero
2025-05-06 09:43:08 +02:00
committed by GitHub
parent e86edf536f
commit ca0e7ffe3b
18 changed files with 365 additions and 104 deletions

View File

@@ -21,6 +21,9 @@
"dist/**/*" "dist/**/*"
], ],
"dependencies": { "dependencies": {
"@n8n/constants": "workspace:^",
"@n8n/di": "workspace:^",
"n8n-workflow": "workspace:^",
"reflect-metadata": "catalog:" "reflect-metadata": "catalog:"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1 +1,2 @@
export {}; export * from './license-state';
export * from './types';

View File

@@ -0,0 +1,192 @@
import { UNLIMITED_LICENSE_QUOTA, type BooleanLicenseFeature } from '@n8n/constants';
import { Service } from '@n8n/di';
import { UnexpectedError } from 'n8n-workflow';
import type { FeatureReturnType, LicenseProvider } from './types';
class ProviderNotSetError extends UnexpectedError {
constructor() {
super('Cannot query license state because license provider has not been set');
}
}
@Service()
export class LicenseState {
licenseProvider: LicenseProvider | null = null;
setLicenseProvider(provider: LicenseProvider) {
this.licenseProvider = provider;
}
private assertProvider(): asserts this is { licenseProvider: LicenseProvider } {
if (!this.licenseProvider) throw new ProviderNotSetError();
}
// --------------------
// core queries
// --------------------
isLicensed(feature: BooleanLicenseFeature) {
this.assertProvider();
return this.licenseProvider.isLicensed(feature);
}
getValue<T extends keyof FeatureReturnType>(feature: T): FeatureReturnType[T] {
this.assertProvider();
return this.licenseProvider.getValue(feature);
}
// --------------------
// booleans
// --------------------
isSharingLicensed() {
return this.isLicensed('feat:sharing');
}
isLogStreamingLicensed() {
return this.isLicensed('feat:logStreaming');
}
isLdapLicensed() {
return this.isLicensed('feat:ldap');
}
isSamlLicensed() {
return this.isLicensed('feat:saml');
}
isApiKeyScopesLicensed() {
return this.isLicensed('feat:apiKeyScopes');
}
isAiAssistantLicensed() {
return this.isLicensed('feat:aiAssistant');
}
isAskAiLicensed() {
return this.isLicensed('feat:askAi');
}
isAiCreditsLicensed() {
return this.isLicensed('feat:aiCredits');
}
isAdvancedExecutionFiltersLicensed() {
return this.isLicensed('feat:advancedExecutionFilters');
}
isAdvancedPermissionsLicensed() {
return this.isLicensed('feat:advancedPermissions');
}
isDebugInEditorLicensed() {
return this.isLicensed('feat:debugInEditor');
}
isBinaryDataS3Licensed() {
return this.isLicensed('feat:binaryDataS3');
}
isMultiMainLicensed() {
return this.isLicensed('feat:multipleMainInstances');
}
isVariablesLicensed() {
return this.isLicensed('feat:variables');
}
isSourceControlLicensed() {
return this.isLicensed('feat:sourceControl');
}
isExternalSecretsLicensed() {
return this.isLicensed('feat:externalSecrets');
}
isWorkflowHistoryLicensed() {
return this.isLicensed('feat:workflowHistory');
}
isAPIDisabled() {
return this.isLicensed('feat:apiDisabled');
}
isWorkerViewLicensed() {
return this.isLicensed('feat:workerView');
}
isProjectRoleAdminLicensed() {
return this.isLicensed('feat:projectRole:admin');
}
isProjectRoleEditorLicensed() {
return this.isLicensed('feat:projectRole:editor');
}
isProjectRoleViewerLicensed() {
return this.isLicensed('feat:projectRole:viewer');
}
isCustomNpmRegistryLicensed() {
return this.isLicensed('feat:communityNodes:customRegistry');
}
isFoldersLicensed() {
return this.isLicensed('feat:folders');
}
isInsightsSummaryLicensed() {
return this.isLicensed('feat:insights:viewSummary');
}
isInsightsDashboardLicensed() {
return this.isLicensed('feat:insights:viewDashboard');
}
isInsightsHourlyDataLicensed() {
return this.isLicensed('feat:insights:viewHourlyData');
}
// --------------------
// integers
// --------------------
getMaxUsers() {
return this.getValue('quota:users') ?? UNLIMITED_LICENSE_QUOTA;
}
getMaxActiveWorkflows() {
return this.getValue('quota:activeWorkflows') ?? UNLIMITED_LICENSE_QUOTA;
}
getMaxVariables() {
return this.getValue('quota:maxVariables') ?? UNLIMITED_LICENSE_QUOTA;
}
getMaxAiCredits() {
return this.getValue('quota:aiCredits') ?? 0;
}
getWorkflowHistoryPruneQuota() {
return this.getValue('quota:workflowHistoryPrune') ?? UNLIMITED_LICENSE_QUOTA;
}
getInsightsMaxHistory() {
return this.getValue('quota:insights:maxHistoryDays') ?? 7;
}
getInsightsRetentionMaxAge() {
return this.getValue('quota:insights:retention:maxAgeDays') ?? 180;
}
getInsightsRetentionPruneInterval() {
return this.getValue('quota:insights:retention:pruneIntervalDays') ?? 24;
}
getMaxTeamProjects() {
return this.getValue('quota:maxTeamProjects') ?? 0;
}
}

View File

@@ -0,0 +1,15 @@
import type { BooleanLicenseFeature, NumericLicenseFeature } from '@n8n/constants';
export type FeatureReturnType = Partial<
{
planName: string;
} & { [K in NumericLicenseFeature]: number } & { [K in BooleanLicenseFeature]: boolean }
>;
export interface LicenseProvider {
/** Returns whether a feature is included in the user's license plan. */
isLicensed(feature: BooleanLicenseFeature): boolean;
/** Returns the value of a feature in the user's license plan, typically a boolean or integer. */
getValue<T extends keyof FeatureReturnType>(feature: T): FeatureReturnType[T];
}

View File

@@ -108,15 +108,15 @@ describe('ControllerRegistry', () => {
}); });
it('should disallow when feature is missing', async () => { it('should disallow when feature is missing', async () => {
license.isFeatureEnabled.calledWith('feat:sharing').mockReturnValue(false); license.isLicensed.calledWith('feat:sharing').mockReturnValue(false);
await agent.get('/rest/test/with-sharing').expect(403); await agent.get('/rest/test/with-sharing').expect(403);
expect(license.isFeatureEnabled).toHaveBeenCalled(); expect(license.isLicensed).toHaveBeenCalled();
}); });
it('should allow when feature is available', async () => { it('should allow when feature is available', async () => {
license.isFeatureEnabled.calledWith('feat:sharing').mockReturnValue(true); license.isLicensed.calledWith('feat:sharing').mockReturnValue(true);
await agent.get('/rest/test/with-sharing').expect(200); await agent.get('/rest/test/with-sharing').expect(200);
expect(license.isFeatureEnabled).toHaveBeenCalled(); expect(license.isLicensed).toHaveBeenCalled();
}); });
}); });

View File

@@ -111,13 +111,13 @@ describe('License', () => {
}); });
test('check if feature is enabled', () => { test('check if feature is enabled', () => {
license.isFeatureEnabled(MOCK_FEATURE_FLAG); license.isLicensed(MOCK_FEATURE_FLAG);
expect(LicenseManager.prototype.hasFeatureEnabled).toHaveBeenCalledWith(MOCK_FEATURE_FLAG); expect(LicenseManager.prototype.hasFeatureEnabled).toHaveBeenCalledWith(MOCK_FEATURE_FLAG);
}); });
test('check if sharing feature is enabled', () => { test('check if sharing feature is enabled', () => {
license.isFeatureEnabled(MOCK_FEATURE_FLAG); license.isLicensed(MOCK_FEATURE_FLAG);
expect(LicenseManager.prototype.hasFeatureEnabled).toHaveBeenCalledWith(MOCK_FEATURE_FLAG); expect(LicenseManager.prototype.hasFeatureEnabled).toHaveBeenCalledWith(MOCK_FEATURE_FLAG);
}); });
@@ -129,7 +129,7 @@ describe('License', () => {
}); });
test('check fetching feature values', async () => { test('check fetching feature values', async () => {
license.getFeatureValue(MOCK_FEATURE_FLAG); license.getValue(MOCK_FEATURE_FLAG);
expect(LicenseManager.prototype.getFeatureValue).toHaveBeenCalledWith(MOCK_FEATURE_FLAG); expect(LicenseManager.prototype.getFeatureValue).toHaveBeenCalledWith(MOCK_FEATURE_FLAG);
}); });

View File

@@ -1,4 +1,5 @@
import 'reflect-metadata'; import 'reflect-metadata';
import { LicenseState } from '@n8n/backend-common';
import { GlobalConfig } from '@n8n/config'; import { GlobalConfig } from '@n8n/config';
import { LICENSE_FEATURES } from '@n8n/constants'; import { LICENSE_FEATURES } from '@n8n/constants';
import { Container } from '@n8n/di'; import { Container } from '@n8n/di';
@@ -197,7 +198,7 @@ export abstract class BaseCommand extends Command {
); );
} }
const isLicensed = Container.get(License).isFeatureEnabled(LICENSE_FEATURES.BINARY_DATA_S3); const isLicensed = Container.get(License).isLicensed(LICENSE_FEATURES.BINARY_DATA_S3);
if (!isLicensed) { if (!isLicensed) {
this.logger.error( this.logger.error(
'No license found for S3 storage. \n Either set `N8N_DEFAULT_BINARY_DATA_MODE` to something else, or upgrade to a license that supports this feature.', 'No license found for S3 storage. \n Either set `N8N_DEFAULT_BINARY_DATA_MODE` to something else, or upgrade to a license that supports this feature.',
@@ -241,6 +242,8 @@ export abstract class BaseCommand extends Command {
this.license = Container.get(License); this.license = Container.get(License);
await this.license.init(); await this.license.init();
Container.get(LicenseState).setLicenseProvider(this.license);
const { activationKey } = this.globalConfig.license; const { activationKey } = this.globalConfig.license;
if (activationKey) { if (activationKey) {

View File

@@ -106,7 +106,7 @@ export class ControllerRegistry {
private createLicenseMiddleware(feature: BooleanLicenseFeature): RequestHandler { private createLicenseMiddleware(feature: BooleanLicenseFeature): RequestHandler {
return (_req, res, next) => { return (_req, res, next) => {
if (!this.license.isFeatureEnabled(feature)) { if (!this.license.isLicensed(feature)) {
res.status(403).json({ status: 'error', message: 'Plan lacks license for this feature' }); res.status(403).json({ status: 'error', message: 'Plan lacks license for this feature' });
return; return;
} }

View File

@@ -153,8 +153,7 @@ export class E2EController {
private readonly userRepository: UserRepository, private readonly userRepository: UserRepository,
private readonly authUserRepository: AuthUserRepository, private readonly authUserRepository: AuthUserRepository,
) { ) {
license.isFeatureEnabled = (feature: BooleanLicenseFeature) => license.isLicensed = (feature: BooleanLicenseFeature) => this.enabledFeatures[feature] ?? false;
this.enabledFeatures[feature] ?? false;
// Ugly hack to satisfy biome parser // Ugly hack to satisfy biome parser
const getFeatureValue = <T extends keyof FeatureReturnType>( const getFeatureValue = <T extends keyof FeatureReturnType>(
@@ -166,7 +165,7 @@ export class E2EController {
return UNLIMITED_LICENSE_QUOTA as FeatureReturnType[T]; return UNLIMITED_LICENSE_QUOTA as FeatureReturnType[T];
} }
}; };
license.getFeatureValue = getFeatureValue; license.getValue = getFeatureValue;
license.getPlanName = () => 'Enterprise'; license.getPlanName = () => 'Enterprise';
} }

View File

@@ -1,3 +1,4 @@
import type { LicenseProvider } from '@n8n/backend-common';
import { GlobalConfig } from '@n8n/config'; import { GlobalConfig } from '@n8n/config';
import { import {
LICENSE_FEATURES, LICENSE_FEATURES,
@@ -28,7 +29,7 @@ export type FeatureReturnType = Partial<
>; >;
@Service() @Service()
export class License { export class License implements LicenseProvider {
private manager: LicenseManager | undefined; private manager: LicenseManager | undefined;
private isShuttingDown = false; private isShuttingDown = false;
@@ -218,123 +219,135 @@ export class License {
this.logger.debug('License shut down'); this.logger.debug('License shut down');
} }
isFeatureEnabled(feature: BooleanLicenseFeature) { isLicensed(feature: BooleanLicenseFeature) {
return this.manager?.hasFeatureEnabled(feature) ?? false; return this.manager?.hasFeatureEnabled(feature) ?? false;
} }
/** @deprecated Use `LicenseState.isSharingLicensed` instead. */
isSharingEnabled() { isSharingEnabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.SHARING); return this.isLicensed(LICENSE_FEATURES.SHARING);
} }
/** @deprecated Use `LicenseState.isLogStreamingLicensed` instead. */
isLogStreamingEnabled() { isLogStreamingEnabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.LOG_STREAMING); return this.isLicensed(LICENSE_FEATURES.LOG_STREAMING);
} }
/** @deprecated Use `LicenseState.isLdapLicensed` instead. */
isLdapEnabled() { isLdapEnabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.LDAP); return this.isLicensed(LICENSE_FEATURES.LDAP);
} }
/** @deprecated Use `LicenseState.isSamlLicensed` instead. */
isSamlEnabled() { isSamlEnabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.SAML); return this.isLicensed(LICENSE_FEATURES.SAML);
} }
/** @deprecated Use `LicenseState.isApiKeyScopesLicensed` instead. */
isApiKeyScopesEnabled() { isApiKeyScopesEnabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.API_KEY_SCOPES); return this.isLicensed(LICENSE_FEATURES.API_KEY_SCOPES);
} }
/** @deprecated Use `LicenseState.isAiAssistantLicensed` instead. */
isAiAssistantEnabled() { isAiAssistantEnabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.AI_ASSISTANT); return this.isLicensed(LICENSE_FEATURES.AI_ASSISTANT);
} }
/** @deprecated Use `LicenseState.isAskAiLicensed` instead. */
isAskAiEnabled() { isAskAiEnabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.ASK_AI); return this.isLicensed(LICENSE_FEATURES.ASK_AI);
} }
/** @deprecated Use `LicenseState.isAiCreditsLicensed` instead. */
isAiCreditsEnabled() { isAiCreditsEnabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.AI_CREDITS); return this.isLicensed(LICENSE_FEATURES.AI_CREDITS);
} }
/** @deprecated Use `LicenseState.isAdvancedExecutionFiltersLicensed` instead. */
isAdvancedExecutionFiltersEnabled() { isAdvancedExecutionFiltersEnabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.ADVANCED_EXECUTION_FILTERS); return this.isLicensed(LICENSE_FEATURES.ADVANCED_EXECUTION_FILTERS);
} }
/** @deprecated Use `LicenseState.isAdvancedPermissionsLicensed` instead. */
isAdvancedPermissionsLicensed() { isAdvancedPermissionsLicensed() {
return this.isFeatureEnabled(LICENSE_FEATURES.ADVANCED_PERMISSIONS); return this.isLicensed(LICENSE_FEATURES.ADVANCED_PERMISSIONS);
} }
/** @deprecated Use `LicenseState.isDebugInEditorLicensed` instead. */
isDebugInEditorLicensed() { isDebugInEditorLicensed() {
return this.isFeatureEnabled(LICENSE_FEATURES.DEBUG_IN_EDITOR); return this.isLicensed(LICENSE_FEATURES.DEBUG_IN_EDITOR);
} }
/** @deprecated Use `LicenseState.isBinaryDataS3Licensed` instead. */
isBinaryDataS3Licensed() { isBinaryDataS3Licensed() {
return this.isFeatureEnabled(LICENSE_FEATURES.BINARY_DATA_S3); return this.isLicensed(LICENSE_FEATURES.BINARY_DATA_S3);
} }
/** @deprecated Use `LicenseState.isMultiMainLicensed` instead. */
isMultiMainLicensed() { isMultiMainLicensed() {
return this.isFeatureEnabled(LICENSE_FEATURES.MULTIPLE_MAIN_INSTANCES); return this.isLicensed(LICENSE_FEATURES.MULTIPLE_MAIN_INSTANCES);
} }
/** @deprecated Use `LicenseState.isVariablesLicensed` instead. */
isVariablesEnabled() { isVariablesEnabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.VARIABLES); return this.isLicensed(LICENSE_FEATURES.VARIABLES);
} }
/** @deprecated Use `LicenseState.isSourceControlLicensed` instead. */
isSourceControlLicensed() { isSourceControlLicensed() {
return this.isFeatureEnabled(LICENSE_FEATURES.SOURCE_CONTROL); return this.isLicensed(LICENSE_FEATURES.SOURCE_CONTROL);
} }
/** @deprecated Use `LicenseState.isExternalSecretsLicensed` instead. */
isExternalSecretsEnabled() { isExternalSecretsEnabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.EXTERNAL_SECRETS); return this.isLicensed(LICENSE_FEATURES.EXTERNAL_SECRETS);
} }
/** @deprecated Use `LicenseState.isWorkflowHistoryLicensed` instead. */
isWorkflowHistoryLicensed() { isWorkflowHistoryLicensed() {
return this.isFeatureEnabled(LICENSE_FEATURES.WORKFLOW_HISTORY); return this.isLicensed(LICENSE_FEATURES.WORKFLOW_HISTORY);
} }
/** @deprecated Use `LicenseState.isAPIDisabled` instead. */
isAPIDisabled() { isAPIDisabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.API_DISABLED); return this.isLicensed(LICENSE_FEATURES.API_DISABLED);
} }
/** @deprecated Use `LicenseState.isWorkerViewLicensed` instead. */
isWorkerViewLicensed() { isWorkerViewLicensed() {
return this.isFeatureEnabled(LICENSE_FEATURES.WORKER_VIEW); return this.isLicensed(LICENSE_FEATURES.WORKER_VIEW);
} }
/** @deprecated Use `LicenseState.isProjectRoleAdminLicensed` instead. */
isProjectRoleAdminLicensed() { isProjectRoleAdminLicensed() {
return this.isFeatureEnabled(LICENSE_FEATURES.PROJECT_ROLE_ADMIN); return this.isLicensed(LICENSE_FEATURES.PROJECT_ROLE_ADMIN);
} }
/** @deprecated Use `LicenseState.isProjectRoleEditorLicensed` instead. */
isProjectRoleEditorLicensed() { isProjectRoleEditorLicensed() {
return this.isFeatureEnabled(LICENSE_FEATURES.PROJECT_ROLE_EDITOR); return this.isLicensed(LICENSE_FEATURES.PROJECT_ROLE_EDITOR);
} }
/** @deprecated Use `LicenseState.isProjectRoleViewerLicensed` instead. */
isProjectRoleViewerLicensed() { isProjectRoleViewerLicensed() {
return this.isFeatureEnabled(LICENSE_FEATURES.PROJECT_ROLE_VIEWER); return this.isLicensed(LICENSE_FEATURES.PROJECT_ROLE_VIEWER);
} }
/** @deprecated Use `LicenseState.isCustomNpmRegistryLicensed` instead. */
isCustomNpmRegistryEnabled() { isCustomNpmRegistryEnabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.COMMUNITY_NODES_CUSTOM_REGISTRY); return this.isLicensed(LICENSE_FEATURES.COMMUNITY_NODES_CUSTOM_REGISTRY);
} }
/** @deprecated Use `LicenseState.isFoldersLicensed` instead. */
isFoldersEnabled() { isFoldersEnabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.FOLDERS); return this.isLicensed(LICENSE_FEATURES.FOLDERS);
}
isInsightsSummaryEnabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.INSIGHTS_VIEW_SUMMARY);
}
isInsightsDashboardEnabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.INSIGHTS_VIEW_DASHBOARD);
}
isInsightsHourlyDataEnabled() {
return this.getFeatureValue(LICENSE_FEATURES.INSIGHTS_VIEW_HOURLY_DATA);
} }
getCurrentEntitlements() { getCurrentEntitlements() {
return this.manager?.getCurrentEntitlements() ?? []; return this.manager?.getCurrentEntitlements() ?? [];
} }
getFeatureValue<T extends keyof FeatureReturnType>(feature: T): FeatureReturnType[T] { getValue<T extends keyof FeatureReturnType>(feature: T): FeatureReturnType[T] {
return this.manager?.getFeatureValue(feature) as FeatureReturnType[T]; return this.manager?.getFeatureValue(feature) as FeatureReturnType[T];
} }
@@ -370,46 +383,54 @@ export class License {
} }
// Helper functions for computed data // Helper functions for computed data
/** @deprecated Use `LicenseState` instead. */
getUsersLimit() { getUsersLimit() {
return this.getFeatureValue(LICENSE_QUOTAS.USERS_LIMIT) ?? UNLIMITED_LICENSE_QUOTA; return this.getValue(LICENSE_QUOTAS.USERS_LIMIT) ?? UNLIMITED_LICENSE_QUOTA;
} }
/** @deprecated Use `LicenseState` instead. */
getTriggerLimit() { getTriggerLimit() {
return this.getFeatureValue(LICENSE_QUOTAS.TRIGGER_LIMIT) ?? UNLIMITED_LICENSE_QUOTA; return this.getValue(LICENSE_QUOTAS.TRIGGER_LIMIT) ?? UNLIMITED_LICENSE_QUOTA;
} }
/** @deprecated Use `LicenseState` instead. */
getVariablesLimit() { getVariablesLimit() {
return this.getFeatureValue(LICENSE_QUOTAS.VARIABLES_LIMIT) ?? UNLIMITED_LICENSE_QUOTA; return this.getValue(LICENSE_QUOTAS.VARIABLES_LIMIT) ?? UNLIMITED_LICENSE_QUOTA;
} }
/** @deprecated Use `LicenseState` instead. */
getAiCredits() { getAiCredits() {
return this.getFeatureValue(LICENSE_QUOTAS.AI_CREDITS) ?? 0; return this.getValue(LICENSE_QUOTAS.AI_CREDITS) ?? 0;
} }
/** @deprecated Use `LicenseState` instead. */
getWorkflowHistoryPruneLimit() { getWorkflowHistoryPruneLimit() {
return ( return this.getValue(LICENSE_QUOTAS.WORKFLOW_HISTORY_PRUNE_LIMIT) ?? UNLIMITED_LICENSE_QUOTA;
this.getFeatureValue(LICENSE_QUOTAS.WORKFLOW_HISTORY_PRUNE_LIMIT) ?? UNLIMITED_LICENSE_QUOTA
);
} }
/** @deprecated Use `LicenseState` instead. */
getInsightsMaxHistory() { getInsightsMaxHistory() {
return this.getFeatureValue(LICENSE_QUOTAS.INSIGHTS_MAX_HISTORY_DAYS) ?? 7; return this.getValue(LICENSE_QUOTAS.INSIGHTS_MAX_HISTORY_DAYS) ?? 7;
} }
/** @deprecated Use `LicenseState` instead. */
getInsightsRetentionMaxAge() { getInsightsRetentionMaxAge() {
return this.getFeatureValue(LICENSE_QUOTAS.INSIGHTS_RETENTION_MAX_AGE_DAYS) ?? 180; return this.getValue(LICENSE_QUOTAS.INSIGHTS_RETENTION_MAX_AGE_DAYS) ?? 180;
} }
/** @deprecated Use `LicenseState` instead. */
getInsightsRetentionPruneInterval() { getInsightsRetentionPruneInterval() {
return this.getFeatureValue(LICENSE_QUOTAS.INSIGHTS_RETENTION_PRUNE_INTERVAL_DAYS) ?? 24; return this.getValue(LICENSE_QUOTAS.INSIGHTS_RETENTION_PRUNE_INTERVAL_DAYS) ?? 24;
} }
/** @deprecated Use `LicenseState` instead. */
getTeamProjectLimit() { getTeamProjectLimit() {
return this.getFeatureValue(LICENSE_QUOTAS.TEAM_PROJECT_LIMIT) ?? 0; return this.getValue(LICENSE_QUOTAS.TEAM_PROJECT_LIMIT) ?? 0;
} }
getPlanName(): string { getPlanName(): string {
return this.getFeatureValue('planName') ?? 'Community'; return this.getValue('planName') ?? 'Community';
} }
getInfo(): string { getInfo(): string {
@@ -420,6 +441,7 @@ export class License {
return this.manager.toString(); return this.manager.toString();
} }
/** @deprecated Use `LicenseState` instead. */
isWithinUsersLimit() { isWithinUsersLimit() {
return this.getUsersLimit() === UNLIMITED_LICENSE_QUOTA; return this.getUsersLimit() === UNLIMITED_LICENSE_QUOTA;
} }

View File

@@ -1,8 +1,10 @@
import { LicenseState } from '@n8n/backend-common';
import { Container } from '@n8n/di'; import { Container } from '@n8n/di';
import { mock } from 'jest-mock-extended'; import { mock } from 'jest-mock-extended';
import type { AuthenticatedRequest } from '@/requests'; import type { AuthenticatedRequest } from '@/requests';
import { mockInstance } from '@test/mocking'; import { mockInstance } from '@test/mocking';
import { LicenseMocker } from '@test-integration/license';
import * as testDb from '@test-integration/test-db'; import * as testDb from '@test-integration/test-db';
import { TypeToNumber } from '../database/entities/insights-shared'; import { TypeToNumber } from '../database/entities/insights-shared';
@@ -12,6 +14,7 @@ import { InsightsController } from '../insights.controller';
// Initialize DB once for all tests // Initialize DB once for all tests
beforeAll(async () => { beforeAll(async () => {
await testDb.init(); await testDb.init();
new LicenseMocker().mockLicenseState(Container.get(LicenseState));
}); });
// Terminate DB once after all tests complete // Terminate DB once after all tests complete

View File

@@ -1,4 +1,5 @@
import type { InsightsDateRange } from '@n8n/api-types'; import type { InsightsDateRange } from '@n8n/api-types';
import type { LicenseState } from '@n8n/backend-common';
import type { Project } from '@n8n/db'; import type { Project } from '@n8n/db';
import type { WorkflowEntity } from '@n8n/db'; import type { WorkflowEntity } from '@n8n/db';
import type { IWorkflowDb } from '@n8n/db'; import type { IWorkflowDb } from '@n8n/db';
@@ -6,7 +7,6 @@ import { Container } from '@n8n/di';
import { mock } from 'jest-mock-extended'; import { mock } from 'jest-mock-extended';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import type { License } from '@/license';
import { createTeamProject } from '@test-integration/db/projects'; import { createTeamProject } from '@test-integration/db/projects';
import { createWorkflow } from '@test-integration/db/workflows'; import { createWorkflow } from '@test-integration/db/workflows';
import * as testDb from '@test-integration/test-db'; import * as testDb from '@test-integration/test-db';
@@ -492,10 +492,10 @@ describe('getInsightsByTime', () => {
describe('getAvailableDateRanges', () => { describe('getAvailableDateRanges', () => {
let insightsService: InsightsService; let insightsService: InsightsService;
let licenseMock: jest.Mocked<License>; let licenseMock: jest.Mocked<LicenseState>;
beforeAll(() => { beforeAll(() => {
licenseMock = mock<License>(); licenseMock = mock<LicenseState>();
insightsService = new InsightsService( insightsService = new InsightsService(
mock<InsightsByPeriodRepository>(), mock<InsightsByPeriodRepository>(),
mock<InsightsCompactionService>(), mock<InsightsCompactionService>(),
@@ -506,7 +506,7 @@ describe('getAvailableDateRanges', () => {
test('returns correct ranges when hourly data is enabled and max history is unlimited', () => { test('returns correct ranges when hourly data is enabled and max history is unlimited', () => {
licenseMock.getInsightsMaxHistory.mockReturnValue(-1); licenseMock.getInsightsMaxHistory.mockReturnValue(-1);
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(true); licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(true);
const result = insightsService.getAvailableDateRanges(); const result = insightsService.getAvailableDateRanges();
@@ -523,7 +523,7 @@ describe('getAvailableDateRanges', () => {
test('returns correct ranges when hourly data is enabled and max history is 365 days', () => { test('returns correct ranges when hourly data is enabled and max history is 365 days', () => {
licenseMock.getInsightsMaxHistory.mockReturnValue(365); licenseMock.getInsightsMaxHistory.mockReturnValue(365);
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(true); licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(true);
const result = insightsService.getAvailableDateRanges(); const result = insightsService.getAvailableDateRanges();
@@ -540,7 +540,7 @@ describe('getAvailableDateRanges', () => {
test('returns correct ranges when hourly data is disabled and max history is 30 days', () => { test('returns correct ranges when hourly data is disabled and max history is 30 days', () => {
licenseMock.getInsightsMaxHistory.mockReturnValue(30); licenseMock.getInsightsMaxHistory.mockReturnValue(30);
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(false); licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(false);
const result = insightsService.getAvailableDateRanges(); const result = insightsService.getAvailableDateRanges();
@@ -557,7 +557,7 @@ describe('getAvailableDateRanges', () => {
test('returns correct ranges when max history is less than 7 days', () => { test('returns correct ranges when max history is less than 7 days', () => {
licenseMock.getInsightsMaxHistory.mockReturnValue(5); licenseMock.getInsightsMaxHistory.mockReturnValue(5);
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(false); licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(false);
const result = insightsService.getAvailableDateRanges(); const result = insightsService.getAvailableDateRanges();
@@ -574,7 +574,7 @@ describe('getAvailableDateRanges', () => {
test('returns correct ranges when max history is 90 days and hourly data is enabled', () => { test('returns correct ranges when max history is 90 days and hourly data is enabled', () => {
licenseMock.getInsightsMaxHistory.mockReturnValue(90); licenseMock.getInsightsMaxHistory.mockReturnValue(90);
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(true); licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(true);
const result = insightsService.getAvailableDateRanges(); const result = insightsService.getAvailableDateRanges();
@@ -592,10 +592,10 @@ describe('getAvailableDateRanges', () => {
describe('getMaxAgeInDaysAndGranularity', () => { describe('getMaxAgeInDaysAndGranularity', () => {
let insightsService: InsightsService; let insightsService: InsightsService;
let licenseMock: jest.Mocked<License>; let licenseMock: jest.Mocked<LicenseState>;
beforeAll(() => { beforeAll(() => {
licenseMock = mock<License>(); licenseMock = mock<LicenseState>();
insightsService = new InsightsService( insightsService = new InsightsService(
mock<InsightsByPeriodRepository>(), mock<InsightsByPeriodRepository>(),
mock<InsightsCompactionService>(), mock<InsightsCompactionService>(),
@@ -606,7 +606,7 @@ describe('getMaxAgeInDaysAndGranularity', () => {
test('returns correct maxAgeInDays and granularity for a valid licensed date range', () => { test('returns correct maxAgeInDays and granularity for a valid licensed date range', () => {
licenseMock.getInsightsMaxHistory.mockReturnValue(365); licenseMock.getInsightsMaxHistory.mockReturnValue(365);
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(true); licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(true);
const result = insightsService.getMaxAgeInDaysAndGranularity('month'); const result = insightsService.getMaxAgeInDaysAndGranularity('month');
@@ -620,7 +620,7 @@ describe('getMaxAgeInDaysAndGranularity', () => {
test('throws an error if the date range is not available', () => { test('throws an error if the date range is not available', () => {
licenseMock.getInsightsMaxHistory.mockReturnValue(365); licenseMock.getInsightsMaxHistory.mockReturnValue(365);
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(true); licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(true);
expect(() => { expect(() => {
insightsService.getMaxAgeInDaysAndGranularity('invalidKey' as InsightsDateRange['key']); insightsService.getMaxAgeInDaysAndGranularity('invalidKey' as InsightsDateRange['key']);
@@ -629,7 +629,7 @@ describe('getMaxAgeInDaysAndGranularity', () => {
test('throws an error if the date range is not licensed', () => { test('throws an error if the date range is not licensed', () => {
licenseMock.getInsightsMaxHistory.mockReturnValue(30); licenseMock.getInsightsMaxHistory.mockReturnValue(30);
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(false); licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(false);
expect(() => { expect(() => {
insightsService.getMaxAgeInDaysAndGranularity('year'); insightsService.getMaxAgeInDaysAndGranularity('year');
@@ -638,7 +638,7 @@ describe('getMaxAgeInDaysAndGranularity', () => {
test('returns correct maxAgeInDays and granularity for a valid date range with hourly data disabled', () => { test('returns correct maxAgeInDays and granularity for a valid date range with hourly data disabled', () => {
licenseMock.getInsightsMaxHistory.mockReturnValue(90); licenseMock.getInsightsMaxHistory.mockReturnValue(90);
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(false); licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(false);
const result = insightsService.getMaxAgeInDaysAndGranularity('quarter'); const result = insightsService.getMaxAgeInDaysAndGranularity('quarter');
@@ -652,7 +652,7 @@ describe('getMaxAgeInDaysAndGranularity', () => {
test('returns correct maxAgeInDays and granularity for a valid date range with unlimited history', () => { test('returns correct maxAgeInDays and granularity for a valid date range with unlimited history', () => {
licenseMock.getInsightsMaxHistory.mockReturnValue(-1); licenseMock.getInsightsMaxHistory.mockReturnValue(-1);
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(true); licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(true);
const result = insightsService.getMaxAgeInDaysAndGranularity('day'); const result = insightsService.getMaxAgeInDaysAndGranularity('day');

View File

@@ -3,12 +3,11 @@ import {
type InsightsDateRange, type InsightsDateRange,
INSIGHTS_DATE_RANGE_KEYS, INSIGHTS_DATE_RANGE_KEYS,
} from '@n8n/api-types'; } from '@n8n/api-types';
import { LicenseState } from '@n8n/backend-common';
import { OnShutdown } from '@n8n/decorators'; import { OnShutdown } from '@n8n/decorators';
import { Service } from '@n8n/di'; import { Service } from '@n8n/di';
import { UserError } from 'n8n-workflow'; import { UserError } from 'n8n-workflow';
import { License } from '@/license';
import type { PeriodUnit, TypeUnit } from './database/entities/insights-shared'; import type { PeriodUnit, TypeUnit } from './database/entities/insights-shared';
import { NumberToType } from './database/entities/insights-shared'; import { NumberToType } from './database/entities/insights-shared';
import { InsightsByPeriodRepository } from './database/repositories/insights-by-period.repository'; import { InsightsByPeriodRepository } from './database/repositories/insights-by-period.repository';
@@ -31,7 +30,7 @@ export class InsightsService {
private readonly insightsByPeriodRepository: InsightsByPeriodRepository, private readonly insightsByPeriodRepository: InsightsByPeriodRepository,
private readonly compactionService: InsightsCompactionService, private readonly compactionService: InsightsCompactionService,
private readonly collectionService: InsightsCollectionService, private readonly collectionService: InsightsCollectionService,
private readonly license: License, private readonly licenseState: LicenseState,
) {} ) {}
startBackgroundProcess() { startBackgroundProcess() {
@@ -191,15 +190,15 @@ export class InsightsService {
*/ */
getAvailableDateRanges(): InsightsDateRange[] { getAvailableDateRanges(): InsightsDateRange[] {
const maxHistoryInDays = const maxHistoryInDays =
this.license.getInsightsMaxHistory() === -1 this.licenseState.getInsightsMaxHistory() === -1
? Number.MAX_SAFE_INTEGER ? Number.MAX_SAFE_INTEGER
: this.license.getInsightsMaxHistory(); : this.licenseState.getInsightsMaxHistory();
const isHourlyDateEnabled = this.license.isInsightsHourlyDataEnabled(); const isHourlyDateLicensed = this.licenseState.isInsightsHourlyDataLicensed();
return INSIGHTS_DATE_RANGE_KEYS.map((key) => ({ return INSIGHTS_DATE_RANGE_KEYS.map((key) => ({
key, key,
licensed: licensed:
key === 'day' ? (isHourlyDateEnabled ?? false) : maxHistoryInDays >= keyRangeToDays[key], key === 'day' ? (isHourlyDateLicensed ?? false) : maxHistoryInDays >= keyRangeToDays[key],
granularity: key === 'day' ? 'hour' : keyRangeToDays[key] <= 30 ? 'day' : 'week', granularity: key === 'day' ? 'hour' : keyRangeToDays[key] <= 30 ? 'day' : 'week',
})); }));
} }

View File

@@ -114,7 +114,7 @@ export const validLicenseWithUserQuota = (
export const isLicensed = (feature: BooleanLicenseFeature) => { export const isLicensed = (feature: BooleanLicenseFeature) => {
return async (_: AuthenticatedRequest, res: express.Response, next: express.NextFunction) => { return async (_: AuthenticatedRequest, res: express.Response, next: express.NextFunction) => {
if (Container.get(License).isFeatureEnabled(feature)) return next(); if (Container.get(License).isLicensed(feature)) return next();
return res.status(403).json({ message: new FeatureNotLicensedError(feature).message }); return res.status(403).json({ message: new FeatureNotLicensedError(feature).message });
}; };

View File

@@ -1,4 +1,5 @@
import type { FrontendSettings, ITelemetrySettings } from '@n8n/api-types'; import type { FrontendSettings, ITelemetrySettings } from '@n8n/api-types';
import { LicenseState } from '@n8n/backend-common';
import { GlobalConfig, SecurityConfig } from '@n8n/config'; import { GlobalConfig, SecurityConfig } from '@n8n/config';
import { LICENSE_FEATURES } from '@n8n/constants'; import { LICENSE_FEATURES } from '@n8n/constants';
import { Container, Service } from '@n8n/di'; import { Container, Service } from '@n8n/di';
@@ -52,6 +53,7 @@ export class FrontendService {
private readonly pushConfig: PushConfig, private readonly pushConfig: PushConfig,
private readonly binaryDataConfig: BinaryDataConfig, private readonly binaryDataConfig: BinaryDataConfig,
private readonly insightsService: InsightsService, private readonly insightsService: InsightsService,
private readonly licenseState: LicenseState,
) { ) {
loadNodesAndCredentials.addPostProcessor(async () => await this.generateTypes()); loadNodesAndCredentials.addPostProcessor(async () => await this.generateTypes());
void this.generateTypes(); void this.generateTypes();
@@ -324,7 +326,7 @@ export class FrontendService {
variables: this.license.isVariablesEnabled(), variables: this.license.isVariablesEnabled(),
sourceControl: this.license.isSourceControlLicensed(), sourceControl: this.license.isSourceControlLicensed(),
externalSecrets: this.license.isExternalSecretsEnabled(), externalSecrets: this.license.isExternalSecretsEnabled(),
showNonProdBanner: this.license.isFeatureEnabled(LICENSE_FEATURES.SHOW_NON_PROD_BANNER), showNonProdBanner: this.license.isLicensed(LICENSE_FEATURES.SHOW_NON_PROD_BANNER),
debugInEditor: this.license.isDebugInEditorLicensed(), debugInEditor: this.license.isDebugInEditorLicensed(),
binaryDataS3: isS3Available && isS3Selected && isS3Licensed, binaryDataS3: isS3Available && isS3Selected && isS3Licensed,
workflowHistory: workflowHistory:
@@ -378,8 +380,8 @@ export class FrontendService {
Object.assign(this.settings.insights, { Object.assign(this.settings.insights, {
enabled: this.modulesConfig.loadedModules.has('insights'), enabled: this.modulesConfig.loadedModules.has('insights'),
summary: this.license.isInsightsSummaryEnabled(), summary: this.licenseState.isInsightsSummaryLicensed(),
dashboard: this.license.isInsightsDashboardEnabled(), dashboard: this.licenseState.isInsightsDashboardLicensed(),
dateRanges: this.insightsService.getAvailableDateRanges(), dateRanges: this.insightsService.getAvailableDateRanges(),
}); });

View File

@@ -1,3 +1,4 @@
import type { LicenseProvider, LicenseState } from '@n8n/backend-common';
import type { BooleanLicenseFeature, NumericLicenseFeature } from '@n8n/constants'; import type { BooleanLicenseFeature, NumericLicenseFeature } from '@n8n/constants';
import type { License } from '@/license'; import type { License } from '@/license';
@@ -17,8 +18,17 @@ export class LicenseMocker {
private _defaultQuotas: Map<NumericLicenseFeature, number> = new Map(); private _defaultQuotas: Map<NumericLicenseFeature, number> = new Map();
mock(license: License) { mock(license: License) {
license.isFeatureEnabled = this.isFeatureEnabled.bind(this); license.isLicensed = this.isFeatureEnabled.bind(this);
license.getFeatureValue = this.getFeatureValue.bind(this); license.getValue = this.getFeatureValue.bind(this);
}
mockLicenseState(licenseState: LicenseState) {
const licenseProvider: LicenseProvider = {
isLicensed: this.isFeatureEnabled.bind(this),
getValue: this.getFeatureValue.bind(this),
};
licenseState.setLicenseProvider(licenseProvider);
} }
reset() { reset() {

View File

@@ -1,3 +1,4 @@
import { LicenseState } from '@n8n/backend-common';
import type { User } from '@n8n/db'; import type { User } from '@n8n/db';
import { Container } from '@n8n/di'; import { Container } from '@n8n/di';
import cookieParser from 'cookie-parser'; import cookieParser from 'cookie-parser';
@@ -125,6 +126,8 @@ export const setupTestServer = ({
config.set('userManagement.isInstanceOwnerSetUp', true); config.set('userManagement.isInstanceOwnerSetUp', true);
testServer.license.mock(Container.get(License)); testServer.license.mock(Container.get(License));
testServer.license.mockLicenseState(Container.get(LicenseState));
if (enabledFeatures) { if (enabledFeatures) {
testServer.license.setDefaults({ testServer.license.setDefaults({
features: enabledFeatures, features: enabledFeatures,

33
pnpm-lock.yaml generated
View File

@@ -387,6 +387,15 @@ importers:
packages/@n8n/backend-common: packages/@n8n/backend-common:
dependencies: dependencies:
'@n8n/constants':
specifier: workspace:^
version: link:../constants
'@n8n/di':
specifier: workspace:^
version: link:../di
n8n-workflow:
specifier: workspace:^
version: link:../../workflow
reflect-metadata: reflect-metadata:
specifier: 'catalog:' specifier: 'catalog:'
version: 0.2.2 version: 0.2.2
@@ -690,7 +699,7 @@ importers:
version: 4.3.0 version: 4.3.0
'@getzep/zep-cloud': '@getzep/zep-cloud':
specifier: 1.0.12 specifier: 1.0.12
version: 1.0.12(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)(langchain@0.3.11(73c39badb3fd5b3eb4d1084b1fb22de6)) version: 1.0.12(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)(langchain@0.3.11(e320b1d8e94e7308fefdef3743329630))
'@getzep/zep-js': '@getzep/zep-js':
specifier: 0.9.0 specifier: 0.9.0
version: 0.9.0 version: 0.9.0
@@ -717,7 +726,7 @@ importers:
version: 0.3.2(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13) version: 0.3.2(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)
'@langchain/community': '@langchain/community':
specifier: 'catalog:' specifier: 'catalog:'
version: 0.3.24(d72b3dbd91eb98a3175f929d13e7c0a7) version: 0.3.24(a23560be5fb93c23c5c4ed2a6b67082b)
'@langchain/core': '@langchain/core':
specifier: 'catalog:' specifier: 'catalog:'
version: 0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)) version: 0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))
@@ -819,7 +828,7 @@ importers:
version: 23.0.1 version: 23.0.1
langchain: langchain:
specifier: 0.3.11 specifier: 0.3.11
version: 0.3.11(73c39badb3fd5b3eb4d1084b1fb22de6) version: 0.3.11(e320b1d8e94e7308fefdef3743329630)
lodash: lodash:
specifier: 'catalog:' specifier: 'catalog:'
version: 4.17.21 version: 4.17.21
@@ -16363,7 +16372,7 @@ snapshots:
'@gar/promisify@1.1.3': '@gar/promisify@1.1.3':
optional: true optional: true
'@getzep/zep-cloud@1.0.12(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)(langchain@0.3.11(73c39badb3fd5b3eb4d1084b1fb22de6))': '@getzep/zep-cloud@1.0.12(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)(langchain@0.3.11(e320b1d8e94e7308fefdef3743329630))':
dependencies: dependencies:
form-data: 4.0.0 form-data: 4.0.0
node-fetch: 2.7.0(encoding@0.1.13) node-fetch: 2.7.0(encoding@0.1.13)
@@ -16372,7 +16381,7 @@ snapshots:
zod: 3.24.1 zod: 3.24.1
optionalDependencies: optionalDependencies:
'@langchain/core': 0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)) '@langchain/core': 0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))
langchain: 0.3.11(73c39badb3fd5b3eb4d1084b1fb22de6) langchain: 0.3.11(e320b1d8e94e7308fefdef3743329630)
transitivePeerDependencies: transitivePeerDependencies:
- encoding - encoding
@@ -16887,7 +16896,7 @@ snapshots:
- aws-crt - aws-crt
- encoding - encoding
'@langchain/community@0.3.24(d72b3dbd91eb98a3175f929d13e7c0a7)': '@langchain/community@0.3.24(a23560be5fb93c23c5c4ed2a6b67082b)':
dependencies: dependencies:
'@browserbasehq/stagehand': 1.9.0(@playwright/test@1.49.1)(deepmerge@4.3.1)(dotenv@16.4.5)(encoding@0.1.13)(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))(zod@3.24.1) '@browserbasehq/stagehand': 1.9.0(@playwright/test@1.49.1)(deepmerge@4.3.1)(dotenv@16.4.5)(encoding@0.1.13)(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))(zod@3.24.1)
'@ibm-cloud/watsonx-ai': 1.1.2 '@ibm-cloud/watsonx-ai': 1.1.2
@@ -16898,7 +16907,7 @@ snapshots:
flat: 5.0.2 flat: 5.0.2
ibm-cloud-sdk-core: 5.3.2 ibm-cloud-sdk-core: 5.3.2
js-yaml: 4.1.0 js-yaml: 4.1.0
langchain: 0.3.11(73c39badb3fd5b3eb4d1084b1fb22de6) langchain: 0.3.11(e320b1d8e94e7308fefdef3743329630)
langsmith: 0.2.15(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)) langsmith: 0.2.15(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))
openai: 4.78.1(encoding@0.1.13)(zod@3.24.1) openai: 4.78.1(encoding@0.1.13)(zod@3.24.1)
uuid: 10.0.0 uuid: 10.0.0
@@ -16913,7 +16922,7 @@ snapshots:
'@aws-sdk/credential-provider-node': 3.666.0(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@aws-sdk/client-sts@3.666.0) '@aws-sdk/credential-provider-node': 3.666.0(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@aws-sdk/client-sts@3.666.0)
'@azure/storage-blob': 12.18.0(encoding@0.1.13) '@azure/storage-blob': 12.18.0(encoding@0.1.13)
'@browserbasehq/sdk': 2.0.0(encoding@0.1.13) '@browserbasehq/sdk': 2.0.0(encoding@0.1.13)
'@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)(langchain@0.3.11(73c39badb3fd5b3eb4d1084b1fb22de6)) '@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)(langchain@0.3.11(e320b1d8e94e7308fefdef3743329630))
'@getzep/zep-js': 0.9.0 '@getzep/zep-js': 0.9.0
'@google-ai/generativelanguage': 2.6.0(encoding@0.1.13) '@google-ai/generativelanguage': 2.6.0(encoding@0.1.13)
'@google-cloud/storage': 7.12.1(encoding@0.1.13) '@google-cloud/storage': 7.12.1(encoding@0.1.13)
@@ -23107,7 +23116,7 @@ snapshots:
'@types/debug': 4.1.12 '@types/debug': 4.1.12
'@types/node': 18.16.16 '@types/node': 18.16.16
'@types/tough-cookie': 4.0.2 '@types/tough-cookie': 4.0.2
axios: 1.8.3(debug@4.4.0) axios: 1.8.3
camelcase: 6.3.0 camelcase: 6.3.0
debug: 4.4.0(supports-color@8.1.1) debug: 4.4.0(supports-color@8.1.1)
dotenv: 16.4.5 dotenv: 16.4.5
@@ -23117,7 +23126,7 @@ snapshots:
isstream: 0.1.2 isstream: 0.1.2
jsonwebtoken: 9.0.2 jsonwebtoken: 9.0.2
mime-types: 2.1.35 mime-types: 2.1.35
retry-axios: 2.6.0(axios@1.8.3) retry-axios: 2.6.0(axios@1.8.3(debug@4.4.0))
tough-cookie: 4.1.3 tough-cookie: 4.1.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -24111,7 +24120,7 @@ snapshots:
kuler@2.0.0: {} kuler@2.0.0: {}
langchain@0.3.11(73c39badb3fd5b3eb4d1084b1fb22de6): langchain@0.3.11(e320b1d8e94e7308fefdef3743329630):
dependencies: dependencies:
'@langchain/core': 0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)) '@langchain/core': 0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))
'@langchain/openai': 0.3.17(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13) '@langchain/openai': 0.3.17(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13)
@@ -26484,7 +26493,7 @@ snapshots:
onetime: 5.1.2 onetime: 5.1.2
signal-exit: 3.0.7 signal-exit: 3.0.7
retry-axios@2.6.0(axios@1.8.3): retry-axios@2.6.0(axios@1.8.3(debug@4.4.0)):
dependencies: dependencies:
axios: 1.8.3 axios: 1.8.3