mirror of
https://github.com/lowdefy/lowdefy.git
synced 2025-04-06 15:30:30 +08:00
fix: Create _js using quickjs-emscripten.
This commit is contained in:
parent
0e82467443
commit
4ec8a300d1
10
.pnp.cjs
generated
10
.pnp.cjs
generated
@ -4655,6 +4655,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["jest", "npm:26.6.3"],
|
||||
["js-yaml", "npm:4.0.0"],
|
||||
["mingo", "npm:4.1.2"],
|
||||
["quickjs-emscripten", "npm:0.11.0"],
|
||||
["uuid", "npm:8.3.2"]
|
||||
],
|
||||
"linkType": "SOFT",
|
||||
@ -21694,6 +21695,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
"linkType": "HARD",
|
||||
}]
|
||||
]],
|
||||
["quickjs-emscripten", [
|
||||
["npm:0.11.0", {
|
||||
"packageLocation": "./.yarn/cache/quickjs-emscripten-npm-0.11.0-4f09eb00c3-a5cf45a1fd.zip/node_modules/quickjs-emscripten/",
|
||||
"packageDependencies": [
|
||||
["quickjs-emscripten", "npm:0.11.0"]
|
||||
],
|
||||
"linkType": "HARD",
|
||||
}]
|
||||
]],
|
||||
["raf", [
|
||||
["npm:3.4.1", {
|
||||
"packageLocation": "./.yarn/cache/raf-npm-3.4.1-c25d48d76e-567b0160be.zip/node_modules/raf/",
|
||||
|
BIN
.yarn/cache/quickjs-emscripten-npm-0.11.0-4f09eb00c3-a5cf45a1fd.zip
vendored
Normal file
BIN
.yarn/cache/quickjs-emscripten-npm-0.11.0-4f09eb00c3-a5cf45a1fd.zip
vendored
Normal file
Binary file not shown.
@ -42,6 +42,7 @@
|
||||
"deep-diff": "1.0.2",
|
||||
"js-yaml": "4.0.0",
|
||||
"mingo": "4.1.2",
|
||||
"quickjs-emscripten": "0.11.0",
|
||||
"uuid": "8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
102
packages/operators/src/common/js.js
Normal file
102
packages/operators/src/common/js.js
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { shouldInterruptAfterDeadline } from 'quickjs-emscripten';
|
||||
import { type } from '@lowdefy/helpers';
|
||||
|
||||
function regexCaptureFunctionBody({ file, location }) {
|
||||
const regex = /^.*function\s*\S+\(\.\.\.args\)\s*(\{.*\})\s*export default .*$/s;
|
||||
const match = regex.exec(file);
|
||||
if (!match) {
|
||||
throw new Error(`Operator Error: _js received invalid javascript file at ${location}.`);
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
|
||||
function createFunction({ params, location, methodName, QuickJsVm }) {
|
||||
let body;
|
||||
if (params.file) {
|
||||
body = regexCaptureFunctionBody({ file: params.file, location });
|
||||
} else {
|
||||
if (!params.body) {
|
||||
throw new Error(
|
||||
`Operator Error: _js.${methodName} did not receive a "file" or "body" argument at ${location}.`
|
||||
);
|
||||
}
|
||||
if (!type.isString(params.body)) {
|
||||
throw new Error(
|
||||
`Operator Error: _js.${methodName} "body" argument should be a string at ${location}.`
|
||||
);
|
||||
}
|
||||
body = params.body;
|
||||
}
|
||||
const fn = (...args) => {
|
||||
const codeHandle = QuickJsVm.unwrapResult(
|
||||
QuickJsVm.evalCode(
|
||||
`
|
||||
var args = JSON.parse('${JSON.stringify(args)}');
|
||||
function fn() {
|
||||
${body}
|
||||
}
|
||||
var result = JSON.stringify(fn());
|
||||
`,
|
||||
{
|
||||
shouldInterrupt: shouldInterruptAfterDeadline(Date.now() + 1000),
|
||||
memoryLimitBytes: 1024 * 1024,
|
||||
}
|
||||
)
|
||||
);
|
||||
const resultHandle = QuickJsVm.getProp(QuickJsVm.global, 'result');
|
||||
codeHandle.dispose();
|
||||
return JSON.parse(QuickJsVm.getString(resultHandle));
|
||||
};
|
||||
return fn;
|
||||
}
|
||||
|
||||
function evaluate({ params, location, methodName, QuickJsVm }) {
|
||||
if (!type.isArray(params.args) && !type.isNone(params.args)) {
|
||||
throw new Error(
|
||||
`Operator Error: _js.evaluate "args" argument should be an array, null or undefined at ${location}.`
|
||||
);
|
||||
}
|
||||
const fn = createFunction({ params, location, methodName, QuickJsVm });
|
||||
return fn(...(params.args || []));
|
||||
}
|
||||
|
||||
const methods = { evaluate, function: createFunction };
|
||||
|
||||
function _js({ params, location, methodName, instances }) {
|
||||
if (!instances || !instances.QuickJsVm) {
|
||||
throw new Error(
|
||||
`Operator Error: _js requires an instance of QuickJsVm. Received: ${JSON.stringify(
|
||||
params
|
||||
)} at ${location}.`
|
||||
);
|
||||
}
|
||||
const { QuickJsVm } = instances;
|
||||
if (!type.isObject(params)) {
|
||||
throw new Error(`Operator Error: _js.${methodName} takes an object as input at ${location}.`);
|
||||
}
|
||||
if (!methods[methodName]) {
|
||||
throw new Error(
|
||||
`Operator Error: _js.${methodName} is not supported at ${location}. Use one of the following: evaluate, function.`
|
||||
);
|
||||
}
|
||||
|
||||
return methods[methodName]({ params, location, methodName, QuickJsVm });
|
||||
}
|
||||
|
||||
export default _js;
|
205
packages/operators/test/common/js.test.js
Normal file
205
packages/operators/test/common/js.test.js
Normal file
@ -0,0 +1,205 @@
|
||||
/*
|
||||
Copyright 2020-2021 Lowdefy, Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import _js from '../../src/common/js';
|
||||
import { getQuickJS } from 'quickjs-emscripten';
|
||||
|
||||
const location = 'location';
|
||||
let instances;
|
||||
beforeAll(async () => {
|
||||
const QuickJs = await getQuickJS();
|
||||
const QuickJsVm = QuickJs.createVm();
|
||||
instances = { QuickJsVm };
|
||||
});
|
||||
|
||||
test('_js.function with body and args specified', () => {
|
||||
const params = {
|
||||
body: `{
|
||||
return args[0] + args[1]
|
||||
}`,
|
||||
};
|
||||
const fn = _js({ location, instances, params, methodName: 'function' });
|
||||
expect(fn).toBeInstanceOf(Function);
|
||||
expect(fn(1, 2)).toEqual(3);
|
||||
});
|
||||
|
||||
test('_js.function with body and no args specified', () => {
|
||||
const params = {
|
||||
body: `{
|
||||
const value = "world";
|
||||
return 'a new ' + 'vm for Hello ' + value;
|
||||
}`,
|
||||
};
|
||||
const fn = _js({ location, instances, params, methodName: 'evaluate' });
|
||||
expect(_js({ location, instances, params, methodName: 'evaluate' })).toEqual(
|
||||
'a new vm for Hello world'
|
||||
);
|
||||
});
|
||||
|
||||
test('_js.function with file specified', () => {
|
||||
const params = {
|
||||
file: `
|
||||
// Header comment
|
||||
|
||||
const a = 3;
|
||||
|
||||
function add(...args) {
|
||||
return args[0] + args[1]
|
||||
}
|
||||
|
||||
export default add`,
|
||||
};
|
||||
const fn = _js({ location, instances, params, methodName: 'function' });
|
||||
expect(fn).toBeInstanceOf(Function);
|
||||
expect(fn(1, 2)).toEqual(3);
|
||||
});
|
||||
|
||||
test('_js.evaluate with body specified', () => {
|
||||
const params = {
|
||||
args: [1, 2],
|
||||
body: `{
|
||||
return args[0] + args[1]
|
||||
}`,
|
||||
};
|
||||
const res = _js({ location, instances, params, methodName: 'evaluate' });
|
||||
expect(res).toEqual(3);
|
||||
});
|
||||
|
||||
test('_js.evaluate with file specified', () => {
|
||||
const params = {
|
||||
args: [1, 2],
|
||||
file: `
|
||||
// Header comment
|
||||
|
||||
const a = 3;
|
||||
|
||||
function add(...args) {
|
||||
return args[0] + args[1]
|
||||
}
|
||||
|
||||
export default add`,
|
||||
};
|
||||
const res = _js({ location, instances, params, methodName: 'evaluate' });
|
||||
expect(res).toEqual(3);
|
||||
});
|
||||
|
||||
test('_js.function params not a object', () => {
|
||||
const params = [];
|
||||
expect(() =>
|
||||
_js({ location, instances, params, methodName: 'function' })
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Operator Error: _js.function takes an object as input at location."`
|
||||
);
|
||||
});
|
||||
|
||||
test('_js.invalid methodName', () => {
|
||||
const params = {
|
||||
body: `{
|
||||
return args[0] + args[1]
|
||||
}`,
|
||||
};
|
||||
expect(() =>
|
||||
_js({ location, instances, params, methodName: 'invalid' })
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Operator Error: _js.invalid is not supported at location. Use one of the following: evaluate, function."`
|
||||
);
|
||||
});
|
||||
|
||||
test('_js.function invalid js file', () => {
|
||||
const params = {
|
||||
file: 1,
|
||||
};
|
||||
expect(() =>
|
||||
_js({ location, instances, params, methodName: 'function' })
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Operator Error: _js received invalid javascript file at location."`
|
||||
);
|
||||
});
|
||||
|
||||
test('_js.function invalid js file', () => {
|
||||
const params = {
|
||||
file: 'Hello',
|
||||
};
|
||||
expect(() =>
|
||||
_js({ location, instances, params, methodName: 'function' })
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Operator Error: _js received invalid javascript file at location."`
|
||||
);
|
||||
});
|
||||
|
||||
test('_js.function no body or file', () => {
|
||||
const params = {};
|
||||
expect(() =>
|
||||
_js({ location, instances, params, methodName: 'function' })
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Operator Error: _js.function did not receive a \\"file\\" or \\"body\\" argument at location."`
|
||||
);
|
||||
});
|
||||
|
||||
test('_js.function body not a string', () => {
|
||||
const params = {
|
||||
body: 1,
|
||||
};
|
||||
expect(() =>
|
||||
_js({ location, instances, params, methodName: 'function' })
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Operator Error: _js.function \\"body\\" argument should be a string at location."`
|
||||
);
|
||||
});
|
||||
|
||||
test('_js.evaluate, args not an array', () => {
|
||||
const params = {
|
||||
args: 1,
|
||||
body: `{
|
||||
return args[0] + args[1]
|
||||
}`,
|
||||
};
|
||||
expect(() =>
|
||||
_js({ location, instances, params, methodName: 'evaluate' })
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Operator Error: _js.evaluate \\"args\\" argument should be an array, null or undefined at location."`
|
||||
);
|
||||
});
|
||||
|
||||
test('_js with undefined vm', () => {
|
||||
const params = {
|
||||
body: `{
|
||||
return args[0] + args[1]
|
||||
}`,
|
||||
};
|
||||
expect(() =>
|
||||
_js({ location, params, methodName: 'evaluate' })
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Operator Error: _js requires an instance of QuickJsVm. Received: {\\"body\\":\\"{\\\\n return args[0] + args[1]\\\\n }\\"} at location."`
|
||||
);
|
||||
expect(() =>
|
||||
_js({ location, instances: {}, params, methodName: 'evaluate' })
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Operator Error: _js requires an instance of QuickJsVm. Received: {\\"body\\":\\"{\\\\n return args[0] + args[1]\\\\n }\\"} at location."`
|
||||
);
|
||||
});
|
||||
|
||||
// TODO: interrupt handler does not seem to work.
|
||||
// test('_js interrupts infinite loop execution', () => {
|
||||
// const params = {
|
||||
// body: `{
|
||||
// i = 0; while (1) { i++ }
|
||||
// }`,
|
||||
// };
|
||||
// expect(() =>
|
||||
// _js({ location, instances, params, methodName: 'evaluate' })
|
||||
// ).toThrowErrorMatchingInlineSnapshot();
|
||||
// });
|
@ -3200,6 +3200,7 @@ __metadata:
|
||||
jest: 26.6.3
|
||||
js-yaml: 4.0.0
|
||||
mingo: 4.1.2
|
||||
quickjs-emscripten: 0.11.0
|
||||
uuid: 8.3.2
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
@ -16350,6 +16351,13 @@ fsevents@^1.2.7:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"quickjs-emscripten@npm:0.11.0":
|
||||
version: 0.11.0
|
||||
resolution: "quickjs-emscripten@npm:0.11.0"
|
||||
checksum: a5cf45a1fdf570decbfd7f224f8cd5b36d838a669232d27cdc9f6ec5c617af1301a36c2c1e3fc4228bed1b65a347256ea0cb62774a00f1ff4d1edd172f101952
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"raf@npm:^3.4.0, raf@npm:^3.4.1":
|
||||
version: 3.4.1
|
||||
resolution: "raf@npm:3.4.1"
|
||||
|
Loading…
x
Reference in New Issue
Block a user