mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
refactor(core): Separate license state from license service (#15097)
This commit is contained in:
@@ -21,6 +21,9 @@
|
||||
"dist/**/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"@n8n/constants": "workspace:^",
|
||||
"@n8n/di": "workspace:^",
|
||||
"n8n-workflow": "workspace:^",
|
||||
"reflect-metadata": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export {};
|
||||
export * from './license-state';
|
||||
export * from './types';
|
||||
|
||||
192
packages/@n8n/backend-common/src/license-state.ts
Normal file
192
packages/@n8n/backend-common/src/license-state.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
15
packages/@n8n/backend-common/src/types.ts
Normal file
15
packages/@n8n/backend-common/src/types.ts
Normal 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];
|
||||
}
|
||||
@@ -108,15 +108,15 @@ describe('ControllerRegistry', () => {
|
||||
});
|
||||
|
||||
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);
|
||||
expect(license.isFeatureEnabled).toHaveBeenCalled();
|
||||
expect(license.isLicensed).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
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);
|
||||
expect(license.isFeatureEnabled).toHaveBeenCalled();
|
||||
expect(license.isLicensed).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -111,13 +111,13 @@ describe('License', () => {
|
||||
});
|
||||
|
||||
test('check if feature is enabled', () => {
|
||||
license.isFeatureEnabled(MOCK_FEATURE_FLAG);
|
||||
license.isLicensed(MOCK_FEATURE_FLAG);
|
||||
|
||||
expect(LicenseManager.prototype.hasFeatureEnabled).toHaveBeenCalledWith(MOCK_FEATURE_FLAG);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
@@ -129,7 +129,7 @@ describe('License', () => {
|
||||
});
|
||||
|
||||
test('check fetching feature values', async () => {
|
||||
license.getFeatureValue(MOCK_FEATURE_FLAG);
|
||||
license.getValue(MOCK_FEATURE_FLAG);
|
||||
|
||||
expect(LicenseManager.prototype.getFeatureValue).toHaveBeenCalledWith(MOCK_FEATURE_FLAG);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'reflect-metadata';
|
||||
import { LicenseState } from '@n8n/backend-common';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import { LICENSE_FEATURES } from '@n8n/constants';
|
||||
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) {
|
||||
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.',
|
||||
@@ -241,6 +242,8 @@ export abstract class BaseCommand extends Command {
|
||||
this.license = Container.get(License);
|
||||
await this.license.init();
|
||||
|
||||
Container.get(LicenseState).setLicenseProvider(this.license);
|
||||
|
||||
const { activationKey } = this.globalConfig.license;
|
||||
|
||||
if (activationKey) {
|
||||
|
||||
@@ -106,7 +106,7 @@ export class ControllerRegistry {
|
||||
|
||||
private createLicenseMiddleware(feature: BooleanLicenseFeature): RequestHandler {
|
||||
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' });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -153,8 +153,7 @@ export class E2EController {
|
||||
private readonly userRepository: UserRepository,
|
||||
private readonly authUserRepository: AuthUserRepository,
|
||||
) {
|
||||
license.isFeatureEnabled = (feature: BooleanLicenseFeature) =>
|
||||
this.enabledFeatures[feature] ?? false;
|
||||
license.isLicensed = (feature: BooleanLicenseFeature) => this.enabledFeatures[feature] ?? false;
|
||||
|
||||
// Ugly hack to satisfy biome parser
|
||||
const getFeatureValue = <T extends keyof FeatureReturnType>(
|
||||
@@ -166,7 +165,7 @@ export class E2EController {
|
||||
return UNLIMITED_LICENSE_QUOTA as FeatureReturnType[T];
|
||||
}
|
||||
};
|
||||
license.getFeatureValue = getFeatureValue;
|
||||
license.getValue = getFeatureValue;
|
||||
|
||||
license.getPlanName = () => 'Enterprise';
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { LicenseProvider } from '@n8n/backend-common';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import {
|
||||
LICENSE_FEATURES,
|
||||
@@ -28,7 +29,7 @@ export type FeatureReturnType = Partial<
|
||||
>;
|
||||
|
||||
@Service()
|
||||
export class License {
|
||||
export class License implements LicenseProvider {
|
||||
private manager: LicenseManager | undefined;
|
||||
|
||||
private isShuttingDown = false;
|
||||
@@ -218,123 +219,135 @@ export class License {
|
||||
this.logger.debug('License shut down');
|
||||
}
|
||||
|
||||
isFeatureEnabled(feature: BooleanLicenseFeature) {
|
||||
isLicensed(feature: BooleanLicenseFeature) {
|
||||
return this.manager?.hasFeatureEnabled(feature) ?? false;
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isSharingLicensed` instead. */
|
||||
isSharingEnabled() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.SHARING);
|
||||
return this.isLicensed(LICENSE_FEATURES.SHARING);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isLogStreamingLicensed` instead. */
|
||||
isLogStreamingEnabled() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.LOG_STREAMING);
|
||||
return this.isLicensed(LICENSE_FEATURES.LOG_STREAMING);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isLdapLicensed` instead. */
|
||||
isLdapEnabled() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.LDAP);
|
||||
return this.isLicensed(LICENSE_FEATURES.LDAP);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isSamlLicensed` instead. */
|
||||
isSamlEnabled() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.SAML);
|
||||
return this.isLicensed(LICENSE_FEATURES.SAML);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isApiKeyScopesLicensed` instead. */
|
||||
isApiKeyScopesEnabled() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.API_KEY_SCOPES);
|
||||
return this.isLicensed(LICENSE_FEATURES.API_KEY_SCOPES);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isAiAssistantLicensed` instead. */
|
||||
isAiAssistantEnabled() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.AI_ASSISTANT);
|
||||
return this.isLicensed(LICENSE_FEATURES.AI_ASSISTANT);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isAskAiLicensed` instead. */
|
||||
isAskAiEnabled() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.ASK_AI);
|
||||
return this.isLicensed(LICENSE_FEATURES.ASK_AI);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isAiCreditsLicensed` instead. */
|
||||
isAiCreditsEnabled() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.AI_CREDITS);
|
||||
return this.isLicensed(LICENSE_FEATURES.AI_CREDITS);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isAdvancedExecutionFiltersLicensed` instead. */
|
||||
isAdvancedExecutionFiltersEnabled() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.ADVANCED_EXECUTION_FILTERS);
|
||||
return this.isLicensed(LICENSE_FEATURES.ADVANCED_EXECUTION_FILTERS);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isAdvancedPermissionsLicensed` instead. */
|
||||
isAdvancedPermissionsLicensed() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.ADVANCED_PERMISSIONS);
|
||||
return this.isLicensed(LICENSE_FEATURES.ADVANCED_PERMISSIONS);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isDebugInEditorLicensed` instead. */
|
||||
isDebugInEditorLicensed() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.DEBUG_IN_EDITOR);
|
||||
return this.isLicensed(LICENSE_FEATURES.DEBUG_IN_EDITOR);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isBinaryDataS3Licensed` instead. */
|
||||
isBinaryDataS3Licensed() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.BINARY_DATA_S3);
|
||||
return this.isLicensed(LICENSE_FEATURES.BINARY_DATA_S3);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isMultiMainLicensed` instead. */
|
||||
isMultiMainLicensed() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.MULTIPLE_MAIN_INSTANCES);
|
||||
return this.isLicensed(LICENSE_FEATURES.MULTIPLE_MAIN_INSTANCES);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isVariablesLicensed` instead. */
|
||||
isVariablesEnabled() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.VARIABLES);
|
||||
return this.isLicensed(LICENSE_FEATURES.VARIABLES);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isSourceControlLicensed` instead. */
|
||||
isSourceControlLicensed() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.SOURCE_CONTROL);
|
||||
return this.isLicensed(LICENSE_FEATURES.SOURCE_CONTROL);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isExternalSecretsLicensed` instead. */
|
||||
isExternalSecretsEnabled() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.EXTERNAL_SECRETS);
|
||||
return this.isLicensed(LICENSE_FEATURES.EXTERNAL_SECRETS);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isWorkflowHistoryLicensed` instead. */
|
||||
isWorkflowHistoryLicensed() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.WORKFLOW_HISTORY);
|
||||
return this.isLicensed(LICENSE_FEATURES.WORKFLOW_HISTORY);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isAPIDisabled` instead. */
|
||||
isAPIDisabled() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.API_DISABLED);
|
||||
return this.isLicensed(LICENSE_FEATURES.API_DISABLED);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isWorkerViewLicensed` instead. */
|
||||
isWorkerViewLicensed() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.WORKER_VIEW);
|
||||
return this.isLicensed(LICENSE_FEATURES.WORKER_VIEW);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isProjectRoleAdminLicensed` instead. */
|
||||
isProjectRoleAdminLicensed() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.PROJECT_ROLE_ADMIN);
|
||||
return this.isLicensed(LICENSE_FEATURES.PROJECT_ROLE_ADMIN);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isProjectRoleEditorLicensed` instead. */
|
||||
isProjectRoleEditorLicensed() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.PROJECT_ROLE_EDITOR);
|
||||
return this.isLicensed(LICENSE_FEATURES.PROJECT_ROLE_EDITOR);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isProjectRoleViewerLicensed` instead. */
|
||||
isProjectRoleViewerLicensed() {
|
||||
return this.isFeatureEnabled(LICENSE_FEATURES.PROJECT_ROLE_VIEWER);
|
||||
return this.isLicensed(LICENSE_FEATURES.PROJECT_ROLE_VIEWER);
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState.isCustomNpmRegistryLicensed` instead. */
|
||||
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() {
|
||||
return this.isFeatureEnabled(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);
|
||||
return this.isLicensed(LICENSE_FEATURES.FOLDERS);
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
@@ -370,46 +383,54 @@ export class License {
|
||||
}
|
||||
|
||||
// Helper functions for computed data
|
||||
|
||||
/** @deprecated Use `LicenseState` instead. */
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
return this.getFeatureValue(LICENSE_QUOTAS.AI_CREDITS) ?? 0;
|
||||
return this.getValue(LICENSE_QUOTAS.AI_CREDITS) ?? 0;
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState` instead. */
|
||||
getWorkflowHistoryPruneLimit() {
|
||||
return (
|
||||
this.getFeatureValue(LICENSE_QUOTAS.WORKFLOW_HISTORY_PRUNE_LIMIT) ?? UNLIMITED_LICENSE_QUOTA
|
||||
);
|
||||
return this.getValue(LICENSE_QUOTAS.WORKFLOW_HISTORY_PRUNE_LIMIT) ?? UNLIMITED_LICENSE_QUOTA;
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState` instead. */
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
return this.getFeatureValue(LICENSE_QUOTAS.TEAM_PROJECT_LIMIT) ?? 0;
|
||||
return this.getValue(LICENSE_QUOTAS.TEAM_PROJECT_LIMIT) ?? 0;
|
||||
}
|
||||
|
||||
getPlanName(): string {
|
||||
return this.getFeatureValue('planName') ?? 'Community';
|
||||
return this.getValue('planName') ?? 'Community';
|
||||
}
|
||||
|
||||
getInfo(): string {
|
||||
@@ -420,6 +441,7 @@ export class License {
|
||||
return this.manager.toString();
|
||||
}
|
||||
|
||||
/** @deprecated Use `LicenseState` instead. */
|
||||
isWithinUsersLimit() {
|
||||
return this.getUsersLimit() === UNLIMITED_LICENSE_QUOTA;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { LicenseState } from '@n8n/backend-common';
|
||||
import { Container } from '@n8n/di';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
|
||||
import type { AuthenticatedRequest } from '@/requests';
|
||||
import { mockInstance } from '@test/mocking';
|
||||
import { LicenseMocker } from '@test-integration/license';
|
||||
import * as testDb from '@test-integration/test-db';
|
||||
|
||||
import { TypeToNumber } from '../database/entities/insights-shared';
|
||||
@@ -12,6 +14,7 @@ import { InsightsController } from '../insights.controller';
|
||||
// Initialize DB once for all tests
|
||||
beforeAll(async () => {
|
||||
await testDb.init();
|
||||
new LicenseMocker().mockLicenseState(Container.get(LicenseState));
|
||||
});
|
||||
|
||||
// Terminate DB once after all tests complete
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { InsightsDateRange } from '@n8n/api-types';
|
||||
import type { LicenseState } from '@n8n/backend-common';
|
||||
import type { Project } from '@n8n/db';
|
||||
import type { WorkflowEntity } 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 { DateTime } from 'luxon';
|
||||
|
||||
import type { License } from '@/license';
|
||||
import { createTeamProject } from '@test-integration/db/projects';
|
||||
import { createWorkflow } from '@test-integration/db/workflows';
|
||||
import * as testDb from '@test-integration/test-db';
|
||||
@@ -492,10 +492,10 @@ describe('getInsightsByTime', () => {
|
||||
|
||||
describe('getAvailableDateRanges', () => {
|
||||
let insightsService: InsightsService;
|
||||
let licenseMock: jest.Mocked<License>;
|
||||
let licenseMock: jest.Mocked<LicenseState>;
|
||||
|
||||
beforeAll(() => {
|
||||
licenseMock = mock<License>();
|
||||
licenseMock = mock<LicenseState>();
|
||||
insightsService = new InsightsService(
|
||||
mock<InsightsByPeriodRepository>(),
|
||||
mock<InsightsCompactionService>(),
|
||||
@@ -506,7 +506,7 @@ describe('getAvailableDateRanges', () => {
|
||||
|
||||
test('returns correct ranges when hourly data is enabled and max history is unlimited', () => {
|
||||
licenseMock.getInsightsMaxHistory.mockReturnValue(-1);
|
||||
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(true);
|
||||
licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(true);
|
||||
|
||||
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', () => {
|
||||
licenseMock.getInsightsMaxHistory.mockReturnValue(365);
|
||||
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(true);
|
||||
licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(true);
|
||||
|
||||
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', () => {
|
||||
licenseMock.getInsightsMaxHistory.mockReturnValue(30);
|
||||
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(false);
|
||||
licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(false);
|
||||
|
||||
const result = insightsService.getAvailableDateRanges();
|
||||
|
||||
@@ -557,7 +557,7 @@ describe('getAvailableDateRanges', () => {
|
||||
|
||||
test('returns correct ranges when max history is less than 7 days', () => {
|
||||
licenseMock.getInsightsMaxHistory.mockReturnValue(5);
|
||||
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(false);
|
||||
licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(false);
|
||||
|
||||
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', () => {
|
||||
licenseMock.getInsightsMaxHistory.mockReturnValue(90);
|
||||
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(true);
|
||||
licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(true);
|
||||
|
||||
const result = insightsService.getAvailableDateRanges();
|
||||
|
||||
@@ -592,10 +592,10 @@ describe('getAvailableDateRanges', () => {
|
||||
|
||||
describe('getMaxAgeInDaysAndGranularity', () => {
|
||||
let insightsService: InsightsService;
|
||||
let licenseMock: jest.Mocked<License>;
|
||||
let licenseMock: jest.Mocked<LicenseState>;
|
||||
|
||||
beforeAll(() => {
|
||||
licenseMock = mock<License>();
|
||||
licenseMock = mock<LicenseState>();
|
||||
insightsService = new InsightsService(
|
||||
mock<InsightsByPeriodRepository>(),
|
||||
mock<InsightsCompactionService>(),
|
||||
@@ -606,7 +606,7 @@ describe('getMaxAgeInDaysAndGranularity', () => {
|
||||
|
||||
test('returns correct maxAgeInDays and granularity for a valid licensed date range', () => {
|
||||
licenseMock.getInsightsMaxHistory.mockReturnValue(365);
|
||||
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(true);
|
||||
licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(true);
|
||||
|
||||
const result = insightsService.getMaxAgeInDaysAndGranularity('month');
|
||||
|
||||
@@ -620,7 +620,7 @@ describe('getMaxAgeInDaysAndGranularity', () => {
|
||||
|
||||
test('throws an error if the date range is not available', () => {
|
||||
licenseMock.getInsightsMaxHistory.mockReturnValue(365);
|
||||
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(true);
|
||||
licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(true);
|
||||
|
||||
expect(() => {
|
||||
insightsService.getMaxAgeInDaysAndGranularity('invalidKey' as InsightsDateRange['key']);
|
||||
@@ -629,7 +629,7 @@ describe('getMaxAgeInDaysAndGranularity', () => {
|
||||
|
||||
test('throws an error if the date range is not licensed', () => {
|
||||
licenseMock.getInsightsMaxHistory.mockReturnValue(30);
|
||||
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(false);
|
||||
licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(false);
|
||||
|
||||
expect(() => {
|
||||
insightsService.getMaxAgeInDaysAndGranularity('year');
|
||||
@@ -638,7 +638,7 @@ describe('getMaxAgeInDaysAndGranularity', () => {
|
||||
|
||||
test('returns correct maxAgeInDays and granularity for a valid date range with hourly data disabled', () => {
|
||||
licenseMock.getInsightsMaxHistory.mockReturnValue(90);
|
||||
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(false);
|
||||
licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(false);
|
||||
|
||||
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', () => {
|
||||
licenseMock.getInsightsMaxHistory.mockReturnValue(-1);
|
||||
licenseMock.isInsightsHourlyDataEnabled.mockReturnValue(true);
|
||||
licenseMock.isInsightsHourlyDataLicensed.mockReturnValue(true);
|
||||
|
||||
const result = insightsService.getMaxAgeInDaysAndGranularity('day');
|
||||
|
||||
|
||||
@@ -3,12 +3,11 @@ import {
|
||||
type InsightsDateRange,
|
||||
INSIGHTS_DATE_RANGE_KEYS,
|
||||
} from '@n8n/api-types';
|
||||
import { LicenseState } from '@n8n/backend-common';
|
||||
import { OnShutdown } from '@n8n/decorators';
|
||||
import { Service } from '@n8n/di';
|
||||
import { UserError } from 'n8n-workflow';
|
||||
|
||||
import { License } from '@/license';
|
||||
|
||||
import type { PeriodUnit, TypeUnit } from './database/entities/insights-shared';
|
||||
import { NumberToType } from './database/entities/insights-shared';
|
||||
import { InsightsByPeriodRepository } from './database/repositories/insights-by-period.repository';
|
||||
@@ -31,7 +30,7 @@ export class InsightsService {
|
||||
private readonly insightsByPeriodRepository: InsightsByPeriodRepository,
|
||||
private readonly compactionService: InsightsCompactionService,
|
||||
private readonly collectionService: InsightsCollectionService,
|
||||
private readonly license: License,
|
||||
private readonly licenseState: LicenseState,
|
||||
) {}
|
||||
|
||||
startBackgroundProcess() {
|
||||
@@ -191,15 +190,15 @@ export class InsightsService {
|
||||
*/
|
||||
getAvailableDateRanges(): InsightsDateRange[] {
|
||||
const maxHistoryInDays =
|
||||
this.license.getInsightsMaxHistory() === -1
|
||||
this.licenseState.getInsightsMaxHistory() === -1
|
||||
? Number.MAX_SAFE_INTEGER
|
||||
: this.license.getInsightsMaxHistory();
|
||||
const isHourlyDateEnabled = this.license.isInsightsHourlyDataEnabled();
|
||||
: this.licenseState.getInsightsMaxHistory();
|
||||
const isHourlyDateLicensed = this.licenseState.isInsightsHourlyDataLicensed();
|
||||
|
||||
return INSIGHTS_DATE_RANGE_KEYS.map((key) => ({
|
||||
key,
|
||||
licensed:
|
||||
key === 'day' ? (isHourlyDateEnabled ?? false) : maxHistoryInDays >= keyRangeToDays[key],
|
||||
key === 'day' ? (isHourlyDateLicensed ?? false) : maxHistoryInDays >= keyRangeToDays[key],
|
||||
granularity: key === 'day' ? 'hour' : keyRangeToDays[key] <= 30 ? 'day' : 'week',
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ export const validLicenseWithUserQuota = (
|
||||
|
||||
export const isLicensed = (feature: BooleanLicenseFeature) => {
|
||||
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 });
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { FrontendSettings, ITelemetrySettings } from '@n8n/api-types';
|
||||
import { LicenseState } from '@n8n/backend-common';
|
||||
import { GlobalConfig, SecurityConfig } from '@n8n/config';
|
||||
import { LICENSE_FEATURES } from '@n8n/constants';
|
||||
import { Container, Service } from '@n8n/di';
|
||||
@@ -52,6 +53,7 @@ export class FrontendService {
|
||||
private readonly pushConfig: PushConfig,
|
||||
private readonly binaryDataConfig: BinaryDataConfig,
|
||||
private readonly insightsService: InsightsService,
|
||||
private readonly licenseState: LicenseState,
|
||||
) {
|
||||
loadNodesAndCredentials.addPostProcessor(async () => await this.generateTypes());
|
||||
void this.generateTypes();
|
||||
@@ -324,7 +326,7 @@ export class FrontendService {
|
||||
variables: this.license.isVariablesEnabled(),
|
||||
sourceControl: this.license.isSourceControlLicensed(),
|
||||
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(),
|
||||
binaryDataS3: isS3Available && isS3Selected && isS3Licensed,
|
||||
workflowHistory:
|
||||
@@ -378,8 +380,8 @@ export class FrontendService {
|
||||
|
||||
Object.assign(this.settings.insights, {
|
||||
enabled: this.modulesConfig.loadedModules.has('insights'),
|
||||
summary: this.license.isInsightsSummaryEnabled(),
|
||||
dashboard: this.license.isInsightsDashboardEnabled(),
|
||||
summary: this.licenseState.isInsightsSummaryLicensed(),
|
||||
dashboard: this.licenseState.isInsightsDashboardLicensed(),
|
||||
dateRanges: this.insightsService.getAvailableDateRanges(),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { LicenseProvider, LicenseState } from '@n8n/backend-common';
|
||||
import type { BooleanLicenseFeature, NumericLicenseFeature } from '@n8n/constants';
|
||||
|
||||
import type { License } from '@/license';
|
||||
@@ -17,8 +18,17 @@ export class LicenseMocker {
|
||||
private _defaultQuotas: Map<NumericLicenseFeature, number> = new Map();
|
||||
|
||||
mock(license: License) {
|
||||
license.isFeatureEnabled = this.isFeatureEnabled.bind(this);
|
||||
license.getFeatureValue = this.getFeatureValue.bind(this);
|
||||
license.isLicensed = this.isFeatureEnabled.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() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LicenseState } from '@n8n/backend-common';
|
||||
import type { User } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import cookieParser from 'cookie-parser';
|
||||
@@ -125,6 +126,8 @@ export const setupTestServer = ({
|
||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||
|
||||
testServer.license.mock(Container.get(License));
|
||||
testServer.license.mockLicenseState(Container.get(LicenseState));
|
||||
|
||||
if (enabledFeatures) {
|
||||
testServer.license.setDefaults({
|
||||
features: enabledFeatures,
|
||||
|
||||
33
pnpm-lock.yaml
generated
33
pnpm-lock.yaml
generated
@@ -387,6 +387,15 @@ importers:
|
||||
|
||||
packages/@n8n/backend-common:
|
||||
dependencies:
|
||||
'@n8n/constants':
|
||||
specifier: workspace:^
|
||||
version: link:../constants
|
||||
'@n8n/di':
|
||||
specifier: workspace:^
|
||||
version: link:../di
|
||||
n8n-workflow:
|
||||
specifier: workspace:^
|
||||
version: link:../../workflow
|
||||
reflect-metadata:
|
||||
specifier: 'catalog:'
|
||||
version: 0.2.2
|
||||
@@ -690,7 +699,7 @@ importers:
|
||||
version: 4.3.0
|
||||
'@getzep/zep-cloud':
|
||||
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':
|
||||
specifier: 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)
|
||||
'@langchain/community':
|
||||
specifier: 'catalog:'
|
||||
version: 0.3.24(d72b3dbd91eb98a3175f929d13e7c0a7)
|
||||
version: 0.3.24(a23560be5fb93c23c5c4ed2a6b67082b)
|
||||
'@langchain/core':
|
||||
specifier: 'catalog:'
|
||||
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
|
||||
langchain:
|
||||
specifier: 0.3.11
|
||||
version: 0.3.11(73c39badb3fd5b3eb4d1084b1fb22de6)
|
||||
version: 0.3.11(e320b1d8e94e7308fefdef3743329630)
|
||||
lodash:
|
||||
specifier: 'catalog:'
|
||||
version: 4.17.21
|
||||
@@ -16363,7 +16372,7 @@ snapshots:
|
||||
'@gar/promisify@1.1.3':
|
||||
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:
|
||||
form-data: 4.0.0
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
@@ -16372,7 +16381,7 @@ snapshots:
|
||||
zod: 3.24.1
|
||||
optionalDependencies:
|
||||
'@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:
|
||||
- encoding
|
||||
|
||||
@@ -16887,7 +16896,7 @@ snapshots:
|
||||
- aws-crt
|
||||
- encoding
|
||||
|
||||
'@langchain/community@0.3.24(d72b3dbd91eb98a3175f929d13e7c0a7)':
|
||||
'@langchain/community@0.3.24(a23560be5fb93c23c5c4ed2a6b67082b)':
|
||||
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)
|
||||
'@ibm-cloud/watsonx-ai': 1.1.2
|
||||
@@ -16898,7 +16907,7 @@ snapshots:
|
||||
flat: 5.0.2
|
||||
ibm-cloud-sdk-core: 5.3.2
|
||||
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))
|
||||
openai: 4.78.1(encoding@0.1.13)(zod@3.24.1)
|
||||
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)
|
||||
'@azure/storage-blob': 12.18.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
|
||||
'@google-ai/generativelanguage': 2.6.0(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/node': 18.16.16
|
||||
'@types/tough-cookie': 4.0.2
|
||||
axios: 1.8.3(debug@4.4.0)
|
||||
axios: 1.8.3
|
||||
camelcase: 6.3.0
|
||||
debug: 4.4.0(supports-color@8.1.1)
|
||||
dotenv: 16.4.5
|
||||
@@ -23117,7 +23126,7 @@ snapshots:
|
||||
isstream: 0.1.2
|
||||
jsonwebtoken: 9.0.2
|
||||
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
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -24111,7 +24120,7 @@ snapshots:
|
||||
|
||||
kuler@2.0.0: {}
|
||||
|
||||
langchain@0.3.11(73c39badb3fd5b3eb4d1084b1fb22de6):
|
||||
langchain@0.3.11(e320b1d8e94e7308fefdef3743329630):
|
||||
dependencies:
|
||||
'@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)
|
||||
@@ -26484,7 +26493,7 @@ snapshots:
|
||||
onetime: 5.1.2
|
||||
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:
|
||||
axios: 1.8.3
|
||||
|
||||
|
||||
Reference in New Issue
Block a user