Features
- Support Typescript code snap
- Compile & run code safely
- Support auto import packages
- Support runtime inject environments
- Support individual fs space for each running script
- Support runtime script access defined interfaces for outside function invoking
Quickstart
import {createApp, Validator} from '@lakutata/core'
import {CodeRunnerComponent, SourceCodeObject} from '@lakutata-component/code-runner'
import path from 'path'
import os from 'os'
import fs from 'fs'
createApp({
id: 'test.app',
name: 'tester',
components: {
test: {
class: CodeRunnerComponent,
//Set temp directory path. default is os.tmpdir()
tmpdir: os.tmpdir(),
//Npm registry for module fetching, default is https://registry.npmmirror.com/
moduleRegistry: 'https://registry.npmmirror.com/',
//The directory for cache & extract modules fetched from registry
extractModulesPath: path.resolve(__dirname, '../../tmpModules'),
//Preset modules' name for script compile
presetModules: [],
//Timeout for each script
timeout: 60000,
//Global output schema, the finalized value of scripts' returned must be match this schema
outputSchema: Validator.Object(Validator.Number),
//Code Runtime Interface, this function return an object that can be invoked by running script
CRI: (runCodeId: string, app: Application) => {
console.log('runCodeId:',runCodeId)
return {
random: async (a: number) => {
return (a + 1) * 1024 * Math.random()
}
}
},
//File system settings (Each script has their own runtime virtual file system)
//If this setting not set, script will always create new virtual file system while their running
fileSystem: {
sizeLimit: '1MB',
write: async (runCodeId: string, fileSystemBinary: Buffer) => {
if (!fs.existsSync(path.resolve(__dirname, '../../tmpFileSystem'))) {
fs.mkdirSync(path.resolve(__dirname, '../../tmpFileSystem'), {recursive: true})
}
fs.writeFileSync(path.resolve(__dirname, `../../tmpFileSystem/${runCodeId}`), fileSystemBinary, {flag: 'w+'})
},
read: async (runCodeId: string) => {
if (!fs.existsSync(path.resolve(__dirname, '../../tmpFileSystem'))) {
fs.mkdirSync(path.resolve(__dirname, '../../tmpFileSystem'), {recursive: true})
}
if (fs.existsSync(path.resolve(__dirname, `../../tmpFileSystem/${runCodeId}`))) {
return fs.readFileSync(path.resolve(__dirname, `../../tmpFileSystem/${runCodeId}`))
} else {
return undefined
}
}
},
//Concurrent settings for script vm
concurrent: {
min: os.cpus().length / 2,
max: os.cpus().length
},
//IPC sock file path, usually it can be generated automatically
ipcSockPath: path.resolve(os.tmpdir(), './ipc.sock')
}
}
}).then(async app => {
const testComponent: CodeRunnerComponent = app.Components.get<CodeRunnerComponent>('test')
//Declare source code object
const sourceCodeObject: SourceCodeObject = {
environments: {
//Declare environments (JSONSchema)
},
//Source code
source: `
const rdm = () => {
return Math.round(Math.random() * 10000)
};
const fs = require('fs');
const path = require('path');
const loki = require('lokijs@1.5.12');
let users;
const db = new loki('/example.db', {
autoload: true,
autoloadCallback: () => {
users = db.getCollection('users') || db.addCollection('users', {indices: ['email']})
for (let i = 0; i < 100; i++) {
users.insert({name: 'odin' + Date.now(), email: 'odin.soap@lokijs.org', age: rdm()})
users.insert({name: 'thor' + Date.now(), email: 'thor.soap@lokijs.org', age: rdm()})
users.insert({name: 'stan' + Date.now(), email: 'stan.soap@lokijs.org', age: rdm()})
users.insert({name: 'oliver' + Date.now(), email: 'oliver.soap@lokijs.org', age: rdm()})
users.insert({name: 'hector' + Date.now(), email: 'hector.soap@lokijs.org', age: rdm()})
users.insert({name: 'achilles' + Date.now(), email: 'achilles.soap@lokijs.org', age: rdm()})
db.save()
}
console.log(users.count())
},
autosave: true,
autosaveInterval: 4000
});
let counter: number;
const testFilePath = path.resolve(__dirname, './test.txt');
if (!fs.existsSync(testFilePath)) {
fs.writeFileSync(testFilePath, '1', {flag: 'w'});
counter = 1;
} else {
const strCounter = fs.readFileSync(testFilePath).toString();
counter = parseInt(strCounter) + 1;
fs.writeFileSync(testFilePath, counter.toString(), {flag: 'w'});
}
const nerdamer = require('nerdamer/all.min');
const res = await CRI.random(123);
return {a: 1, b: res, c: parseFloat(nerdamer('cos(x)').evaluate({x: ENV.c}).text()), d: counter};
`,
//Declare runtime code output data schema (JSONSchema)
schema: {
type: 'object',
properties: {
a: {type: 'number'},
b: {type: 'number'}
},
required: ['a', 'b']
}
}
//Compile source code object
const result = await testComponent.compile(sourceCodeObject)
console.log('Compile Success')
//Run compiled code, and set code id
const runCodeObject = Object.assign(result, {
id: 'TEST_ID_1234567890'
})
const start = Date.now()
const res = await testComponent.run(runCodeObject, {c: Math.round(Math.random() * 1000)})
//Output result
console.log(JSON.stringify(res, null, 2), Date.now() - start)
}).catch(e => {
console.error(e)
process.exit(1)
})
@lakutata/core required.
How to Contribute
Please let us know how can we help. Do check out issues for bug reports or suggestions first.