+9
-2
@@ -38,8 +38,9 @@ import {
|
||||
LogicFunctionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/logic-function/logic-function.exception';
|
||||
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
|
||||
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/application-layer/utils/copy-yarn-engine-and-build-dependencies';
|
||||
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/application/utils/copy-yarn-engine-and-build-dependencies';
|
||||
import { type LogicFunctionResourceService } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service';
|
||||
import { callWithTimeout } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/call-with-timeout';
|
||||
|
||||
const UPDATE_FUNCTION_DURATION_TIMEOUT_IN_SECONDS = 60;
|
||||
const CREDENTIALS_DURATION_IN_SECONDS = 60 * 60; // 1h
|
||||
@@ -322,6 +323,7 @@ export class LambdaDriver implements LogicFunctionDriver {
|
||||
applicationUniversalIdentifier,
|
||||
payload,
|
||||
env,
|
||||
timeoutMs = 900_000,
|
||||
}: LogicFunctionExecuteParams): Promise<LogicFunctionExecuteResult> {
|
||||
await this.build({
|
||||
flatLogicFunction,
|
||||
@@ -355,7 +357,12 @@ export class LambdaDriver implements LogicFunctionDriver {
|
||||
const command = new InvokeCommand(params);
|
||||
|
||||
try {
|
||||
const result = await (await this.getLambdaClient()).send(command);
|
||||
const lambdaClient = await this.getLambdaClient();
|
||||
|
||||
const result = await callWithTimeout({
|
||||
callback: () => lambdaClient.send(command),
|
||||
timeoutMs,
|
||||
});
|
||||
|
||||
const parsedResult = result.Payload
|
||||
? JSON.parse(result.Payload.transformToString())
|
||||
|
||||
+10
-13
@@ -13,7 +13,7 @@ import { LOGIC_FUNCTION_EXECUTOR_TMPDIR_FOLDER } from 'src/engine/core-modules/l
|
||||
import { ConsoleListener } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/intercept-console';
|
||||
import { TemporaryDirManager } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/temporary-dir-manager';
|
||||
import { LogicFunctionExecutionStatus } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto';
|
||||
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/application-layer/utils/copy-yarn-engine-and-build-dependencies';
|
||||
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/application/utils/copy-yarn-engine-and-build-dependencies';
|
||||
import type { LogicFunctionResourceService } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service';
|
||||
|
||||
export interface LocalDriverOptions {
|
||||
@@ -76,6 +76,7 @@ export class LocalDriver implements LogicFunctionDriver {
|
||||
applicationUniversalIdentifier,
|
||||
payload,
|
||||
env,
|
||||
timeoutMs = 900_000,
|
||||
}: LogicFunctionExecuteParams): Promise<LogicFunctionExecuteResult> {
|
||||
await this.build({
|
||||
flatApplication,
|
||||
@@ -89,17 +90,13 @@ export class LocalDriver implements LogicFunctionDriver {
|
||||
try {
|
||||
const { sourceTemporaryDir } = await temporaryDirManager.init();
|
||||
|
||||
const inMemoryBuiltHandlerPath = join(
|
||||
sourceTemporaryDir,
|
||||
flatLogicFunction.builtHandlerPath,
|
||||
);
|
||||
|
||||
await this.logicFunctionResourceService.copyBuiltCodeInMemory({
|
||||
workspaceId: flatLogicFunction.workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
builtHandlerPath: flatLogicFunction.builtHandlerPath,
|
||||
inMemoryDestinationPath: inMemoryBuiltHandlerPath,
|
||||
});
|
||||
const inMemoryBuiltHandlerPath =
|
||||
await this.logicFunctionResourceService.copyBuiltCodeInMemory({
|
||||
workspaceId: flatLogicFunction.workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
builtHandlerPath: flatLogicFunction.builtHandlerPath,
|
||||
inMemoryDestinationPath: sourceTemporaryDir,
|
||||
});
|
||||
|
||||
try {
|
||||
await fs.symlink(
|
||||
@@ -161,7 +158,7 @@ export class LocalDriver implements LogicFunctionDriver {
|
||||
runnerPath,
|
||||
env: env ?? {},
|
||||
payload,
|
||||
timeoutMs: 900_000, // timeout is handled by the logic function service
|
||||
timeoutMs,
|
||||
});
|
||||
|
||||
if (stdout)
|
||||
|
||||
+1
@@ -26,6 +26,7 @@ export type LogicFunctionExecuteParams = {
|
||||
applicationUniversalIdentifier: string;
|
||||
payload: object;
|
||||
env?: Record<string, string>;
|
||||
timeoutMs?: number;
|
||||
};
|
||||
|
||||
export interface LogicFunctionDriver {
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
export const callWithTimeout = async <T>({
|
||||
callback,
|
||||
timeoutMs,
|
||||
}: {
|
||||
callback: () => Promise<T>;
|
||||
timeoutMs: number;
|
||||
}): Promise<T> => {
|
||||
return Promise.race([
|
||||
callback(),
|
||||
new Promise<T>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Execution timed out')), timeoutMs),
|
||||
),
|
||||
]);
|
||||
};
|
||||
+7
-25
@@ -83,16 +83,13 @@ export class LogicFunctionExecutorService {
|
||||
flatLogicFunction,
|
||||
});
|
||||
|
||||
const resultLogicFunction = await this.callWithTimeout({
|
||||
callback: () =>
|
||||
this.driver.execute({
|
||||
flatLogicFunction,
|
||||
flatApplication,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
payload,
|
||||
env: envVariables,
|
||||
}),
|
||||
timeoutMs: flatLogicFunction.timeoutSeconds * 1000,
|
||||
const resultLogicFunction = await this.driver.execute({
|
||||
flatLogicFunction,
|
||||
flatApplication,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
payload,
|
||||
env: envVariables,
|
||||
timeoutMs: flatLogicFunction.timeoutSeconds * 1_000,
|
||||
});
|
||||
|
||||
await this.handleExecutionResult({
|
||||
@@ -121,21 +118,6 @@ export class LogicFunctionExecutorService {
|
||||
}
|
||||
}
|
||||
|
||||
private async callWithTimeout<T>({
|
||||
callback,
|
||||
timeoutMs,
|
||||
}: {
|
||||
callback: () => Promise<T>;
|
||||
timeoutMs: number;
|
||||
}): Promise<T> {
|
||||
return Promise.race([
|
||||
callback(),
|
||||
new Promise<T>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Execution timed out')), timeoutMs),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
private async getFlatEntitiesOrThrow({
|
||||
workspaceId,
|
||||
logicFunctionId,
|
||||
|
||||
+63
-125
@@ -4,9 +4,8 @@ import crypto from 'crypto';
|
||||
import fs from 'fs/promises';
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
import { isObject } from '@sniptt/guards';
|
||||
import { build } from 'esbuild';
|
||||
import { FileFolder, Sources } from 'twenty-shared/types';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { NODE_ESM_CJS_BANNER } from 'twenty-shared/application';
|
||||
|
||||
@@ -14,64 +13,56 @@ import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/i
|
||||
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { TemporaryDirManager } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/temporary-dir-manager';
|
||||
import {
|
||||
getLogicFunctionBaseFolderPath,
|
||||
getRelativePathFromBase,
|
||||
} from 'src/engine/core-modules/logic-function/logic-function-resource/utils/get-logic-function-handler-path.util';
|
||||
import {
|
||||
getLogicFunctionSeedProjectFiles,
|
||||
LogicFunctionSeedProjectFile,
|
||||
} from 'src/engine/core-modules/logic-function/logic-function-resource/utils/get-logic-function-seed-project-files.util';
|
||||
import {
|
||||
DEFAULT_BUILT_HANDLER_PATH,
|
||||
DEFAULT_SOURCE_HANDLER_PATH,
|
||||
} from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
import {
|
||||
LogicFunctionException,
|
||||
LogicFunctionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/logic-function/logic-function.exception';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
|
||||
type SeedSourceFilesParams = {
|
||||
type Identifier = {
|
||||
workspaceId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
sourceSubfolder: string;
|
||||
};
|
||||
|
||||
type SeedSourceFilesParams = Identifier & {
|
||||
sourceHandlerPath: string;
|
||||
builtHandlerPath: string;
|
||||
};
|
||||
|
||||
type SeedSourceFilesResult = {
|
||||
sourceHandlerPath: string;
|
||||
builtHandlerPath: string;
|
||||
handlerName: string;
|
||||
checksum: string;
|
||||
};
|
||||
|
||||
type UpdateSourceFilesParams = {
|
||||
sourceHandlerPath: string;
|
||||
workspaceId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
code: Sources;
|
||||
type UpdateSourceFilesParams = Omit<
|
||||
SeedSourceFilesParams,
|
||||
'builtHandlerPath'
|
||||
> & {
|
||||
sourceHandlerCode: string;
|
||||
};
|
||||
|
||||
type BuildFromSourceParams = {
|
||||
type BuildFromSourceParams = Identifier & {
|
||||
sourceHandlerPath: string;
|
||||
builtHandlerPath: string;
|
||||
workspaceId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
};
|
||||
|
||||
type GetSourceCodeParams = {
|
||||
type GetSourceCodeParams = Identifier & {
|
||||
sourceHandlerPath: string;
|
||||
workspaceId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
};
|
||||
|
||||
type CopySourceParams = {
|
||||
type GetBuiltCodeParams = Identifier & {
|
||||
builtHandlerPath: string;
|
||||
};
|
||||
|
||||
type CopySourceParams = Identifier & {
|
||||
fromSourceHandlerPath: string;
|
||||
fromBuiltHandlerPath: string;
|
||||
toSourceHandlerPath: string;
|
||||
fromBuiltHandlerPath: string;
|
||||
toBuiltHandlerPath: string;
|
||||
workspaceId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
@@ -81,11 +72,9 @@ export class LogicFunctionResourceService {
|
||||
async seedSourceFiles({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
sourceSubfolder,
|
||||
sourceHandlerPath,
|
||||
builtHandlerPath,
|
||||
}: SeedSourceFilesParams): Promise<SeedSourceFilesResult> {
|
||||
const sourceHandlerPath = `${sourceSubfolder}/${DEFAULT_SOURCE_HANDLER_PATH}`;
|
||||
const builtHandlerPath = `${sourceSubfolder}/${DEFAULT_BUILT_HANDLER_PATH}`;
|
||||
|
||||
const seedProjectFiles = await getLogicFunctionSeedProjectFiles();
|
||||
|
||||
const sourceFiles = seedProjectFiles.filter(
|
||||
@@ -138,40 +127,28 @@ export class LogicFunctionResourceService {
|
||||
|
||||
return {
|
||||
handlerName: 'main',
|
||||
sourceHandlerPath,
|
||||
builtHandlerPath,
|
||||
checksum,
|
||||
};
|
||||
}
|
||||
|
||||
async updateSourceFiles({
|
||||
async uploadSourceFile({
|
||||
sourceHandlerPath,
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
code,
|
||||
sourceHandlerCode,
|
||||
}: UpdateSourceFilesParams): Promise<void> {
|
||||
const temporaryDirManager = new TemporaryDirManager();
|
||||
|
||||
try {
|
||||
const { sourceTemporaryDir } = await temporaryDirManager.init();
|
||||
|
||||
await this.writeSourcesToLocalFolder(code, sourceTemporaryDir);
|
||||
|
||||
const baseFolderPath = getLogicFunctionBaseFolderPath(sourceHandlerPath);
|
||||
|
||||
await this.fileStorageService.uploadFolder({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: baseFolderPath,
|
||||
localPath: sourceTemporaryDir,
|
||||
});
|
||||
} finally {
|
||||
await temporaryDirManager.clean();
|
||||
}
|
||||
await this.fileStorageService.writeFile({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: sourceHandlerPath,
|
||||
sourceFile: sourceHandlerCode,
|
||||
settings: { isTemporaryFile: false, toDelete: false },
|
||||
mimeType: 'application/typescript',
|
||||
});
|
||||
}
|
||||
|
||||
async buildFromSource({
|
||||
async buildFromSourceFile({
|
||||
sourceHandlerPath,
|
||||
builtHandlerPath,
|
||||
workspaceId,
|
||||
@@ -182,29 +159,18 @@ export class LogicFunctionResourceService {
|
||||
try {
|
||||
const { sourceTemporaryDir } = await temporaryDirManager.init();
|
||||
|
||||
const baseFolderPath = getLogicFunctionBaseFolderPath(sourceHandlerPath);
|
||||
|
||||
await this.fileStorageService.downloadFolder({
|
||||
await this.fileStorageService.downloadFile({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: baseFolderPath,
|
||||
localPath: sourceTemporaryDir,
|
||||
resourcePath: sourceHandlerPath,
|
||||
localPath: join(sourceTemporaryDir, sourceHandlerPath),
|
||||
});
|
||||
|
||||
const relativeSourcePath = getRelativePathFromBase(
|
||||
sourceHandlerPath,
|
||||
baseFolderPath,
|
||||
);
|
||||
const relativeBuiltPath = getRelativePathFromBase(
|
||||
builtHandlerPath,
|
||||
baseFolderPath,
|
||||
);
|
||||
|
||||
const builtBundleFilePath = await this.buildInMemory({
|
||||
sourceTemporaryDir,
|
||||
sourceHandlerPath: relativeSourcePath,
|
||||
builtHandlerPath: relativeBuiltPath,
|
||||
sourceHandlerPath,
|
||||
builtHandlerPath,
|
||||
});
|
||||
|
||||
const builtFile = await fs.readFile(builtBundleFilePath, 'utf-8');
|
||||
@@ -230,20 +196,22 @@ export class LogicFunctionResourceService {
|
||||
}
|
||||
}
|
||||
|
||||
async getSourceCode({
|
||||
async getSourceFile({
|
||||
sourceHandlerPath,
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
}: GetSourceCodeParams): Promise<Sources | null> {
|
||||
const baseFolderPath = getLogicFunctionBaseFolderPath(sourceHandlerPath);
|
||||
|
||||
}: GetSourceCodeParams): Promise<string | null> {
|
||||
try {
|
||||
return await this.fileStorageService.readFolder({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: baseFolderPath,
|
||||
});
|
||||
return (
|
||||
await streamToBuffer(
|
||||
await this.fileStorageService.readFile({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: sourceHandlerPath,
|
||||
}),
|
||||
)
|
||||
).toString('utf-8');
|
||||
} catch (error) {
|
||||
if (
|
||||
isDefined(error) &&
|
||||
@@ -259,34 +227,24 @@ export class LogicFunctionResourceService {
|
||||
|
||||
async copyResources({
|
||||
fromSourceHandlerPath,
|
||||
fromBuiltHandlerPath,
|
||||
toSourceHandlerPath,
|
||||
fromBuiltHandlerPath,
|
||||
toBuiltHandlerPath,
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
}: CopySourceParams): Promise<void> {
|
||||
const fromSourceBaseFolderPath = getLogicFunctionBaseFolderPath(
|
||||
fromSourceHandlerPath,
|
||||
);
|
||||
const toSourceBaseFolderPath =
|
||||
getLogicFunctionBaseFolderPath(toSourceHandlerPath);
|
||||
const fromBuiltBaseFolderPath =
|
||||
getLogicFunctionBaseFolderPath(fromBuiltHandlerPath);
|
||||
const toBuiltBaseFolderPath =
|
||||
getLogicFunctionBaseFolderPath(toBuiltHandlerPath);
|
||||
|
||||
await this.fileStorageService.copy({
|
||||
from: {
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: fromSourceBaseFolderPath,
|
||||
resourcePath: fromSourceHandlerPath,
|
||||
},
|
||||
to: {
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: toSourceBaseFolderPath,
|
||||
resourcePath: toSourceHandlerPath,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -295,13 +253,13 @@ export class LogicFunctionResourceService {
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.BuiltLogicFunction,
|
||||
resourcePath: fromBuiltBaseFolderPath,
|
||||
resourcePath: fromBuiltHandlerPath,
|
||||
},
|
||||
to: {
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.BuiltLogicFunction,
|
||||
resourcePath: toBuiltBaseFolderPath,
|
||||
resourcePath: toBuiltHandlerPath,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -337,11 +295,7 @@ export class LogicFunctionResourceService {
|
||||
builtHandlerPath,
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
}: {
|
||||
builtHandlerPath: string;
|
||||
workspaceId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
}): Promise<string> {
|
||||
}: GetBuiltCodeParams): Promise<string> {
|
||||
return (
|
||||
await streamToBuffer(
|
||||
await this.fileStorageService.readFile({
|
||||
@@ -359,36 +313,20 @@ export class LogicFunctionResourceService {
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
inMemoryDestinationPath,
|
||||
}: {
|
||||
builtHandlerPath: string;
|
||||
workspaceId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
}: GetBuiltCodeParams & {
|
||||
inMemoryDestinationPath: string;
|
||||
}): Promise<void> {
|
||||
}): Promise<string> {
|
||||
const localPath = join(inMemoryDestinationPath, builtHandlerPath);
|
||||
|
||||
await this.fileStorageService.downloadFile({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.BuiltLogicFunction,
|
||||
resourcePath: builtHandlerPath,
|
||||
localPath: inMemoryDestinationPath,
|
||||
localPath,
|
||||
});
|
||||
}
|
||||
|
||||
async writeSourcesToLocalFolder(
|
||||
sources: Sources,
|
||||
localPath: string,
|
||||
): Promise<void> {
|
||||
for (const key of Object.keys(sources)) {
|
||||
const filePath = join(localPath, key);
|
||||
const value = sources[key];
|
||||
|
||||
if (isObject(value)) {
|
||||
await this.writeSourcesToLocalFolder(value as Sources, filePath);
|
||||
continue;
|
||||
}
|
||||
await fs.mkdir(dirname(filePath), { recursive: true });
|
||||
await fs.writeFile(filePath, value);
|
||||
}
|
||||
return localPath;
|
||||
}
|
||||
|
||||
private async buildInMemory({
|
||||
|
||||
Reference in New Issue
Block a user