Merge pull request #384 from lowdefy/function-operator

Function operator
This commit is contained in:
Gervwyk 2021-02-04 17:49:49 +02:00 committed by GitHub
commit 684343e1bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 371 additions and 65 deletions

View File

@ -16,6 +16,7 @@
import path from 'path';
import { spawnSync } from 'child_process';
import { cleanDirectory } from '@lowdefy/node-utils';
import checkChildProcessError from '../../utils/checkChildProcessError';
import startUp from '../../utils/startUp';
@ -97,6 +98,10 @@ async function buildNetlify(options) {
});
context.print.log(`Netlify functions artifacts moved to "./lowdefy/functions".`);
context.print.log(`Cleaning "./node_modules".`);
cleanDirectory('./node_modules');
context.print.log(`Moving Lowdefy server node_modules.`);
proccessOutput = spawnSync('cp', [
'-r',
path.resolve(netlifyDir, 'package/node_modules'),

View File

@ -24,21 +24,21 @@ global:
sm:
span: 23
types:
TitleInput:
url: https://unpkg.com/@lowdefy/blocks-antd@^3.0.0/dist/meta/TitleInput.json
ParagraphInput:
url: https://unpkg.com/@lowdefy/blocks-antd@^3.0.0/dist/meta/ParagraphInput.json
DangerousHtml:
url: http://localhost:3002/meta/DangerousHtml.json
Html:
url: http://localhost:3002/meta/Html.json
DangerousMarkdown:
url: http://localhost:3003/meta/DangerousMarkdown.json
Markdown:
url: http://localhost:3003/meta/Markdown.json
MarkdownWithCode:
url: http://localhost:3003/meta/MarkdownWithCode.json
# types:
# TitleInput:
# url: https://unpkg.com/@lowdefy/blocks-antd@^3.0.0/dist/meta/TitleInput.json
# ParagraphInput:
# url: https://unpkg.com/@lowdefy/blocks-antd@^3.0.0/dist/meta/ParagraphInput.json
# DangerousHtml:
# url: http://localhost:3002/meta/DangerousHtml.json
# Html:
# url: http://localhost:3002/meta/Html.json
# DangerousMarkdown:
# url: http://localhost:3003/meta/DangerousMarkdown.json
# Markdown:
# url: http://localhost:3003/meta/Markdown.json
# MarkdownWithCode:
# url: http://localhost:3003/meta/MarkdownWithCode.json
menus:
- id: default
@ -372,6 +372,9 @@ menus:
- id: _and
type: MenuLink
pageId: _and
- id: _args
type: MenuLink
pageId: _args
- id: _array
type: MenuLink
pageId: _array
@ -393,12 +396,15 @@ menus:
- id: _event
type: MenuLink
pageId: _event
- id: _global
- id: _function
type: MenuLink
pageId: _global
pageId: _function
- id: _get
type: MenuLink
pageId: _get
- id: _global
type: MenuLink
pageId: _global
- id: _gt
type: MenuLink
pageId: _gt
@ -529,54 +535,54 @@ pages:
- _ref: concepts/events-and-actions.yaml
- _ref: concepts/operators.yaml
- _ref: blocks/input/AutoComplete.yaml
- _ref: blocks/input/ButtonSelector.yaml
- _ref: blocks/input/CheckboxSelector.yaml
- _ref: blocks/input/ChromeColorSelector.yaml
- _ref: blocks/input/CircleColorSelector.yaml
- _ref: blocks/input/ColorSelector.yaml
- _ref: blocks/input/CompactColorSelector.yaml
- _ref: blocks/input/DateRangeSelector.yaml
- _ref: blocks/input/DateSelector.yaml
- _ref: blocks/input/DateTimeSelector.yaml
- _ref: blocks/input/GithubColorSelector.yaml
- _ref: blocks/input/MonthSelector.yaml
- _ref: blocks/input/MultipleSelector.yaml
- _ref: blocks/input/NumberInput.yaml
- _ref: blocks/input/Pagination.yaml
- _ref: blocks/input/ParagraphInput.yaml
- _ref: blocks/input/RadioSelector.yaml
- _ref: blocks/input/RatingSlider.yaml
- _ref: blocks/input/S3UploadButton.yaml
- _ref: blocks/input/Selector.yaml
- _ref: blocks/input/SliderColorSelector.yaml
- _ref: blocks/input/SwatchesColorSelector.yaml
- _ref: blocks/input/Switch.yaml
- _ref: blocks/input/TextArea.yaml
- _ref: blocks/input/TextInput.yaml
- _ref: blocks/input/TitleInput.yaml
- _ref: blocks/input/TwitterColorSelector.yaml
- _ref: blocks/input/WeekSelector.yaml
# - _ref: blocks/input/AutoComplete.yaml
# - _ref: blocks/input/ButtonSelector.yaml
# - _ref: blocks/input/CheckboxSelector.yaml
# - _ref: blocks/input/ChromeColorSelector.yaml
# - _ref: blocks/input/CircleColorSelector.yaml
# - _ref: blocks/input/ColorSelector.yaml
# - _ref: blocks/input/CompactColorSelector.yaml
# - _ref: blocks/input/DateRangeSelector.yaml
# - _ref: blocks/input/DateSelector.yaml
# - _ref: blocks/input/DateTimeSelector.yaml
# - _ref: blocks/input/GithubColorSelector.yaml
# - _ref: blocks/input/MonthSelector.yaml
# - _ref: blocks/input/MultipleSelector.yaml
# - _ref: blocks/input/NumberInput.yaml
# - _ref: blocks/input/Pagination.yaml
# - _ref: blocks/input/ParagraphInput.yaml
# - _ref: blocks/input/RadioSelector.yaml
# - _ref: blocks/input/RatingSlider.yaml
# - _ref: blocks/input/S3UploadButton.yaml
# - _ref: blocks/input/Selector.yaml
# - _ref: blocks/input/SliderColorSelector.yaml
# - _ref: blocks/input/SwatchesColorSelector.yaml
# - _ref: blocks/input/Switch.yaml
# - _ref: blocks/input/TextArea.yaml
# - _ref: blocks/input/TextInput.yaml
# - _ref: blocks/input/TitleInput.yaml
# - _ref: blocks/input/TwitterColorSelector.yaml
# - _ref: blocks/input/WeekSelector.yaml
- _ref: blocks/display/Alert.yaml
- _ref: blocks/display/Anchor.yaml
- _ref: blocks/display/Avatar.yaml
- _ref: blocks/display/Breadcrumb.yaml
- _ref: blocks/display/Button.yaml
- _ref: blocks/display/DangerousHtml.yaml
- _ref: blocks/display/DangerousMarkdown.yaml
- _ref: blocks/display/Descriptions.yaml
- _ref: blocks/display/Divider.yaml
- _ref: blocks/display/Html.yaml
- _ref: blocks/display/Icon.yaml
- _ref: blocks/display/Markdown.yaml
- _ref: blocks/display/MarkdownWithCode.yaml
- _ref: blocks/display/Menu.yaml
- _ref: blocks/display/Paragraph.yaml
- _ref: blocks/display/Progress.yaml
- _ref: blocks/display/Skeleton.yaml
- _ref: blocks/display/Statistic.yaml
- _ref: blocks/display/Title.yaml
# - _ref: blocks/display/Alert.yaml
# - _ref: blocks/display/Anchor.yaml
# - _ref: blocks/display/Avatar.yaml
# - _ref: blocks/display/Breadcrumb.yaml
# - _ref: blocks/display/Button.yaml
# - _ref: blocks/display/DangerousHtml.yaml
# - _ref: blocks/display/DangerousMarkdown.yaml
# - _ref: blocks/display/Descriptions.yaml
# - _ref: blocks/display/Divider.yaml
# - _ref: blocks/display/Html.yaml
# - _ref: blocks/display/Icon.yaml
# - _ref: blocks/display/Markdown.yaml
# - _ref: blocks/display/MarkdownWithCode.yaml
# - _ref: blocks/display/Menu.yaml
# - _ref: blocks/display/Paragraph.yaml
# - _ref: blocks/display/Progress.yaml
# - _ref: blocks/display/Skeleton.yaml
# - _ref: blocks/display/Statistic.yaml
# - _ref: blocks/display/Title.yaml
- _ref: connections/AWSS3.yaml
- _ref: connections/AxiosHttp.yaml
@ -595,6 +601,7 @@ pages:
- _ref: actions/Validate.yaml
- _ref: operators/_and.yaml
- _ref: operators/_args.yaml
- _ref: operators/_array.yaml
- _ref: operators/_base64.yaml
- _ref: operators/_date.yaml
@ -602,8 +609,9 @@ pages:
- _ref: operators/_divide.yaml
- _ref: operators/_eq.yaml
- _ref: operators/_event.yaml
- _ref: operators/_global.yaml
- _ref: operators/_function.yaml
- _ref: operators/_get.yaml
- _ref: operators/_global.yaml
- _ref: operators/_gt.yaml
- _ref: operators/_gte.yaml
- _ref: operators/_if.yaml

View File

@ -0,0 +1,74 @@
# 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.
_ref:
path: templates/operators.yaml.njk
transformer: templates/operatorsMethodTransformer.js
vars:
pageId: _args
pageTitle: _args
types: |
```
(key: string): any
(all: boolean): any
(arguments: {
all?: boolean,
key?: string,
default?: any
}): any
```
description: |
The `_args` operator gets a value from the `arguments` array passed to a function operator. The `arguments` array is an array of all the positional arguments passed to the function.
arguments: |
###### string
If the `_args` operator is called with a string argument, the value of the key in the `arguments` array is returned. If the value is not found, `null` is returned. Dot notation and [block list indexes](/lists) are supported.
###### boolean
If the `_args` operator is called with boolean argument `true`, the entire `arguments` array is returned.
###### object
- `all: boolean`: If `all` is set to `true`, the entire `arguments` array is returned. One of `all` or `key` are required.
- `key: string`: The value of the key in the `arguments` array is returned. If the value is not found, `null`, or the specified default value is returned. Dot notation and [block list indexes](/lists) are supported. One of `all` or `key` are required.
- `default: any`: A value to return if the `key` is not found in `arguments`. By default, `null` is returned if a value is not found.
examples: |
###### Map over an array:
```yaml
_array.map:
on:
- firstName: Ted
lastName: Mosby
- firstName: Robin
lastName: Scherbatsky
- firstName: Marshall
lastName: Eriksen
- firstName: Lily
lastName: Aldrin
- firstName: Barney
lastName: Stinson
callback:
_function:
__string.concat:
- __args: 0.firstName
- ' '
- __args: 0.lastName
```
Returns:
```yaml
- Ted Mosby
- Robin Scherbatsky
- Marshall Eriksen
- Lily Aldrin
- Barney Stinson
```

View File

@ -0,0 +1,65 @@
# 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.
_ref:
path: templates/operators.yaml.njk
transformer: templates/operatorsMethodTransformer.js
vars:
pageId: _function
pageTitle: _function
types: |
```
(definition: any): function
```
description: |
The `_function` operator returns a function that evaluates the given operator definition when called. The resulting function can then be used by other operators like `_array.map`, or by blocks (for example as a formatter function).
To use a operator in a function definition, the operator name should start with a double underscore, `__`, instead of a single underscore. Operators that start with a single underscore will be evaluated when the function is created, and those that start with a double underscore are evaluated when the function is executed.
The arguments passed to the function when it is executed can be accessed with the [`_args`](/_args) operator.
arguments: |
###### any
The function definition. To use operators here, the operator names should start with a double underscore.
examples: |
###### Map over an array:
```yaml
_array.map:
on:
- firstName: Ted
lastName: Mosby
- firstName: Robin
lastName: Scherbatsky
- firstName: Marshall
lastName: Eriksen
- firstName: Lily
lastName: Aldrin
- firstName: Barney
lastName: Stinson
callback:
_function:
__string.concat:
- __args: 0.firstName
- ' '
- __args: 0.lastName
```
Returns:
```yaml
- Ted Mosby
- Robin Scherbatsky
- Marshall Eriksen
- Lily Aldrin
- Barney Stinson
```

View File

@ -0,0 +1,47 @@
/*
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 { serializer, type } from '@lowdefy/helpers';
function removeUnderscore(_, value) {
if (type.isObject(value) && Object.keys(value).length === 1) {
const key = Object.keys(value)[0];
if (key.startsWith('__')) {
const newKey = key.substring(1);
return { [newKey]: value[key] };
}
}
return value;
}
function _function({ arrayIndices, event, location, params, parser }) {
const preparedParams = serializer.copy(params, { reviver: removeUnderscore });
return (...args) => {
const { output, errors } = parser.parse({
arrayIndices,
args,
event,
input: preparedParams,
location,
});
if (errors.length > 0) {
throw new Error(errors[0]);
}
return output;
};
}
export default _function;

View File

@ -21,6 +21,7 @@ import _date from './date';
import _divide from './divide';
import _eq from './eq';
import _event from './event';
import _function from './function';
import _get from './get';
import _global from './global';
import _gt from './gt';
@ -59,6 +60,7 @@ export default {
_divide,
_eq,
_event,
_function,
_get,
_global,
_gt,

View File

@ -27,6 +27,7 @@ class NodeParser {
this.secrets = secrets;
this.state = state;
this.urlQuery = urlQuery;
this.parse = this.parse.bind(this);
this.operations = {
...commonOperators,
...nodeOperators,
@ -67,6 +68,7 @@ class NodeParser {
secrets: this.secrets,
state: this.state,
urlQuery: this.urlQuery,
parser: this,
});
return res;
}

View File

@ -23,6 +23,7 @@ class WebParser {
constructor({ context, contexts }) {
this.context = context;
this.contexts = contexts;
this.parse = this.parse.bind(this);
this.operations = {
...commonOperators,
...webOperators,
@ -67,6 +68,7 @@ class WebParser {
requests: this.context.requests,
state: this.context.state,
urlQuery: this.context.urlQuery,
parser: this,
});
return res;
}

View File

@ -406,3 +406,12 @@ test('parse _url_query operator', () => {
expect(res.output).toEqual('value');
expect(res.errors).toMatchInlineSnapshot(`Array []`);
});
test('parse _function operator', () => {
const input = { _function: { state: { __state: 'key' }, args: { __args: true } } };
const parser = new NodeParser({ state: { key: 'value' } });
const { output, errors } = parser.parse({ input, location: 'locationId' });
expect(output).toBeInstanceOf(Function);
expect(output(1, 2)).toEqual({ state: 'value', args: [1, 2] });
expect(errors).toEqual([]);
});

View File

@ -447,3 +447,12 @@ describe('parse operators', () => {
expect(res.errors).toMatchInlineSnapshot(`Array []`);
});
});
test('parse _function operator', () => {
const input = { _function: { state: { __state: 'string' }, args: { __args: true } } };
const parser = new WebParser({ context, contexts });
const { output, errors } = parser.parse({ input, location: 'locationId' });
expect(output).toBeInstanceOf(Function);
expect(output(1, 2)).toEqual({ state: 'state', args: [1, 2] });
expect(errors).toEqual([]);
});

View File

@ -0,0 +1,83 @@
/*
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 NodeParser from '../../src/nodeParser';
import WebParser from '../../src/webParser';
import _function from '../../src/common/function';
const state = {
string: 'Some String',
number: 42,
arr: [{ a: 'a1' }, { a: 'a2' }],
};
const location = 'location';
const context = {
state,
};
const contexts = {};
test('NodeParser, _function that gets from state', () => {
const parser = new NodeParser({ state });
const params = { __state: 'string' };
const fn = _function({ location, params, parser });
expect(fn).toBeInstanceOf(Function);
expect(fn()).toEqual('Some String');
expect(fn()).toEqual('Some String');
});
test('NodeParser, _function gives args as an array', () => {
const parser = new NodeParser({ state });
const params = { __args: true };
const fn = _function({ location, params, parser });
expect(fn('a')).toEqual(['a']);
expect(fn('a', { b: true })).toEqual(['a', { b: true }]);
});
test('NodeParser, _function throws on parser errors', () => {
const parser = new NodeParser({ state });
const params = { __state: [] };
const fn = _function({ location, params, parser });
expect(fn).toThrow(
'Error: Operator Error: _state params must be of type string, boolean or object. Received: [] at location.'
);
});
test('WebParser, _function that gets from state', () => {
const parser = new WebParser({ context, contexts });
const params = { __state: 'string' };
const fn = _function({ location, params, parser });
expect(fn).toBeInstanceOf(Function);
expect(fn()).toEqual('Some String');
expect(fn()).toEqual('Some String');
});
test('WebParser, _function gives args as an array', () => {
const parser = new WebParser({ context, contexts });
const params = { __args: true };
const fn = _function({ location, params, parser });
expect(fn('a')).toEqual(['a']);
expect(fn('a', { b: true })).toEqual(['a', { b: true }]);
});
test('WebParser, _function throws on parser errors', () => {
const parser = new WebParser({ context, contexts });
const params = { __state: [] };
const fn = _function({ location, params, parser });
expect(fn).toThrow(
'Error: Operator Error: _state params must be of type string, boolean or object. Received: [] at location.'
);
});