Files
twenty/packages/twenty-front/src/modules/client-config/hooks/useClientConfig.ts
T
Félix Malfait c6f11d8adb fix: migrate driver modules to DriverFactoryBase lazy-loading pattern (#18731)
## Summary

- Migrates `LogicFunctionModule`, `CodeInterpreterModule`, and
`CaptchaModule` from the `forRootAsync` + injection token pattern to the
`DriverFactoryBase` lazy-loading pattern (matching `EmailModule` and
`FileStorageModule`)
- Fixes #18724 where `LOGIC_FUNCTION_TYPE` was not respected in worker
processes because the driver was created at module boot time before the
DB config cache was loaded
- Removes `isEnvOnly` from `LOGIC_FUNCTION_TYPE`,
`CODE_INTERPRETER_TYPE`, `CAPTCHA_DRIVER`, `IS_MULTIWORKSPACE_ENABLED`,
and `FRONTEND_URL` — these can now be safely configured via the database
at runtime

## How it works

Each migrated module now uses a `DriverFactory` (extending
`DriverFactoryBase`) instead of a module-level async factory + Symbol
injection token:

1. **Lazy creation**: `getCurrentDriver()` creates the driver on first
call, after `DatabaseConfigDriver.onModuleInit()` has loaded the DB
cache
2. **Auto-recreation**: If config changes in the DB, the next
`getCurrentDriver()` call detects the key mismatch and creates a new
driver instance
3. **Unified config**: Both server and worker read from the same
database — driver config only needs to be set once

### Files deleted (old pattern)
- `logic-function-module.factory.ts`,
`logic-function-drivers.module.ts`, `logic-function-driver.constants.ts`
- `code-interpreter-module.factory.ts`
- `captcha.module-factory.ts`, `captcha-driver.constants.ts`

### Files created (new pattern)
- `logic-function-driver.factory.ts`
- `code-interpreter-driver.factory.ts`
- `captcha-driver.factory.ts`

Net: **-150 lines**

## Test plan

- [x] `npx nx typecheck twenty-server` passes
- [x] `npx nx lint:diff-with-main twenty-server` passes
- [ ] Integration tests pass (`npx nx run
twenty-server:test:integration:with-db-reset`)
- [ ] Verify logic functions execute in workflow runs (the original bug)
- [ ] Verify code interpreter works in workflow code steps
- [ ] Verify captcha validation works on sign-up (when captcha is
configured)


Made with [Cursor](https://cursor.com)
2026-03-18 16:00:45 +01:00

248 lines
9.7 KiB
TypeScript

import { aiModelsState } from '@/client-config/states/aiModelsState';
import { apiConfigState } from '@/client-config/states/apiConfigState';
import { appVersionState } from '@/client-config/states/appVersionState';
import { authProvidersState } from '@/client-config/states/authProvidersState';
import { billingState } from '@/client-config/states/billingState';
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState';
import { captchaState } from '@/client-config/states/captchaState';
import { isAnalyticsEnabledState } from '@/client-config/states/isAnalyticsEnabledState';
import { isAttachmentPreviewEnabledState } from '@/client-config/states/isAttachmentPreviewEnabledState';
import { isConfigVariablesInDbEnabledState } from '@/client-config/states/isConfigVariablesInDbEnabledState';
import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState';
import { isClickHouseConfiguredState } from '@/client-config/states/isClickHouseConfiguredState';
import { isCloudflareIntegrationEnabledState } from '@/client-config/states/isCloudflareIntegrationEnabledState';
import { isEmailingDomainsEnabledState } from '@/client-config/states/isEmailingDomainsEnabledState';
import { isEmailVerificationRequiredState } from '@/client-config/states/isEmailVerificationRequiredState';
import { isGoogleCalendarEnabledState } from '@/client-config/states/isGoogleCalendarEnabledState';
import { isGoogleMessagingEnabledState } from '@/client-config/states/isGoogleMessagingEnabledState';
import { isImapSmtpCaldavEnabledState } from '@/client-config/states/isImapSmtpCaldavEnabledState';
import { isMicrosoftCalendarEnabledState } from '@/client-config/states/isMicrosoftCalendarEnabledState';
import { isMicrosoftMessagingEnabledState } from '@/client-config/states/isMicrosoftMessagingEnabledState';
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
import { labPublicFeatureFlagsState } from '@/client-config/states/labPublicFeatureFlagsState';
import { sentryConfigState } from '@/client-config/states/sentryConfigState';
import { supportChatState } from '@/client-config/states/supportChatState';
import { type ClientConfig } from '@/client-config/types/ClientConfig';
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
import { useCallback } from 'react';
import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
import { getClientConfig } from '@/client-config/utils/getClientConfig';
import { allowRequestsToTwentyIconsState } from '@/client-config/states/allowRequestsToTwentyIcons';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
type UseClientConfigResult = {
data: { clientConfig: ClientConfig } | undefined;
loading: boolean;
error: Error | undefined;
fetchClientConfig: () => Promise<void>;
refetch: () => Promise<void>;
};
export const useClientConfig = (): UseClientConfigResult => {
const setIsAnalyticsEnabled = useSetAtomState(isAnalyticsEnabledState);
const setDomainConfiguration = useSetAtomState(domainConfigurationState);
const setAuthProviders = useSetAtomState(authProvidersState);
const setAiModels = useSetAtomState(aiModelsState);
const setIsDeveloperDefaultSignInPrefilled = useSetAtomState(
isDeveloperDefaultSignInPrefilledState,
);
const setIsMultiWorkspaceEnabled = useSetAtomState(
isMultiWorkspaceEnabledState,
);
const setIsEmailVerificationRequired = useSetAtomState(
isEmailVerificationRequiredState,
);
const setBilling = useSetAtomState(billingState);
const setSupportChat = useSetAtomState(supportChatState);
const setSentryConfig = useSetAtomState(sentryConfigState);
const [clientConfigApiStatus, setClientConfigApiStatus] = useAtomState(
clientConfigApiStatusState,
);
const setCaptcha = useSetAtomState(captchaState);
const setApiConfig = useSetAtomState(apiConfigState);
const setCanManageFeatureFlags = useSetAtomState(canManageFeatureFlagsState);
const setLabPublicFeatureFlags = useSetAtomState(labPublicFeatureFlagsState);
const setIsMicrosoftMessagingEnabled = useSetAtomState(
isMicrosoftMessagingEnabledState,
);
const setIsMicrosoftCalendarEnabled = useSetAtomState(
isMicrosoftCalendarEnabledState,
);
const setIsGoogleMessagingEnabled = useSetAtomState(
isGoogleMessagingEnabledState,
);
const setIsGoogleCalendarEnabled = useSetAtomState(
isGoogleCalendarEnabledState,
);
const setIsAttachmentPreviewEnabled = useSetAtomState(
isAttachmentPreviewEnabledState,
);
const setIsConfigVariablesInDbEnabled = useSetAtomState(
isConfigVariablesInDbEnabledState,
);
const setCalendarBookingPageId = useSetAtomState(calendarBookingPageIdState);
const setIsImapSmtpCaldavEnabled = useSetAtomState(
isImapSmtpCaldavEnabledState,
);
const setIsEmailingDomainsEnabled = useSetAtomState(
isEmailingDomainsEnabledState,
);
const setAllowRequestsToTwentyIcons = useSetAtomState(
allowRequestsToTwentyIconsState,
);
const setIsCloudflareIntegrationEnabled = useSetAtomState(
isCloudflareIntegrationEnabledState,
);
const setIsClickHouseConfigured = useSetAtomState(
isClickHouseConfiguredState,
);
const setAppVersion = useSetAtomState(appVersionState);
const fetchClientConfig = useCallback(async () => {
setClientConfigApiStatus((prev) => ({
...prev,
isLoading: true,
}));
try {
const clientConfig = await getClientConfig();
setClientConfigApiStatus((prev) => ({
...prev,
isLoading: false,
isLoadedOnce: true,
isErrored: false,
error: undefined,
data: { clientConfig },
}));
setClientConfigApiStatus((currentStatus) => ({
...currentStatus,
isErrored: false,
error: undefined,
}));
setAppVersion(clientConfig.appVersion);
setAuthProviders({
google: clientConfig.authProviders.google,
microsoft: clientConfig.authProviders.microsoft,
password: clientConfig.authProviders.password,
magicLink: false,
sso: clientConfig.authProviders.sso,
});
setAiModels(clientConfig.aiModels ?? []);
setIsAnalyticsEnabled(clientConfig.analyticsEnabled);
setIsDeveloperDefaultSignInPrefilled(clientConfig.signInPrefilled);
setIsMultiWorkspaceEnabled(clientConfig.isMultiWorkspaceEnabled);
setIsEmailVerificationRequired(clientConfig.isEmailVerificationRequired);
setBilling(clientConfig.billing);
setSupportChat(clientConfig.support);
setSentryConfig({
dsn: clientConfig?.sentry?.dsn,
release: clientConfig?.sentry?.release,
environment: clientConfig?.sentry?.environment,
});
setCaptcha({
provider: clientConfig?.captcha?.provider,
siteKey: clientConfig?.captcha?.siteKey,
});
setApiConfig(clientConfig?.api);
setDomainConfiguration({
defaultSubdomain: clientConfig?.defaultSubdomain,
frontDomain: clientConfig?.frontDomain,
});
setCanManageFeatureFlags(clientConfig?.canManageFeatureFlags);
setLabPublicFeatureFlags(clientConfig?.publicFeatureFlags);
setIsMicrosoftMessagingEnabled(clientConfig?.isMicrosoftMessagingEnabled);
setIsMicrosoftCalendarEnabled(clientConfig?.isMicrosoftCalendarEnabled);
setIsGoogleMessagingEnabled(clientConfig?.isGoogleMessagingEnabled);
setIsGoogleCalendarEnabled(clientConfig?.isGoogleCalendarEnabled);
setIsAttachmentPreviewEnabled(clientConfig?.isAttachmentPreviewEnabled);
setIsConfigVariablesInDbEnabled(
clientConfig?.isConfigVariablesInDbEnabled,
);
setClientConfigApiStatus((currentStatus) => ({
...currentStatus,
isSaved: true,
}));
setCalendarBookingPageId(clientConfig?.calendarBookingPageId ?? null);
setIsImapSmtpCaldavEnabled(clientConfig?.isImapSmtpCaldavEnabled);
setIsEmailingDomainsEnabled(clientConfig?.isEmailingDomainsEnabled);
setAllowRequestsToTwentyIcons(clientConfig?.allowRequestsToTwentyIcons);
setIsCloudflareIntegrationEnabled(
clientConfig?.isCloudflareIntegrationEnabled,
);
setIsClickHouseConfigured(clientConfig?.isClickHouseConfigured ?? false);
} catch (err) {
const error =
err instanceof Error ? err : new Error('Failed to fetch client config');
setClientConfigApiStatus((prev) => ({
...prev,
isLoading: false,
isLoadedOnce: true,
isErrored: true,
error,
}));
}
}, [
setAiModels,
setApiConfig,
setAppVersion,
setAuthProviders,
setBilling,
setCalendarBookingPageId,
setCanManageFeatureFlags,
setCaptcha,
setClientConfigApiStatus,
setDomainConfiguration,
setIsGoogleCalendarEnabled,
setIsGoogleMessagingEnabled,
setIsAnalyticsEnabled,
setIsAttachmentPreviewEnabled,
setIsConfigVariablesInDbEnabled,
setIsDeveloperDefaultSignInPrefilled,
setIsEmailVerificationRequired,
setIsImapSmtpCaldavEnabled,
setIsMultiWorkspaceEnabled,
setIsEmailingDomainsEnabled,
setIsClickHouseConfigured,
setIsCloudflareIntegrationEnabled,
setLabPublicFeatureFlags,
setIsMicrosoftCalendarEnabled,
setIsMicrosoftMessagingEnabled,
setSentryConfig,
setSupportChat,
setAllowRequestsToTwentyIcons,
]);
return {
data: clientConfigApiStatus.data,
loading: clientConfigApiStatus.isLoading || false,
error: clientConfigApiStatus.error,
fetchClientConfig,
refetch: fetchClientConfig,
};
};