Remove logic function layer (#17697)

## Remove logic function layer

Package.json and yarn.lock are now on the application entity, so the
logic function layer is no longer used except as a legacy source for the
1.17 backfill. This PR removes all layer usage outside of that migration
and keeps only the entity for backfill.

### Summary

- **Kept:** `LogicFunctionLayerEntity` and its table, only used by the
1.17 backfill command to read legacy layer data and backfill application
package files.
- **Removed:** All other layer logic: CRUD, cache, resolvers, services,
DTOs, and frontend types. Logic functions now depend only on the
application for package/dependency context.


### Why Dependencies instead of Source for package files

Package.json and yarn.lock are the application’s dependency set and are
stored under the application in **FileFolder.Dependencies**. The build
service and drivers now read them only from Dependencies; nothing is
written to Source for these files.
This commit is contained in:
Charles Bochet
2026-02-04 10:17:27 +01:00
committed by GitHub
parent e10e0b337e
commit 402d149ee1
36 changed files with 171 additions and 643 deletions
@@ -11,7 +11,6 @@ import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { LogicFunctionLayerModule } from 'src/engine/metadata-modules/logic-function-layer/logic-function-layer.module';
import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
import { ObjectPermissionModule } from 'src/engine/metadata-modules/object-permission/object-permission.module';
@@ -33,7 +32,6 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
ObjectMetadataModule,
FieldMetadataModule,
DataSourceModule,
LogicFunctionLayerModule,
LogicFunctionModule,
WorkspaceMigrationModule,
PermissionsModule,
@@ -26,7 +26,6 @@ import { type FlatApplication } from 'src/engine/core-modules/application/types/
import { getDefaultApplicationPackageFields } from 'src/engine/core-modules/application/utils/get-default-application-package-fields.util';
import { ApplicationVariableEntityService } from 'src/engine/core-modules/applicationVariable/application-variable.service';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { LogicFunctionLayerService } from 'src/engine/core-modules/logic-function/logic-function-layer/services/logic-function-layer.service';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service';
@@ -54,7 +53,6 @@ export class ApplicationSyncService {
constructor(
private readonly applicationService: ApplicationService,
private readonly applicationVariableService: ApplicationVariableEntityService,
private readonly logicFunctionLayerService: LogicFunctionLayerService,
private readonly objectMetadataService: ObjectMetadataService,
private readonly fieldMetadataService: FieldMetadataService,
private readonly logicFunctionService: LogicFunctionService,
@@ -102,19 +100,11 @@ export class ApplicationSyncService {
}
if (manifest.logicFunctions.length > 0) {
if (!isDefined(application.logicFunctionLayerId)) {
throw new ApplicationException(
`Failed to sync logic function, could not find a logic function layer.`,
ApplicationExceptionCode.FIELD_NOT_FOUND,
);
}
await this.syncLogicFunctions({
logicFunctionsToSync: manifest.logicFunctions,
code: manifest.sources,
workspaceId,
ownerFlatApplication,
logicFunctionLayerId: application.logicFunctionLayerId,
});
}
@@ -162,7 +152,6 @@ export class ApplicationSyncService {
description: manifest.application.description,
version: packageJson.version,
sourcePath: 'cli-sync', // Placeholder for CLI-synced apps
logicFunctionLayerId: null,
defaultRoleId: null,
workspaceId,
packageJsonChecksum: defaultPackageFields.packageJsonChecksum,
@@ -179,35 +168,27 @@ export class ApplicationSyncService {
application = created;
}
let logicFunctionLayerId = application.logicFunctionLayerId;
if (
manifest.logicFunctions.length > 0 &&
isDefined(manifest.application.packageJsonChecksum) &&
isDefined(manifest.application.yarnLockChecksum)
) {
if (!isDefined(logicFunctionLayerId)) {
logicFunctionLayerId = (
await this.logicFunctionLayerService.create(
{
packageJsonChecksum: manifest.application.packageJsonChecksum,
yarnLockChecksum: manifest.application.yarnLockChecksum,
applicationUniversalIdentifier:
manifest.application.universalIdentifier,
},
const yarnLockContent = (
await streamToBuffer(
await this.fileStorageService.readFile_v2({
applicationUniversalIdentifier:
manifest.application.universalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: 'yarn.lock',
workspaceId,
)
).id;
}
}),
)
).toString('utf-8');
await this.logicFunctionLayerService.update(
logicFunctionLayerId,
{
packageJsonChecksum: manifest.application.packageJsonChecksum,
yarnLockChecksum: manifest.application.yarnLockChecksum,
},
manifest.application.universalIdentifier,
workspaceId,
await this.applicationService.uploadPackageFilesFromContent(
application,
JSON.stringify(packageJson, null, 2),
yarnLockContent,
);
}
@@ -223,7 +204,6 @@ export class ApplicationSyncService {
name,
description: manifest.application.description,
version: packageJson.version,
logicFunctionLayerId,
defaultRoleId: null,
});
}
@@ -848,13 +828,11 @@ export class ApplicationSyncService {
code,
workspaceId,
ownerFlatApplication,
logicFunctionLayerId,
}: {
logicFunctionsToSync: LogicFunctionManifest[];
workspaceId: string;
code: Sources;
ownerFlatApplication: FlatApplication;
logicFunctionLayerId: string;
}) {
const { flatLogicFunctionMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
@@ -964,7 +942,6 @@ export class ApplicationSyncService {
sourceHandlerPath: logicFunctionToCreate.sourceHandlerPath,
handlerName: logicFunctionToCreate.handlerName,
builtHandlerPath: logicFunctionToCreate.builtHandlerPath,
logicFunctionLayerId,
toolInputSchema: logicFunctionToCreate.toolInputSchema,
isTool: logicFunctionToCreate.isTool,
},
@@ -405,6 +405,51 @@ export class ApplicationService {
}
}
async uploadPackageFilesFromContent(
application: Pick<
ApplicationEntity,
'id' | 'universalIdentifier' | 'workspaceId'
>,
packageJsonContent: string,
yarnLockContent: string,
): Promise<void> {
const packageJsonChecksum = logicFunctionCreateHash(packageJsonContent);
const yarnLockChecksum = logicFunctionCreateHash(yarnLockContent);
const availablePackages = parseAvailablePackagesFromPackageJsonAndYarnLock(
packageJsonContent,
yarnLockContent,
);
const packageJsonFile = await this.fileStorageService.writeFile_v2({
sourceFile: packageJsonContent,
mimeType: undefined,
fileFolder: FileFolder.Dependencies,
applicationUniversalIdentifier: application.universalIdentifier,
workspaceId: application.workspaceId,
resourcePath: 'package.json',
settings: { isTemporaryFile: false, toDelete: false },
});
const yarnLockFile = await this.fileStorageService.writeFile_v2({
sourceFile: yarnLockContent,
mimeType: undefined,
fileFolder: FileFolder.Dependencies,
applicationUniversalIdentifier: application.universalIdentifier,
workspaceId: application.workspaceId,
resourcePath: 'yarn.lock',
settings: { isTemporaryFile: false, toDelete: false },
});
await this.update(application.id, {
packageJsonFileId: packageJsonFile.id,
yarnLockFileId: yarnLockFile.id,
packageJsonChecksum,
yarnLockChecksum,
availablePackages,
workspaceId: application.workspaceId,
});
}
async create(
data: Partial<ApplicationEntity> & { workspaceId: string },
queryRunner?: QueryRunner,
@@ -1,20 +1,20 @@
import { Injectable } from '@nestjs/common';
import crypto from 'crypto';
import fs from 'fs/promises';
import { dirname, join } from 'path';
import crypto from 'crypto';
import { build } from 'esbuild';
import { FileFolder } from 'twenty-shared/types';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { LambdaBuildDirectoryManager } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/lambda-build-directory-manager';
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
import {
getLogicFunctionBaseFolderPath,
getRelativePathFromBase,
} from 'src/engine/core-modules/logic-function/logic-function-build/utils/get-logic-function-base-folder-path.util';
import { FlatLogicFunctionLayer } from 'src/engine/metadata-modules/logic-function-layer/types/flat-logic-function-layer.type';
import { LambdaBuildDirectoryManager } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/lambda-build-directory-manager';
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
export type FunctionBuildParams = {
flatLogicFunction: FlatLogicFunction;
@@ -26,23 +26,22 @@ export class LogicFunctionBuildService {
constructor(private readonly fileStorageService: FileStorageService) {}
async hasLayerDependencies({
flatLogicFunctionLayer,
flatApplication,
applicationUniversalIdentifier,
}: {
flatLogicFunctionLayer: FlatLogicFunctionLayer;
flatApplication: FlatApplication;
applicationUniversalIdentifier: string;
}): Promise<boolean> {
const packageJsonExists = await this.fileStorageService.checkFileExists_v2({
workspaceId: flatLogicFunctionLayer.workspaceId,
workspaceId: flatApplication.workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
fileFolder: FileFolder.Dependencies,
resourcePath: 'package.json',
});
const yarnLockExists = await this.fileStorageService.checkFileExists_v2({
workspaceId: flatLogicFunctionLayer.workspaceId,
workspaceId: flatApplication.workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
fileFolder: FileFolder.Dependencies,
resourcePath: 'yarn.lock',
});
@@ -50,37 +49,14 @@ export class LogicFunctionBuildService {
}
async uploadDependencies({
flatLogicFunctionLayer,
applicationUniversalIdentifier,
flatApplication: _flatApplication,
applicationUniversalIdentifier: _applicationUniversalIdentifier,
}: {
flatLogicFunctionLayer: FlatLogicFunctionLayer;
flatApplication: FlatApplication;
applicationUniversalIdentifier: string;
}) {
await this.fileStorageService.writeFile_v2({
workspaceId: flatLogicFunctionLayer.workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: 'package.json',
sourceFile: JSON.stringify(flatLogicFunctionLayer.packageJson, null, 2),
mimeType: undefined,
settings: {
isTemporaryFile: false,
toDelete: false,
},
});
await this.fileStorageService.writeFile_v2({
workspaceId: flatLogicFunctionLayer.workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: 'yarn.lock',
sourceFile: flatLogicFunctionLayer.yarnLock,
mimeType: undefined,
settings: {
isTemporaryFile: false,
toDelete: false,
},
});
// Package files live in Dependencies; no copy needed drivers read from
// Dependencies when building the layer.
}
async isBuilt({
@@ -28,15 +28,15 @@ import {
type LogicFunctionExecutorDriver,
} from 'src/engine/core-modules/logic-function/logic-function-drivers/interfaces/logic-function-executor-driver.interface';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/copy-yarn-engine-and-build-dependencies';
import { copyExecutor } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/copy-executor';
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/copy-yarn-engine-and-build-dependencies';
import { createZipFile } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/create-zip-file';
import {
LambdaBuildDirectoryManager,
NODE_LAYER_SUBFOLDER,
} from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/lambda-build-directory-manager';
import { type FlatLogicFunctionLayer } from 'src/engine/metadata-modules/logic-function-layer/types/flat-logic-function-layer.type';
import { LogicFunctionExecutionStatus } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto';
import { LogicFunctionRuntime } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
import {
@@ -138,8 +138,8 @@ export class LambdaDriver implements LogicFunctionExecutorDriver {
);
}
private getLayerName(flatLogicFunctionLayer: FlatLogicFunctionLayer) {
return flatLogicFunctionLayer.yarnLockChecksum;
private getLayerName(flatApplication: FlatApplication) {
return flatApplication.yarnLockChecksum ?? 'default';
}
private async copyDependenciesInMemory({
@@ -155,14 +155,14 @@ export class LambdaDriver implements LogicFunctionExecutorDriver {
this.fileStorageService.downloadFile_v2({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
fileFolder: FileFolder.Dependencies,
resourcePath: 'package.json',
localPath: join(inMemoryLayerFolderPath, 'package.json'),
}),
this.fileStorageService.downloadFile_v2({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
fileFolder: FileFolder.Dependencies,
resourcePath: 'yarn.lock',
localPath: join(inMemoryLayerFolderPath, 'yarn.lock'),
}),
@@ -170,13 +170,13 @@ export class LambdaDriver implements LogicFunctionExecutorDriver {
}
private async createLayerIfNotExists({
flatLogicFunctionLayer,
flatApplication,
applicationUniversalIdentifier,
}: {
flatLogicFunctionLayer: FlatLogicFunctionLayer;
flatApplication: FlatApplication;
applicationUniversalIdentifier: string;
}): Promise<string> {
const layerName = this.getLayerName(flatLogicFunctionLayer);
const layerName = this.getLayerName(flatApplication);
const listLayerParams: ListLayerVersionsCommandInput = {
LayerName: layerName,
@@ -204,7 +204,7 @@ export class LambdaDriver implements LogicFunctionExecutorDriver {
await this.copyDependenciesInMemory({
applicationUniversalIdentifier,
workspaceId: flatLogicFunctionLayer.workspaceId,
workspaceId: flatApplication.workspaceId,
inMemoryLayerFolderPath: nodeDependenciesFolder,
});
await copyYarnEngineAndBuildDependencies(nodeDependenciesFolder);
@@ -263,7 +263,7 @@ export class LambdaDriver implements LogicFunctionExecutorDriver {
private async isAlreadyBuilt(
flatLogicFunction: FlatLogicFunction,
flatLogicFunctionLayer: FlatLogicFunctionLayer,
flatApplication: FlatApplication,
) {
const lambdaExecutor = await this.getLambdaExecutor(flatLogicFunction);
@@ -279,7 +279,7 @@ export class LambdaDriver implements LogicFunctionExecutorDriver {
return false;
}
const layerName = this.getLayerName(flatLogicFunctionLayer);
const layerName = this.getLayerName(flatApplication);
if (layers[0].Arn?.includes(layerName)) {
return true;
@@ -292,19 +292,19 @@ export class LambdaDriver implements LogicFunctionExecutorDriver {
private async build({
flatLogicFunction,
flatLogicFunctionLayer,
flatApplication,
applicationUniversalIdentifier,
}: {
flatLogicFunction: FlatLogicFunction;
flatLogicFunctionLayer: FlatLogicFunctionLayer;
flatApplication: FlatApplication;
applicationUniversalIdentifier: string;
}) {
if (await this.isAlreadyBuilt(flatLogicFunction, flatLogicFunctionLayer)) {
if (await this.isAlreadyBuilt(flatLogicFunction, flatApplication)) {
return;
}
const layerArn = await this.createLayerIfNotExists({
flatLogicFunctionLayer,
flatApplication,
applicationUniversalIdentifier,
});
@@ -353,14 +353,14 @@ export class LambdaDriver implements LogicFunctionExecutorDriver {
async execute({
flatLogicFunction,
flatLogicFunctionLayer,
flatApplication,
applicationUniversalIdentifier,
payload,
env,
}: LogicFunctionExecuteParams): Promise<LogicFunctionExecuteResult> {
await this.build({
flatLogicFunction,
flatLogicFunctionLayer,
flatApplication,
applicationUniversalIdentifier,
});
@@ -10,17 +10,17 @@ import {
type LogicFunctionExecutorDriver,
} from 'src/engine/core-modules/logic-function/logic-function-drivers/interfaces/logic-function-executor-driver.interface';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { LOGIC_FUNCTION_EXECUTOR_TMPDIR_FOLDER } from 'src/engine/core-modules/logic-function/logic-function-drivers/constants/logic-function-executor-tmpdir-folder';
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/copy-yarn-engine-and-build-dependencies';
import { ConsoleListener } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/intercept-console';
import { LambdaBuildDirectoryManager } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/lambda-build-directory-manager';
import { type FlatLogicFunctionLayer } from 'src/engine/metadata-modules/logic-function-layer/types/flat-logic-function-layer.type';
import { LogicFunctionExecutionStatus } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto';
import {
getLogicFunctionBaseFolderPath,
getRelativePathFromBase,
} from 'src/engine/core-modules/logic-function/logic-function-build/utils/get-logic-function-base-folder-path.util';
import { LOGIC_FUNCTION_EXECUTOR_TMPDIR_FOLDER } from 'src/engine/core-modules/logic-function/logic-function-drivers/constants/logic-function-executor-tmpdir-folder';
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/copy-yarn-engine-and-build-dependencies';
import { ConsoleListener } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/intercept-console';
import { LambdaBuildDirectoryManager } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/lambda-build-directory-manager';
import { LogicFunctionExecutionStatus } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto';
export interface LocalDriverOptions {
fileStorageService: FileStorageService;
@@ -33,13 +33,10 @@ export class LocalDriver implements LogicFunctionExecutorDriver {
this.fileStorageService = options.fileStorageService;
}
private getInMemoryLayerFolderPath = (
flatLogicFunctionLayer: FlatLogicFunctionLayer,
) => {
return join(
LOGIC_FUNCTION_EXECUTOR_TMPDIR_FOLDER,
flatLogicFunctionLayer.yarnLockChecksum,
);
private getInMemoryLayerFolderPath = (flatApplication: FlatApplication) => {
const checksum = flatApplication.yarnLockChecksum ?? 'default';
return join(LOGIC_FUNCTION_EXECUTOR_TMPDIR_FOLDER, checksum);
};
private async copyDependenciesInMemory({
@@ -55,14 +52,14 @@ export class LocalDriver implements LogicFunctionExecutorDriver {
this.fileStorageService.downloadFile_v2({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
fileFolder: FileFolder.Dependencies,
resourcePath: 'package.json',
localPath: join(inMemoryLayerFolderPath, 'package.json'),
}),
this.fileStorageService.downloadFile_v2({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
fileFolder: FileFolder.Dependencies,
resourcePath: 'yarn.lock',
localPath: join(inMemoryLayerFolderPath, 'yarn.lock'),
}),
@@ -70,22 +67,21 @@ export class LocalDriver implements LogicFunctionExecutorDriver {
}
private async createLayerIfNotExists({
flatLogicFunctionLayer,
flatApplication,
applicationUniversalIdentifier,
}: {
flatLogicFunctionLayer: FlatLogicFunctionLayer;
flatApplication: FlatApplication;
applicationUniversalIdentifier: string;
}) {
const inMemoryLayerFolderPath = this.getInMemoryLayerFolderPath(
flatLogicFunctionLayer,
);
const inMemoryLayerFolderPath =
this.getInMemoryLayerFolderPath(flatApplication);
try {
await fs.access(inMemoryLayerFolderPath);
} catch {
await this.copyDependenciesInMemory({
applicationUniversalIdentifier,
workspaceId: flatLogicFunctionLayer.workspaceId,
workspaceId: flatApplication.workspaceId,
inMemoryLayerFolderPath,
});
await copyYarnEngineAndBuildDependencies(inMemoryLayerFolderPath);
@@ -95,27 +91,27 @@ export class LocalDriver implements LogicFunctionExecutorDriver {
async delete() {}
private async build({
flatLogicFunctionLayer,
flatApplication,
applicationUniversalIdentifier,
}: {
flatLogicFunctionLayer: FlatLogicFunctionLayer;
flatApplication: FlatApplication;
applicationUniversalIdentifier: string;
}) {
await this.createLayerIfNotExists({
flatLogicFunctionLayer,
flatApplication,
applicationUniversalIdentifier,
});
}
async execute({
flatLogicFunction,
flatLogicFunctionLayer,
flatApplication,
applicationUniversalIdentifier,
payload,
env,
}: LogicFunctionExecuteParams): Promise<LogicFunctionExecuteResult> {
await this.build({
flatLogicFunctionLayer,
flatApplication,
applicationUniversalIdentifier,
});
@@ -141,7 +137,7 @@ export class LocalDriver implements LogicFunctionExecutorDriver {
try {
await fs.symlink(
join(
this.getInMemoryLayerFolderPath(flatLogicFunctionLayer),
this.getInMemoryLayerFolderPath(flatApplication),
'node_modules',
),
join(sourceTemporaryDir, 'node_modules'),
@@ -1,4 +1,4 @@
import { type FlatLogicFunctionLayer } from 'src/engine/metadata-modules/logic-function-layer/types/flat-logic-function-layer.type';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type LogicFunctionExecutionStatus } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto';
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
@@ -18,7 +18,7 @@ export type LogicFunctionExecuteResult = {
export type LogicFunctionExecuteParams = {
flatLogicFunction: FlatLogicFunction;
flatLogicFunctionLayer: FlatLogicFunctionLayer;
flatApplication: FlatApplication;
applicationUniversalIdentifier: string;
payload: object;
env?: Record<string, string>;
@@ -12,7 +12,6 @@ import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.mod
import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
import { LogicFunctionLayerModule } from 'src/engine/metadata-modules/logic-function-layer/logic-function-layer.module';
@Module({
imports: [
@@ -23,7 +22,6 @@ import { LogicFunctionLayerModule } from 'src/engine/metadata-modules/logic-func
SubscriptionsModule,
WorkspaceCacheModule,
LogicFunctionBuildModule,
LogicFunctionLayerModule,
FileModule,
TypeOrmModule.forFeature([LogicFunctionEntity]),
],
@@ -6,7 +6,7 @@ import {
DEFAULT_API_URL_NAME,
} from 'twenty-shared/application';
import { FileFolder } from 'twenty-shared/types';
import { isDefined, isEmptyObject } from 'twenty-shared/utils';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import {
@@ -35,7 +35,6 @@ import { SubscriptionChannel } from 'src/engine/subscriptions/enums/subscription
import { SubscriptionService } from 'src/engine/subscriptions/subscription.service';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { cleanServerUrl } from 'src/utils/clean-server-url';
import { LogicFunctionLayerService } from 'src/engine/core-modules/logic-function/logic-function-layer/services/logic-function-layer.service';
const MIN_TOKEN_EXPIRATION_IN_SECONDS = 5;
@@ -69,7 +68,6 @@ export class LogicFunctionExecutorService
private readonly functionBuildService: LogicFunctionBuildService,
private readonly subscriptionService: SubscriptionService,
private readonly auditService: AuditService,
private readonly logicFunctionLayerService: LogicFunctionLayerService,
private readonly fileStorageService: FileStorageService,
@InjectRepository(LogicFunctionEntity)
private readonly logicFunctionRepository: Repository<LogicFunctionEntity>,
@@ -100,12 +98,10 @@ export class LogicFunctionExecutorService
flatLogicFunctionMaps,
flatApplicationMaps,
applicationVariableMaps,
logicFunctionLayerMaps,
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatLogicFunctionMaps',
'flatApplicationMaps',
'applicationVariableMaps',
'logicFunctionLayerMaps',
]);
const flatLogicFunction = findFlatEntityByIdInFlatEntityMaps({
@@ -123,12 +119,13 @@ export class LogicFunctionExecutorService
);
}
const flatLogicFunctionLayer =
logicFunctionLayerMaps.byId[flatLogicFunction.logicFunctionLayerId];
const flatApplication = isDefined(flatLogicFunction.applicationId)
? flatApplicationMaps.byId[flatLogicFunction.applicationId]
: undefined;
if (!isDefined(flatLogicFunctionLayer)) {
if (!isDefined(flatApplication)) {
throw new LogicFunctionExecutionException(
`Logic function layer with id ${flatLogicFunction.logicFunctionLayerId} not found`,
`Application not found for logic function ${id}`,
LogicFunctionExecutionExceptionCode.LOGIC_FUNCTION_NOT_FOUND,
);
}
@@ -180,15 +177,14 @@ export class LogicFunctionExecutorService
);
}
// TODO: remove when all logic functions are migrated
if (
!(await this.functionBuildService.hasLayerDependencies({
flatLogicFunctionLayer,
flatApplication,
applicationUniversalIdentifier,
}))
) {
await this.functionBuildService.uploadDependencies({
flatLogicFunctionLayer,
flatApplication,
applicationUniversalIdentifier,
});
}
@@ -210,7 +206,7 @@ export class LogicFunctionExecutorService
callback: () =>
this.execute({
flatLogicFunction,
flatLogicFunctionLayer,
flatApplication,
applicationUniversalIdentifier,
payload,
env: envVariables,
@@ -307,26 +303,10 @@ export class LogicFunctionExecutorService
async getAvailablePackages(logicFunctionId: string) {
const logicFunction = await this.logicFunctionRepository.findOneOrFail({
where: { id: logicFunctionId },
relations: ['logicFunctionLayer', 'application'],
relations: ['application'],
});
if (isEmptyObject(logicFunction.logicFunctionLayer.availablePackages)) {
await this.logicFunctionLayerService.update(
logicFunction.logicFunctionLayer.id,
{},
logicFunction.application.universalIdentifier,
logicFunction.workspaceId,
);
return (
await this.logicFunctionRepository.findOneOrFail({
where: { id: logicFunctionId },
relations: ['logicFunctionLayer'],
})
).logicFunctionLayer.availablePackages;
}
return logicFunction.logicFunctionLayer.availablePackages;
return logicFunction.application.availablePackages ?? {};
}
private async throttleExecution(workspaceId: string) {
@@ -1,16 +0,0 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { LogicFunctionLayerService } from 'src/engine/core-modules/logic-function/logic-function-layer/services/logic-function-layer.service';
import { LogicFunctionLayerEntity } from 'src/engine/metadata-modules/logic-function-layer/logic-function-layer.entity';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
@Module({
imports: [
TypeOrmModule.forFeature([LogicFunctionLayerEntity]),
WorkspaceCacheModule,
],
providers: [LogicFunctionLayerService],
exports: [LogicFunctionLayerService],
})
export class CoreLogicFunctionLayerModule {}
@@ -1,186 +0,0 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { isDefined } from 'twenty-shared/utils';
import { FileFolder } from 'twenty-shared/types';
import { PackageJson } from 'type-fest';
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
import { LogicFunctionLayerEntity } from 'src/engine/metadata-modules/logic-function-layer/logic-function-layer.entity';
import { CreateLogicFunctionLayerInput } from 'src/engine/metadata-modules/logic-function-layer/dtos/create-logic-function-layer.input';
import { getLastCommonLayerDependencies } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/get-last-common-layer-dependencies';
import { logicFunctionCreateHash } from 'src/engine/metadata-modules/logic-function/utils/logic-function-create-hash.utils';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { streamToBuffer } from 'src/utils/stream-to-buffer';
@Injectable()
export class LogicFunctionLayerService {
constructor(
@InjectRepository(LogicFunctionLayerEntity)
private readonly logicFunctionLayerRepository: Repository<LogicFunctionLayerEntity>,
private readonly workspaceCacheService: WorkspaceCacheService,
private readonly fileStorageService: FileStorageService,
) {}
async create(
{
packageJsonChecksum,
yarnLockChecksum,
applicationUniversalIdentifier,
}: CreateLogicFunctionLayerInput,
workspaceId: string,
) {
const logicFunctionLayer = this.logicFunctionLayerRepository.create({
packageJson: {}, // TODO: Delete when migration to Source files storage is done
yarnLock: '', // TODO: Delete when migration to Source files storage is done
packageJsonChecksum,
yarnLockChecksum,
workspaceId,
} as Omit<LogicFunctionLayerEntity, 'workspace'>);
const availablePackages = await this.getAvailablePackages({
workspaceId,
applicationUniversalIdentifier,
});
const savedLayer = await this.logicFunctionLayerRepository.save({
...logicFunctionLayer,
availablePackages,
});
await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
'logicFunctionLayerMaps',
]);
return savedLayer;
}
async update(
id: string,
data: QueryDeepPartialEntity<Omit<LogicFunctionLayerEntity, 'workspace'>>,
applicationUniversalIdentifier: string,
workspaceId: string,
) {
const availablePackages = await this.getAvailablePackages({
workspaceId,
applicationUniversalIdentifier,
});
const result = await this.logicFunctionLayerRepository.update(id, {
...data,
availablePackages,
});
await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
'logicFunctionLayerMaps',
]);
return result;
}
async createCommonLayer({
workspaceId,
applicationUniversalIdentifier,
}: {
workspaceId: string;
applicationUniversalIdentifier: string;
}) {
const { packageJson, yarnLock } = await getLastCommonLayerDependencies();
await this.fileStorageService.writeFile_v2({
sourceFile: packageJson,
mimeType: undefined,
fileFolder: FileFolder.Source,
applicationUniversalIdentifier,
workspaceId,
resourcePath: 'package.json',
settings: { isTemporaryFile: false, toDelete: false },
});
await this.fileStorageService.writeFile_v2({
sourceFile: yarnLock,
mimeType: undefined,
fileFolder: FileFolder.Source,
applicationUniversalIdentifier,
workspaceId,
resourcePath: 'yarn.lock',
settings: { isTemporaryFile: false, toDelete: false },
});
const packageJsonChecksum = logicFunctionCreateHash(
JSON.stringify(packageJson),
);
const yarnLockChecksum = logicFunctionCreateHash(yarnLock);
const commonLayer = await this.logicFunctionLayerRepository.findOne({
where: {
yarnLockChecksum,
packageJsonChecksum,
workspaceId,
},
});
if (isDefined(commonLayer)) {
return commonLayer;
}
return this.create(
{ packageJsonChecksum, yarnLockChecksum, applicationUniversalIdentifier },
workspaceId,
);
}
private async getAvailablePackages({
workspaceId,
applicationUniversalIdentifier,
}: {
workspaceId: string;
applicationUniversalIdentifier: string;
}) {
const packageJson = JSON.parse(
(
await streamToBuffer(
await this.fileStorageService.readFile_v2({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: 'package.json',
}),
)
).toString('utf-8'),
) as PackageJson;
const yarnLock = (
await streamToBuffer(
await this.fileStorageService.readFile_v2({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: 'yarn.lock',
}),
)
).toString('utf-8');
const packageVersionRegex =
/^"(@?[^@]+(?:\/[^@]+)?)@.*?":\n\s+version:\s*(.+)$/gm;
const versions: Record<string, string> = {};
let match: RegExpExecArray | null;
while ((match = packageVersionRegex.exec(yarnLock)) !== null) {
const packageName = match[1];
const version = match[2];
if (packageJson.dependencies?.[packageName]) {
versions[packageName] = version;
}
}
return versions;
}
}
@@ -5,7 +5,6 @@ import { type LogicFunctionExecutorModuleAsyncOptions } from 'src/engine/core-mo
import { LogicFunctionBuildModule } from 'src/engine/core-modules/logic-function/logic-function-build/logic-function-build.module';
import { LogicFunctionDriversModule } from 'src/engine/core-modules/logic-function/logic-function-drivers/logic-function-drivers.module';
import { LogicFunctionExecutorModule } from 'src/engine/core-modules/logic-function/logic-function-executor/logic-function-executor.module';
import { CoreLogicFunctionLayerModule } from 'src/engine/core-modules/logic-function/logic-function-layer/logic-function-layer.module';
import { LogicFunctionTriggerModule } from 'src/engine/core-modules/logic-function/logic-function-trigger/logic-function-trigger.module';
@Global()
@@ -20,14 +19,12 @@ export class LogicFunctionModule {
LogicFunctionDriversModule.forRootAsync(options),
LogicFunctionExecutorModule,
LogicFunctionBuildModule,
CoreLogicFunctionLayerModule,
LogicFunctionTriggerModule,
],
exports: [
LogicFunctionDriversModule,
LogicFunctionExecutorModule,
LogicFunctionBuildModule,
CoreLogicFunctionLayerModule,
LogicFunctionTriggerModule,
],
};