Skip to content

Commit

Permalink
feat: add module entry point
Browse files Browse the repository at this point in the history
  • Loading branch information
stfsy committed Nov 30, 2023
1 parent 8bc639e commit bc8c5f7
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 0 deletions.
53 changes: 53 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict'

const { readFileSync } = require('fs')
const fn = readFileSync('./lib/script-handler-runner.js', 'utf-8')
const { createTransferableReference, copyValueFromReference, callFunctionWithArguments, runWithIsolatedContext } = require('./isolate/isolate.js')
const { compileAndRunScript } = require('./isolate/script-runner.js')
const { wrapScript } = require('./isolate/wrap-user-provided-adapter-fn-script.js')

/**
* @typedef ExecutionError
* @property {string} cause
* @property {number} code
* @property {string} stack
* @property {string} message
*/

/**
* @typedef ExecutionResult
* @property {any} result
* @property {Object<String, Array>} logs
* @property {number} durationMillis
* @property {ExecutionError} stack
*/

/**
* @param {string} script
* @param {Array} args
* @returns {ExecutionResult}
*/
async function run(script, args) {
return await runWithIsolatedContext({}, async ({ isolate, context }, collectAndReleaseRefs) => {
await compileAndRunScript({ isolate, context, script })
const ref = collectAndReleaseRefs(createTransferableReference({ args }))
const fnResult = collectAndReleaseRefs(await callFunctionWithArguments(context, fn, ref))

const logs = await copyValueFromReference(fnResult, 'console')
const result = await copyValueFromReference(fnResult, 'result')
const error = await copyValueFromReference(fnResult, 'error')
const durationMillis = await copyValueFromReference(fnResult, 'durationMillis')

return { result, logs, error, durationMillis }
})
}

/**
*
* @param {string} script
* @param {Array} args
*/
module.exports = async (script, ...args) => {
const wrappedScript = wrapScript(script)
return run(wrappedScript, args)
}
79 changes: 79 additions & 0 deletions test/spec/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use strict'

const run = require('../../lib/index.js')
const { expect } = require('chai')

const allowedPrimitives = ['NaN', 'undefined']
const allowedArrayTypes = ['BigUint64Array', 'BigInt64Array', 'Uint8Array', 'Int8Array', 'Uint16Array', 'Int16Array', 'Uint32Array', 'Int32Array', 'Float32Array', 'Float64Array', 'Uint8ClampedArray']
const allowedTypes = ['BigInt', 'DataView', 'Map', 'Set', 'WeakMap', 'WeakSet', 'Proxy', 'Reflect', 'WeakRef', 'Object', 'Function', 'Array', 'Number', 'Infinity', 'Boolean', 'String', 'Symbol',]
const allowedErrors = ['Error', 'AggregateError', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError']
const allowedVariables = ['console', 'global', 'globalThis']
const allowedObjects = ['FinalizationRegistry', 'Math', 'JSON', 'Atomics', 'WebAssembly', 'Intl', 'SharedArrayBuffer', 'ArrayBuffer', 'Date', 'Promise', 'RegExp']
const allowedFunctions = ['atob', 'btoa', 'encodeURI', 'encodeURIComponent', 'escape', 'unescape', 'eval', 'decodeURI', 'decodeURIComponent', 'isFinite', 'isNaN', 'parseInt', 'parseFloat']
const allowedGlobals = [...allowedPrimitives, ...allowedErrors, ...allowedTypes, ...allowedArrayTypes, ...allowedObjects, ...allowedFunctions, ...allowedVariables]
const vars = Object.getOwnPropertyNames(global).filter(global => !allowedGlobals.includes(global))

describe('FunctionExecutor', () => {

it('executes a simple script', async () => {
const { result } = await run('return 1+1')
expect(result).to.equal(2)
})

it('throws if the return value is a function', async () => {
const { error } = await run('return Date.now')
expect(error.message).to.equal('Function result must not be of type "function"')
})

it('throws if the response is a function', async () => {
const { error } = await run('return Symbol("1")')
expect(error.message).to.equal('Function result must not be of type "symbol"')
})

it('returns a string result', async () => {
const { result } = await run('return "2"')
expect(result).to.equal("2")
})

it('returns a numeric result', async () => {
const { result } = await run('return 2')
expect(result).to.equal(2)
})

it('returns a boolean result', async () => {
const { result } = await run('return true')
expect(result).to.equal(true)
})

it('returns an object result', async () => {
const { result } = await run('return {hello: {world: "yes"}}')
expect(result).to.deep.equal({ hello: { world: 'yes' } })
})

it('returns an undefined result', async () => {
const { result } = await run('return undefined')
expect(result).to.be.undefined
})

it('returns a null result', async () => {
const { result } = await run('return null')
expect(result).to.be.null
})

it('provides arguments to the function', async () => {
const { result } = await run('return args', { a: 1 })
expect(result).to.deep.equal({ a: 1 })
})

it('provides arguments to the function 2', async () => {
const { result } = await run('return args.a + args.b', { a: 1, b: 3 })
expect(result).to.deep.equal(4)
})

vars.forEach((globalVar) => {
it(`does not allow accessing ${globalVar}`, async () => {
const { error } = await run(`return ${globalVar}`)
expect(error.message).to.equal(`${globalVar} is not defined`)
})
})
})

0 comments on commit bc8c5f7

Please sign in to comment.