@directus/api
Version:
Directus is a real-time API and App dashboard for managing SQL database content
49 lines (48 loc) • 2.4 kB
JavaScript
import { defineOperationApi } from '@directus/extensions';
import { createRequire } from 'node:module';
import { sieveFunctions } from '@directus/utils';
const require = createRequire(import.meta.url);
const ivm = require('isolated-vm');
/**
* A helper for making the logs prettier.
* The logger prints arrays with their indices but this looks "bad" when you have only one argument.
*/
function unpackArgs(args) {
return args.length === 1 ? args[0] : args;
}
export default defineOperationApi({
id: 'exec',
handler: async ({ code }, { data, env, logger }) => {
const allowedEnv = data['$env'] ?? {};
const isolateSizeMb = env['FLOWS_RUN_SCRIPT_MAX_MEMORY'];
const scriptTimeoutMs = env['FLOWS_RUN_SCRIPT_TIMEOUT'];
const isolate = new ivm.Isolate({ memoryLimit: isolateSizeMb });
const context = isolate.createContextSync();
const jail = context.global;
jail.setSync('global', jail.derefInto());
jail.setSync('process', { env: allowedEnv }, { copy: true });
jail.setSync('module', { exports: null }, { copy: true });
jail.setSync('console', {
log: new ivm.Callback((...args) => logger.info(unpackArgs(args)), { sync: true }),
info: new ivm.Callback((...args) => logger.info(unpackArgs(args)), { sync: true }),
warn: new ivm.Callback((...args) => logger.warn(unpackArgs(args)), { sync: true }),
error: new ivm.Callback((...args) => logger.error(unpackArgs(args)), { sync: true }),
trace: new ivm.Callback((...args) => logger.trace(unpackArgs(args)), { sync: true }),
debug: new ivm.Callback((...args) => logger.debug(unpackArgs(args)), { sync: true }),
}, { copy: true });
// Run the operation once to define the module.exports function
await context.eval(code, { timeout: scriptTimeoutMs });
const inputData = new ivm.ExternalCopy({ data: sieveFunctions(data) });
const resultRef = await context.evalClosure(`return module.exports($0.data)`, [inputData.copyInto()], {
result: { reference: true, promise: true },
timeout: scriptTimeoutMs,
});
const result = await resultRef.copy();
// Memory cleanup
resultRef.release();
inputData.release();
context.release();
isolate.dispose();
return result;
},
});