fix(operators): Remove deprecated _js wasm.

This commit is contained in:
Gervwyk 2021-06-06 21:38:54 +02:00
parent 70707b4ec0
commit 20d2d0f0d9
3 changed files with 17 additions and 423 deletions

View File

@ -16,126 +16,14 @@
import { type } from '@lowdefy/helpers';
// ! ---------------
// ! DEPRECATED
// ! ---------------
import { serializer } from '@lowdefy/helpers';
import { getQuickJS, shouldInterruptAfterDeadline } from 'quickjs-emscripten';
let QuickJsVm;
function createFunction({ params, location, methodName }) {
if (!params.code || !type.isString(params.code)) {
throw new Error(
`Operator Error: _js.${methodName} "code" argument should be a string at ${location}.`
);
}
const fn = (...args) => {
const jsFnString = `
var logs = [];
function log(...item) {
logs.push(item);
}
var console = {
log,
};
function dateReviver(key, value) {
if (typeof value === 'object' && value !== null && value.hasOwnProperty('_date')) {
var date = new Date(value._date);
if (date instanceof Date && !Number.isNaN(date.getTime())) {
return date;
}
}
return value;
}
function dateSerialize(key, value) {
if (typeof value === 'object' && value !== null) {
Object.keys(value).forEach((k) => {
if (value[k] instanceof Date && !Number.isNaN(value[k].getTime())) {
value[k] = { "_date": value[k].valueOf() };
}
})
}
return value;
}
var args = JSON.parse(decodeURIComponent("${encodeURIComponent(
serializer.serializeToString(args)
)}"), dateReviver);
var fnResult = (${params.code})(...(args || []));
var result = encodeURIComponent(JSON.stringify([fnResult, logs], dateSerialize));
`;
const codeHandle = QuickJsVm.unwrapResult(
QuickJsVm.evalCode(jsFnString, {
shouldInterrupt: shouldInterruptAfterDeadline(Date.now() + 1000),
memoryLimitBytes: 1024 * 1024,
})
);
const resultHandle = QuickJsVm.getProp(QuickJsVm.global, 'result');
codeHandle.dispose();
const result = serializer.deserializeFromString(
decodeURIComponent(QuickJsVm.getString(resultHandle))
);
result[1].forEach((item) => {
console.log(...item);
});
return result[0];
};
return fn;
}
function evaluate({ params, location, methodName }) {
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 });
return fn(...(params.args || []));
}
const methods = { evaluate, function: createFunction };
function _DEPRECATED_js({ params, location, methodName }) {
if (!QuickJsVm) {
throw new Error(
`Operator Error: _js is not initialized. Received: ${JSON.stringify(params)} at ${location}.`
);
}
return methods[methodName]({ params, location, methodName });
}
async function init() {
const QuickJs = await getQuickJS();
QuickJsVm = QuickJs.createVm();
}
async function clear() {
QuickJsVm = null;
}
_js.init = init;
_js.clear = clear;
// ! ---------------
function _js({ context, params, location, methodName }) {
// ! DEPRECATED methods
if (!type.isFunction(context.lowdefy.imports.jsOperators[methodName]) && !methods[methodName]) {
if (!type.isFunction(context.lowdefy.imports.jsOperators[methodName])) {
throw new Error(`Operator Error: _js.${methodName} is not a function.`);
}
if (context.lowdefy.imports.jsOperators[methodName]) {
if (!type.isNone(params) && !type.isArray(params)) {
throw new Error(`Operator Error: _js.${methodName} takes an array as input at ${location}.`);
}
return context.lowdefy.imports.jsOperators[methodName](...(params || []));
if (!type.isNone(params) && !type.isArray(params)) {
throw new Error(`Operator Error: _js.${methodName} takes an array as input at ${location}.`);
}
// ! DEPRECATED ---------------
console.warn(
'WARNING: _js.evaluate and _js.function will has been deprecated and will be removed in the next version. Please see: https://docs.lowdefy.com/_js for more details.'
);
if (!type.isObject(params)) {
throw new Error(`Operator Error: _js.${methodName} takes an object as input at ${location}.`);
}
return _DEPRECATED_js({ params, location, methodName });
// ! ---------------
return context.lowdefy.imports.jsOperators[methodName](...(params || []));
}
export default _js;

View File

@ -499,19 +499,18 @@ describe('parse operators', () => {
expect(errors).toEqual([]);
});
test('parse _js operator', async () => {
test.only('parse _js operator retuning a function', async () => {
const test_fn = () => (a, b) => a + b;
const mockFn = jest.fn().mockImplementation(test_fn);
context.lowdefy.imports.jsOperators.test_fn = mockFn;
const input = {
'_js.function': {
code: `function (a, b){
return a + b;
}`,
},
'_js.test_fn': [],
};
const parser = new WebParser({ context, contexts });
await parser.init();
const { output, errors } = parser.parse({ input, location: 'locationId' });
expect(output).toBeInstanceOf(Function);
expect(output(1, 2)).toEqual(3);
expect(output(4, 2)).toEqual(6);
expect(errors).toEqual([]);
});

View File

@ -18,9 +18,6 @@ import _js from '../../src/web/js';
import { context } from '../testContext';
const location = 'location';
beforeAll(async () => {
await _js.init();
});
test('_js.test_fn and params to return a value', () => {
const params = [12, 14];
@ -59,302 +56,12 @@ test('_js.test_fn params not an array', () => {
);
});
// ! --------------
// ! DEPRECATED
// ! --------------
test('_js with code and args specified', () => {
const params = {
code: `function (one, two) {
return one + two;
}`,
args: [12, 14],
};
const fn = _js({ context, location, params, methodName: 'function' });
expect(fn).toBeInstanceOf(Function);
expect(fn(1, 2)).toEqual(3);
expect(_js({ context, location, params, methodName: 'evaluate' })).toEqual(26);
expect(_js({ context, location, params: { code: params.code }, methodName: 'evaluate' })).toEqual(
null
test('_js.not_a_function', () => {
const params = 10;
const test_fn = (a, b) => a + b;
const mockFn = jest.fn().mockImplementation(test_fn);
context.lowdefy.imports.jsOperators.test_fn = mockFn;
expect(() => _js({ context, location, params, methodName: 'not_a_function' })).toThrow(
new Error('Operator Error: _js.not_a_function is not a function.')
);
});
test('_js with code and args specified to return json object', () => {
const params = {
code: `function (one, two) {
return { a: one, b: two, c: [1,2,3, one, two, "three"]};
}`,
args: [12, 14],
};
const fn = _js({ context, location, params, methodName: 'function' });
expect(fn).toBeInstanceOf(Function);
expect(fn(1, 2)).toEqual({ a: 1, b: 2, c: [1, 2, 3, 1, 2, 'three'] });
expect(_js({ context, location, params, methodName: 'evaluate' })).toEqual({
a: 12,
b: 14,
c: [1, 2, 3, 12, 14, 'three'],
});
expect(_js({ context, location, params: { code: params.code }, methodName: 'evaluate' })).toEqual(
{
c: [1, 2, 3, null, null, 'three'],
}
);
});
test('_js with code and args specified to return json array', () => {
const params = {
code: `function (one, two) {
return [1,2,3, one, two, "three"];
}`,
args: [12, 14],
};
const fn = _js({ context, location, params, methodName: 'function' });
expect(fn).toBeInstanceOf(Function);
expect(fn(1, 2)).toEqual([1, 2, 3, 1, 2, 'three']);
expect(_js({ context, location, params, methodName: 'evaluate' })).toEqual([
1,
2,
3,
12,
14,
'three',
]);
expect(_js({ context, location, params: { code: params.code }, methodName: 'evaluate' })).toEqual(
[1, 2, 3, null, null, 'three']
);
});
test('_js with open "\'" in result', () => {
const str = `<div style=\"test: one;\">one's result</div>`;
const params = {
code: `function chars(input) {
return [{x: input, b: 1}];
}`,
args: [str],
};
const fn = _js({ context, location, params, methodName: 'function' });
expect(fn).toBeInstanceOf(Function);
expect(fn(str)).toEqual([{ x: str, b: 1 }]);
expect(_js({ context, location, params, methodName: 'evaluate' })).toEqual([{ x: str, b: 1 }]);
});
test('_js with date in input and result', () => {
const params = {
code: `function duration(from, to) {
return {
from,
to,
fromIsDate: from instanceof Date,
toIsDate: from instanceof Date,
duration: to - from,
};
}`,
args: [new Date(0), new Date(10)],
};
const fn = _js({ context, location, params, methodName: 'function' });
expect(fn).toBeInstanceOf(Function);
expect(_js({ context, location, params, methodName: 'evaluate' })).toEqual({
from: new Date(0),
to: new Date(10),
duration: 10,
fromIsDate: true,
toIsDate: true,
});
expect(fn(new Date(0), new Date(10))).toEqual({
from: new Date(0),
to: new Date(10),
duration: 10,
fromIsDate: true,
toIsDate: true,
});
});
test('_js with undefined result returns null', () => {
const params = {
code: `function add(one, two) {
return;
}`,
args: [12, 14],
};
const fn = _js({ context, location, params, methodName: 'function' });
expect(fn).toBeInstanceOf(Function);
expect(fn()).toEqual(null);
expect(_js({ context, location, params, methodName: 'evaluate' })).toEqual(null);
expect(_js({ context, location, params: { code: params.code }, methodName: 'evaluate' })).toEqual(
null
);
});
test('_js with console.log', () => {
const logger = console.log;
console.log = jest.fn();
const params = {
code: `function logTest(one, two) {
console.log(one)
console.log(two)
console.log(one, two, null, 123, 'abc', true, false);
return;
}`,
args: [12, new Date(1)],
};
const fn = _js({ context, location, params, methodName: 'function' });
expect(fn).toBeInstanceOf(Function);
_js({ context, location, params, methodName: 'evaluate' });
expect(console.log.mock.calls).toEqual([
[12],
[new Date(1)],
[12, new Date(1), null, 123, 'abc', true, false],
]);
console.log = logger;
});
test('_js with code with args that needs escaped characters', () => {
const params = {
code: `function (obj) {
return "<div>" + obj.html + "</html>";
}`,
args: [
{
html: '<a href="https://lowdefy.com">Lowdefy Website</a>',
},
],
};
expect(_js({ context, location, params, methodName: 'evaluate' })).toEqual(
'<div><a href="https://lowdefy.com">Lowdefy Website</a></html>'
);
const fn = _js({ context, location, params, methodName: 'function' });
expect(fn).toBeInstanceOf(Function);
expect(
fn({
html: '<a href="https://lowdefy.com">Lowdefy Website</a>',
})
).toEqual('<div><a href="https://lowdefy.com">Lowdefy Website</a></html>');
});
test('_js with code and no "function" specified to throw', () => {
const params = {
code: `
var value = "world";
var result = 'a new ' + 'vm for Hello ' + value;
`,
};
expect(() =>
_js({ context, location, params, methodName: 'evaluate' })
).toThrowErrorMatchingInlineSnapshot(`"unexpected token in expression: 'var'"`);
expect(() => {
const fn = _js({ context, location, params, methodName: 'function' });
fn();
}).toThrowErrorMatchingInlineSnapshot(`"unexpected token in expression: 'var'"`);
});
test('_js invalid js code', () => {
const params = {
code: 'Hello',
};
expect(() =>
_js({ context, location, params, methodName: 'evaluate' })
).toThrowErrorMatchingInlineSnapshot(`"'Hello' is not defined"`);
const fn = _js({ context, location, params, methodName: 'function' });
expect(() => fn()).toThrowErrorMatchingInlineSnapshot(`"'Hello' is not defined"`);
});
test('_js not a function', () => {
const params = {
code: '"Hello"',
};
expect(() =>
_js({ context, location, params, methodName: 'evaluate' })
).toThrowErrorMatchingInlineSnapshot(`"not a function"`);
const fn = _js({ context, location, params, methodName: 'function' });
expect(() => fn()).toThrowErrorMatchingInlineSnapshot(`"not a function"`);
});
test('_js params not a object', () => {
const params = [];
expect(() =>
_js({ context, location, params, methodName: 'function' })
).toThrowErrorMatchingInlineSnapshot(
`"Operator Error: _js.function takes an object as input at location."`
);
expect(() =>
_js({ context, location, params, methodName: 'evaluate' })
).toThrowErrorMatchingInlineSnapshot(
`"Operator Error: _js.evaluate takes an object as input at location."`
);
});
test('_js.invalid methodName', () => {
const params = {
code: `function () {
return args[0] + args[1]
}`,
};
expect(() =>
_js({ context, location, params, methodName: 'invalid' })
).toThrowErrorMatchingInlineSnapshot(`"Operator Error: _js.invalid is not a function."`);
});
test('_js invalid js code', () => {
const params = {
code: 1,
};
expect(() =>
_js({ context, location, params, methodName: 'function' })
).toThrowErrorMatchingInlineSnapshot(
`"Operator Error: _js.function \\"code\\" argument should be a string at location."`
);
expect(() =>
_js({ context, location, params, methodName: 'evaluate' })
).toThrowErrorMatchingInlineSnapshot(
`"Operator Error: _js.evaluate \\"code\\" argument should be a string at location."`
);
});
test('_js no body or file', () => {
const params = {};
expect(() =>
_js({ context, location, params, methodName: 'function' })
).toThrowErrorMatchingInlineSnapshot(
`"Operator Error: _js.function \\"code\\" argument should be a string at location."`
);
expect(() =>
_js({ context, location, params, methodName: 'evaluate' })
).toThrowErrorMatchingInlineSnapshot(
`"Operator Error: _js.evaluate \\"code\\" argument should be a string at location."`
);
});
test('_js.evaluate, args not an array', () => {
const params = {
args: 1,
body: `function () {
return args[0] + args[1]
}`,
};
expect(() =>
_js({ context, location, 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 = {
code: `{
return args[0] + args[1]
}`,
};
_js.clear();
expect(() =>
_js({ context, location, params, methodName: 'function' })
).toThrowErrorMatchingInlineSnapshot(
`"Operator Error: _js is not initialized. Received: {\\"code\\":\\"{\\\\n return args[0] + args[1]\\\\n }\\"} at location."`
);
expect(() =>
_js({ context, location, params, methodName: 'evaluate' })
).toThrowErrorMatchingInlineSnapshot(
`"Operator Error: _js is not initialized. Received: {\\"code\\":\\"{\\\\n return args[0] + args[1]\\\\n }\\"} at location."`
);
});
// ! --------------