diff --git a/packages/cli/src/commands/buildNetlify/buildNetlify.js b/packages/cli/src/commands/buildNetlify/buildNetlify.js index 5c55047cb..ce101ed5a 100644 --- a/packages/cli/src/commands/buildNetlify/buildNetlify.js +++ b/packages/cli/src/commands/buildNetlify/buildNetlify.js @@ -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'), diff --git a/packages/docs/lowdefy.yaml b/packages/docs/lowdefy.yaml index 453987634..5db1f9d12 100644 --- a/packages/docs/lowdefy.yaml +++ b/packages/docs/lowdefy.yaml @@ -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 diff --git a/packages/docs/operators/_args.yaml b/packages/docs/operators/_args.yaml new file mode 100644 index 000000000..5748aac78 --- /dev/null +++ b/packages/docs/operators/_args.yaml @@ -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 + ``` diff --git a/packages/docs/operators/_function.yaml b/packages/docs/operators/_function.yaml new file mode 100644 index 000000000..0832ba4d5 --- /dev/null +++ b/packages/docs/operators/_function.yaml @@ -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 + ``` diff --git a/packages/operators/src/common/function.js b/packages/operators/src/common/function.js new file mode 100644 index 000000000..0833e1709 --- /dev/null +++ b/packages/operators/src/common/function.js @@ -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; diff --git a/packages/operators/src/common/index.js b/packages/operators/src/common/index.js index 35805df83..1bbb9a118 100644 --- a/packages/operators/src/common/index.js +++ b/packages/operators/src/common/index.js @@ -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, diff --git a/packages/operators/src/nodeParser.js b/packages/operators/src/nodeParser.js index 1b67c4a5c..79ff5b887 100644 --- a/packages/operators/src/nodeParser.js +++ b/packages/operators/src/nodeParser.js @@ -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; } diff --git a/packages/operators/src/webParser.js b/packages/operators/src/webParser.js index 8cfaa7992..4a2aaf8b2 100644 --- a/packages/operators/src/webParser.js +++ b/packages/operators/src/webParser.js @@ -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; } diff --git a/packages/operators/test/NodeParser.test.js b/packages/operators/test/NodeParser.test.js index 63c9853d9..2f90ff883 100644 --- a/packages/operators/test/NodeParser.test.js +++ b/packages/operators/test/NodeParser.test.js @@ -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([]); +}); diff --git a/packages/operators/test/WebParser.test.js b/packages/operators/test/WebParser.test.js index 55f2763f5..86ac6dfa1 100644 --- a/packages/operators/test/WebParser.test.js +++ b/packages/operators/test/WebParser.test.js @@ -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([]); +}); diff --git a/packages/operators/test/common/function.test.js b/packages/operators/test/common/function.test.js new file mode 100644 index 000000000..d171f48a4 --- /dev/null +++ b/packages/operators/test/common/function.test.js @@ -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.' + ); +});