@lowdefy/docs-content 0.0.0-experimental-20260710073520
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +20 -0
- package/content/actions/callapi.md +66 -0
- package/content/actions/callmethod.md +41 -0
- package/content/actions/copytoclipboard.md +32 -0
- package/content/actions/displaymessage.md +53 -0
- package/content/actions/downloadcsv.md +89 -0
- package/content/actions/fetch.md +50 -0
- package/content/actions/geolocationcurrentposition.md +56 -0
- package/content/actions/link.md +104 -0
- package/content/actions/login.md +85 -0
- package/content/actions/logout.md +56 -0
- package/content/actions/pdfmake.md +304 -0
- package/content/actions/request.md +67 -0
- package/content/actions/reset.md +27 -0
- package/content/actions/resetvalidation.md +85 -0
- package/content/actions/scrollto.md +85 -0
- package/content/actions/setdarkmode.md +62 -0
- package/content/actions/setfocus.md +33 -0
- package/content/actions/setglobal.md +63 -0
- package/content/actions/setlocale.md +69 -0
- package/content/actions/setstate.md +70 -0
- package/content/actions/throw.md +72 -0
- package/content/actions/validate.md +87 -0
- package/content/actions/wait.md +24 -0
- package/content/agents/agent-endpoint-tools.md +190 -0
- package/content/agents/agent-mcp-tools.md +124 -0
- package/content/agents/agent-multi-agent.md +136 -0
- package/content/agents/agent-properties.md +174 -0
- package/content/agents/agent-server-hooks.md +336 -0
- package/content/agents/agentchat.md +515 -0
- package/content/agents/agentconversations.md +148 -0
- package/content/concepts/ai-agent-docs.md +190 -0
- package/content/concepts/cli.md +213 -0
- package/content/concepts/connections-and-requests.md +7 -0
- package/content/concepts/custom-html.md +3 -0
- package/content/concepts/custom-styling.md +161 -0
- package/content/concepts/events-and-actions.md +271 -0
- package/content/concepts/hosting-files.md +18 -0
- package/content/concepts/i18n.md +320 -0
- package/content/concepts/keyboard-shortcuts.md +218 -0
- package/content/concepts/layout-overview.md +478 -0
- package/content/concepts/lists.md +144 -0
- package/content/concepts/logger.md +43 -0
- package/content/concepts/lowdefy-schema.md +365 -0
- package/content/concepts/menus.md +144 -0
- package/content/concepts/module-authoring.md +564 -0
- package/content/concepts/modules.md +338 -0
- package/content/concepts/operators.md +45 -0
- package/content/concepts/overview.md +19 -0
- package/content/concepts/references-and-templates.md +207 -0
- package/content/concepts/secrets.md +24 -0
- package/content/concepts/selectors.md +143 -0
- package/content/connections/amazonredshift.md +176 -0
- package/content/connections/anthropic.md +232 -0
- package/content/connections/awss3.md +313 -0
- package/content/connections/azureblobstorage.md +133 -0
- package/content/connections/elasticsearch.md +371 -0
- package/content/connections/google.md +228 -0
- package/content/connections/googlecloudstorage.md +139 -0
- package/content/connections/googlesheet.md +313 -0
- package/content/connections/knex.md +206 -0
- package/content/connections/mariadb.md +177 -0
- package/content/connections/mcp.md +134 -0
- package/content/connections/mongodb.md +739 -0
- package/content/connections/mssql.md +191 -0
- package/content/connections/mysql.md +192 -0
- package/content/connections/openai-connection.md +228 -0
- package/content/connections/postgresql.md +200 -0
- package/content/connections/redis.md +116 -0
- package/content/connections/sendgridmail.md +94 -0
- package/content/connections/smtp.md +149 -0
- package/content/connections/sqlite.md +187 -0
- package/content/connections/stripe.md +115 -0
- package/content/container-blocks/affix.md +413 -0
- package/content/container-blocks/alert.md +937 -0
- package/content/container-blocks/badge.md +1222 -0
- package/content/container-blocks/box.md +1453 -0
- package/content/container-blocks/card.md +1200 -0
- package/content/container-blocks/carousel.md +2247 -0
- package/content/container-blocks/collapse.md +1335 -0
- package/content/container-blocks/configprovider.md +1070 -0
- package/content/container-blocks/confirmmodal.md +1159 -0
- package/content/container-blocks/content.md +597 -0
- package/content/container-blocks/descriptions.md +870 -0
- package/content/container-blocks/drawer.md +1379 -0
- package/content/container-blocks/dropdownmenu.md +643 -0
- package/content/container-blocks/flex.md +1510 -0
- package/content/container-blocks/footer.md +380 -0
- package/content/container-blocks/googlemaps.md +546 -0
- package/content/container-blocks/googlemapsheatmap.md +607 -0
- package/content/container-blocks/googlemapsscript.md +61 -0
- package/content/container-blocks/header.md +302 -0
- package/content/container-blocks/label.md +739 -0
- package/content/container-blocks/layout.md +604 -0
- package/content/container-blocks/masonry.md +1090 -0
- package/content/container-blocks/mobilemenu.md +1244 -0
- package/content/container-blocks/modal.md +1180 -0
- package/content/container-blocks/pageheadermenu.md +614 -0
- package/content/container-blocks/pagesidebarlayout.md +438 -0
- package/content/container-blocks/pagesidermenu.md +651 -0
- package/content/container-blocks/popover.md +1421 -0
- package/content/container-blocks/result.md +972 -0
- package/content/container-blocks/sider.md +1064 -0
- package/content/container-blocks/span.md +747 -0
- package/content/container-blocks/spinner.md +267 -0
- package/content/container-blocks/splitter.md +1735 -0
- package/content/container-blocks/tabs.md +1718 -0
- package/content/container-blocks/tooltip.md +1274 -0
- package/content/container-blocks/watermark.md +515 -0
- package/content/controls/for.md +103 -0
- package/content/controls/if.md +107 -0
- package/content/controls/log.md +50 -0
- package/content/controls/parallel.md +71 -0
- package/content/controls/parallel_for.md +36 -0
- package/content/controls/reject.md +56 -0
- package/content/controls/return.md +66 -0
- package/content/controls/set_state.md +98 -0
- package/content/controls/switch.md +90 -0
- package/content/controls/throw.md +79 -0
- package/content/controls/try.md +88 -0
- package/content/deployment/docker.md +149 -0
- package/content/deployment/node-server.md +27 -0
- package/content/deployment/vercel.md +101 -0
- package/content/display-blocks/aggrid.md +1459 -0
- package/content/display-blocks/anchor.md +336 -0
- package/content/display-blocks/avatar.md +1154 -0
- package/content/display-blocks/breadcrumb.md +517 -0
- package/content/display-blocks/button.md +758 -0
- package/content/display-blocks/calendar.md +156 -0
- package/content/display-blocks/dangeroushtml.md +314 -0
- package/content/display-blocks/dangerousmarkdown.md +423 -0
- package/content/display-blocks/diffgit.md +44 -0
- package/content/display-blocks/difflist.md +225 -0
- package/content/display-blocks/diffsidebyside.md +56 -0
- package/content/display-blocks/difftimeline.md +94 -0
- package/content/display-blocks/divider.md +406 -0
- package/content/display-blocks/download.md +60 -0
- package/content/display-blocks/dropdownbutton.md +661 -0
- package/content/display-blocks/echart.md +418 -0
- package/content/display-blocks/floatbutton.md +545 -0
- package/content/display-blocks/html.md +311 -0
- package/content/display-blocks/icon.md +717 -0
- package/content/display-blocks/img.md +479 -0
- package/content/display-blocks/markdown.md +520 -0
- package/content/display-blocks/markdownwithcode.md +730 -0
- package/content/display-blocks/menu.md +1123 -0
- package/content/display-blocks/message.md +941 -0
- package/content/display-blocks/notification.md +1410 -0
- package/content/display-blocks/paragraph.md +643 -0
- package/content/display-blocks/progress.md +818 -0
- package/content/display-blocks/qrcode.md +519 -0
- package/content/display-blocks/s3download.md +240 -0
- package/content/display-blocks/search.md +360 -0
- package/content/display-blocks/skeleton.md +272 -0
- package/content/display-blocks/skeletonavatar.md +222 -0
- package/content/display-blocks/skeletonbutton.md +245 -0
- package/content/display-blocks/skeletoninput.md +223 -0
- package/content/display-blocks/skeletonparagraph.md +201 -0
- package/content/display-blocks/statistic.md +485 -0
- package/content/display-blocks/steps.md +585 -0
- package/content/display-blocks/tag.md +620 -0
- package/content/display-blocks/title.md +654 -0
- package/content/display-blocks/tour.md +2359 -0
- package/content/input-blocks/autocomplete.md +786 -0
- package/content/input-blocks/buttonselector.md +964 -0
- package/content/input-blocks/checkboxselector.md +765 -0
- package/content/input-blocks/checkboxswitch.md +497 -0
- package/content/input-blocks/colorselector.md +884 -0
- package/content/input-blocks/daterangeselector.md +777 -0
- package/content/input-blocks/dateselector.md +693 -0
- package/content/input-blocks/datetimeselector.md +791 -0
- package/content/input-blocks/inputgallery.md +528 -0
- package/content/input-blocks/monthselector.md +717 -0
- package/content/input-blocks/multipleselector.md +1337 -0
- package/content/input-blocks/numberinput.md +855 -0
- package/content/input-blocks/pagination.md +679 -0
- package/content/input-blocks/paragraphinput.md +1664 -0
- package/content/input-blocks/passwordinput.md +480 -0
- package/content/input-blocks/phonenumberinput.md +688 -0
- package/content/input-blocks/radioselector.md +820 -0
- package/content/input-blocks/ratingslider.md +684 -0
- package/content/input-blocks/s3uploadbutton.md +384 -0
- package/content/input-blocks/s3uploaddragger.md +305 -0
- package/content/input-blocks/s3uploadphoto.md +288 -0
- package/content/input-blocks/segmentedselector.md +782 -0
- package/content/input-blocks/selector.md +1162 -0
- package/content/input-blocks/switch.md +552 -0
- package/content/input-blocks/textarea.md +714 -0
- package/content/input-blocks/textinput.md +639 -0
- package/content/input-blocks/tiptapinput.md +226 -0
- package/content/input-blocks/tiptapmentioninput.md +471 -0
- package/content/input-blocks/titleinput.md +1094 -0
- package/content/input-blocks/treeinput.md +144 -0
- package/content/input-blocks/treemultipleselector.md +162 -0
- package/content/input-blocks/treeselector.md +185 -0
- package/content/input-blocks/upload.md +149 -0
- package/content/input-blocks/uploaddragger.md +85 -0
- package/content/input-blocks/uploadphoto.md +84 -0
- package/content/input-blocks/weekselector.md +630 -0
- package/content/list-blocks/controlledlist.md +498 -0
- package/content/list-blocks/list.md +529 -0
- package/content/list-blocks/listselector.md +301 -0
- package/content/list-blocks/masonrylist.md +230 -0
- package/content/migration/upgrade-guide.md +106 -0
- package/content/migration/v4-to-v5.md +589 -0
- package/content/migration/v5-to-v6.md +36 -0
- package/content/operators/_actions.md +58 -0
- package/content/operators/_and.md +76 -0
- package/content/operators/_api.md +254 -0
- package/content/operators/_app.md +114 -0
- package/content/operators/_args.md +65 -0
- package/content/operators/_array.md +331 -0
- package/content/operators/_base64.md +49 -0
- package/content/operators/_boolean.md +33 -0
- package/content/operators/_build.md +166 -0
- package/content/operators/_change_case.md +357 -0
- package/content/operators/_date.md +415 -0
- package/content/operators/_dayjs.md +128 -0
- package/content/operators/_diff.md +61 -0
- package/content/operators/_divide.md +32 -0
- package/content/operators/_eq.md +52 -0
- package/content/operators/_event.md +86 -0
- package/content/operators/_function.md +52 -0
- package/content/operators/_get.md +92 -0
- package/content/operators/_global.md +86 -0
- package/content/operators/_gt.md +50 -0
- package/content/operators/_gte.md +50 -0
- package/content/operators/_hash.md +112 -0
- package/content/operators/_if.md +30 -0
- package/content/operators/_if_none.md +47 -0
- package/content/operators/_index.md +48 -0
- package/content/operators/_input.md +86 -0
- package/content/operators/_intl.md +135 -0
- package/content/operators/_item.md +166 -0
- package/content/operators/_js.md +113 -0
- package/content/operators/_json.md +120 -0
- package/content/operators/_jsonata.md +152 -0
- package/content/operators/_locale.md +65 -0
- package/content/operators/_location.md +40 -0
- package/content/operators/_log.md +22 -0
- package/content/operators/_lt.md +50 -0
- package/content/operators/_lte.md +50 -0
- package/content/operators/_math.md +407 -0
- package/content/operators/_media.md +83 -0
- package/content/operators/_menu.md +62 -0
- package/content/operators/_module.md +340 -0
- package/content/operators/_mql.md +95 -0
- package/content/operators/_ne.md +62 -0
- package/content/operators/_not.md +33 -0
- package/content/operators/_number.md +159 -0
- package/content/operators/_nunjucks.md +117 -0
- package/content/operators/_object.md +253 -0
- package/content/operators/_operator.md +33 -0
- package/content/operators/_or.md +67 -0
- package/content/operators/_product.md +45 -0
- package/content/operators/_random.md +90 -0
- package/content/operators/_ref.md +80 -0
- package/content/operators/_regex.md +69 -0
- package/content/operators/_request.md +35 -0
- package/content/operators/_request_details.md +29 -0
- package/content/operators/_secret.md +56 -0
- package/content/operators/_state.md +134 -0
- package/content/operators/_step.md +98 -0
- package/content/operators/_string.md +327 -0
- package/content/operators/_subtract.md +24 -0
- package/content/operators/_sum.md +45 -0
- package/content/operators/_switch.md +37 -0
- package/content/operators/_t.md +96 -0
- package/content/operators/_theme.md +65 -0
- package/content/operators/_type.md +78 -0
- package/content/operators/_uri.md +49 -0
- package/content/operators/_url_query.md +92 -0
- package/content/operators/_user.md +154 -0
- package/content/operators/_uuid.md +118 -0
- package/content/operators/_var.md +88 -0
- package/content/operators/_yaml.md +96 -0
- package/content/other/introduction.md +39 -0
- package/content/other/next-auth-providers.md +3 -0
- package/content/other/updatesession.md +26 -0
- package/content/plugins/plugins-actions.md +80 -0
- package/content/plugins/plugins-connections.md +98 -0
- package/content/plugins/plugins-dev.md +138 -0
- package/content/plugins/plugins-introduction.md +29 -0
- package/content/plugins/plugins-operators.md +71 -0
- package/content/testing/e2e-auth.md +108 -0
- package/content/testing/e2e-configuration.md +107 -0
- package/content/testing/e2e-getting-started.md +96 -0
- package/content/testing/e2e-introduction.md +53 -0
- package/content/testing/e2e-mocking.md +155 -0
- package/content/testing/e2e-writing-tests.md +214 -0
- package/content/tutorial/tutorial-actions-operators.md +222 -0
- package/content/tutorial/tutorial-add-blocks.md +86 -0
- package/content/tutorial/tutorial-create-page.md +57 -0
- package/content/tutorial/tutorial-display-data-page.md +181 -0
- package/content/tutorial/tutorial-next-steps.md +53 -0
- package/content/tutorial/tutorial-requests-sql.md +22 -0
- package/content/tutorial/tutorial-start.md +71 -0
- package/content/user-authentication/auth-providers.md +15 -0
- package/content/user-authentication/login-and-logout.md +122 -0
- package/content/user-authentication/users-introduction.md +31 -0
- package/index.json +2281 -0
- package/package.json +34 -0
- package/scripts/extractAgentDocs.js +130 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Action Plugins
|
|
2
|
+
|
|
3
|
+
Actions are functions that are triggered by [events](https://docs.lowdefy.com/events-and-actions) in a Lowdefy app. Actions can be `async` functions. The response returned by the action can be read using the `_actions` operator. If a promise is returned, the promise will be awaited before the next action is run.
|
|
4
|
+
|
|
5
|
+
The Lowdefy engine provides a context object to the action with the following parameters:
|
|
6
|
+
|
|
7
|
+
- `globals: object`: Commonly used Javascript global objects. These are passed to the action for easier testing.
|
|
8
|
+
- `document: object`: The browser [`window.document`](https://developer.mozilla.org/en-US/docs/Web/API/Document/Document) global variable.
|
|
9
|
+
- `fetch: function`: The global [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) Javascript method.
|
|
10
|
+
- `window: object`: The browser [`window`](https://developer.mozilla.org/en-US/docs/Web/API/Window) global variable.
|
|
11
|
+
- `methods: object`:
|
|
12
|
+
- `callMethod: function`: Call the CallMethod action.
|
|
13
|
+
- `displayMessage: function`: Call the DisplayMessage action.
|
|
14
|
+
- `getActions: function`: Get data from previous actions in the action chain.
|
|
15
|
+
- `getBlockId: function`: Get the blockId of the block that triggered the event.
|
|
16
|
+
- `getEvent: function`: Get event data from the event that triggered the action.
|
|
17
|
+
- `getGlobal: function`: Get data from the Lowdefy global object.
|
|
18
|
+
- `getInput: function`: Get data from the Lowdefy input object.
|
|
19
|
+
- `getLocale: function`: Return the active i18n locale code (or undefined when `config.i18n` is not configured).
|
|
20
|
+
- `getPageId: function`: Get the pageId of the block that the action is defined on.
|
|
21
|
+
- `getRequestDetails: function`: Get data from Lowdefy requests.
|
|
22
|
+
- `getState: function`: Get data from the Lowdefy state object.
|
|
23
|
+
- `getUrlQuery: function`: Get data from the Lowdefy urlQuery object.
|
|
24
|
+
- `getUser: function`: Get data from the Lowdefy user object.
|
|
25
|
+
- `link: function`: Call the Link action.
|
|
26
|
+
- `login: function`: Call the Login action.
|
|
27
|
+
- `logout: function`: Call the Logout action.
|
|
28
|
+
- `request: function`: Call the Request action.
|
|
29
|
+
- `reset: function`: Call the Reset action.
|
|
30
|
+
- `resetValidation: function`: Call the ResetValidation action.
|
|
31
|
+
- `setGlobal: function`: Call the SetGlobal action.
|
|
32
|
+
- `setState: function`: Call the SetState action.
|
|
33
|
+
- `translate: function`: Look up a translation key (`(key, values?) => string`) — same lookup chain as the [`_t`](/_t) operator. See [i18n](/i18n) for the plugin-author contract.
|
|
34
|
+
- `validate: function`: Call the Validate action.
|
|
35
|
+
- `params: any`: The `params` defined by the user in the Lowdefy configuration. Operators are evaluated before the params are passed to the action. No validation is performed on this object.
|
|
36
|
+
|
|
37
|
+
#### Examples
|
|
38
|
+
|
|
39
|
+
###### The Lowdefy SetState action:
|
|
40
|
+
```js
|
|
41
|
+
function SetState({ methods: { setState }, params }) {
|
|
42
|
+
setState(params);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default SetState;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
###### The Lowdefy Fetch action:
|
|
49
|
+
```js
|
|
50
|
+
async function Fetch({ globals, params }) {
|
|
51
|
+
const { fetch } = globals;
|
|
52
|
+
const { url, options, responseFunction } = params;
|
|
53
|
+
const res = await fetch(url, options);
|
|
54
|
+
if (responseFunction) {
|
|
55
|
+
return res[responseFunction]();
|
|
56
|
+
}
|
|
57
|
+
return res;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default Fetch;
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
###### An action that shows the user confetti using the js-confetti package:
|
|
64
|
+
```js
|
|
65
|
+
// Confetti.js
|
|
66
|
+
import JSConfetti from 'js-confetti';
|
|
67
|
+
|
|
68
|
+
async function Confetti({ params }) {
|
|
69
|
+
const jsConfetti = new JSConfetti();
|
|
70
|
+
const { emojis, confettiRadius = 3, confettiNumber = 50, emojiSize = 10 } = params;
|
|
71
|
+
jsConfetti.addConfetti({
|
|
72
|
+
emojis,
|
|
73
|
+
confettiRadius,
|
|
74
|
+
confettiNumber,
|
|
75
|
+
emojiSize,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default Confetti;
|
|
80
|
+
```
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Connection and Request Plugins
|
|
2
|
+
|
|
3
|
+
Connections and requests are used to integrate with other services like API's or databases.
|
|
4
|
+
|
|
5
|
+
Connections configure the settings to the external service, and often contain parameters like connection strings, urls and secrets like passwords or API keys. Lowdefy does not execute any code for the connection, it is merely a convenient object to hold configuration that is shared by multiple requests.
|
|
6
|
+
|
|
7
|
+
Each connection type can include a number of request types. Requests are the functions that the Lowdefy server executes. The response returned by the request function will be serialised into JSON (with additional support for dates and Javascript errors) and used as the response value for the request by the client. If a promise is returned it wil be awaited.
|
|
8
|
+
|
|
9
|
+
Request parameters:
|
|
10
|
+
- `callApi: function`: Invoke another Lowdefy API endpoint in-process. See [Calling another endpoint](#calling-another-endpoint) below.
|
|
11
|
+
- `connection: object`: The connection `properties` defined by the user in the Lowdefy configuration. Operators are evaluated before the properties are passed to the request.
|
|
12
|
+
- `connectionId: string`: The connectionId used by the request and as set on the request.
|
|
13
|
+
- `pageId: string`: The pageId from which the request was called.
|
|
14
|
+
- `payload: object`: The payload data object passed to the request. Operators are evaluated on the client before the payload is passed to the server request function.
|
|
15
|
+
- `request: object`: The request `properties` defined by the user in the Lowdefy configuration. Operators are evaluated before the properties are passed to the request.
|
|
16
|
+
- `requestId: object`: The requestId of the request.
|
|
17
|
+
|
|
18
|
+
#### Calling another endpoint
|
|
19
|
+
|
|
20
|
+
Request resolvers can invoke a Lowdefy API endpoint from inside the resolver using the `callApi` function from the argument bag:
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
const response = await callApi({ endpointId, payload });
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- `endpointId: string`: __Required__ - The id of the target endpoint. Module endpoints use a `<module>/<endpoint>` id — `callApi` treats this as an opaque string and does not parse it.
|
|
27
|
+
- `payload: object`: Optional payload passed to the target endpoint's routine as `_payload`. Already-evaluated by the caller.
|
|
28
|
+
|
|
29
|
+
`callApi` returns the value returned by the target's `:return` step, or `null` if the target routine terminates without an explicit `:return`. The semantics match the routine [`CallApi`](/lowdefy-api#calling-other-endpoints) step:
|
|
30
|
+
|
|
31
|
+
- The caller's user identity is used to authorize the target endpoint — no auth bypass.
|
|
32
|
+
- The target runs in an isolated routine context: fresh `_payload`, fresh `_state`, fresh `_step` namespace.
|
|
33
|
+
- The target inherits the caller's parser closure — `_user`, `_secret`, `_env` resolve to the same values as the caller.
|
|
34
|
+
- [Internal API endpoints](/lowdefy-api#internal-api-endpoints) are reachable.
|
|
35
|
+
- Endpoint calls share a per-chain depth cap of 10 with routine `:call_api` steps. Exceeding the cap throws an error.
|
|
36
|
+
|
|
37
|
+
###### Errors
|
|
38
|
+
|
|
39
|
+
`callApi` throws on failure. The thrown error preserves its underlying class so resolvers can branch on it:
|
|
40
|
+
|
|
41
|
+
- `ConfigError` — unknown endpoint id, unauthorized against the target, or depth cap exceeded. Unauthorized collapses to the same "does not exist" message as a missing endpoint to avoid leaking endpoint existence.
|
|
42
|
+
- `UserError` — the target routine terminated with [`:throw`](/:throw) or [`:reject`](/:reject). Catch this class to distinguish a deliberately-failed routine from a system fault.
|
|
43
|
+
- Other Lowdefy error classes (`RequestError`, `ServiceError`) propagate unchanged from the target's failure site.
|
|
44
|
+
|
|
45
|
+
###### Example
|
|
46
|
+
|
|
47
|
+
A simplified resolver that looks up a user via another endpoint before running its own request:
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
async function FetchOrderWithUser({ callApi, request, connection }) {
|
|
51
|
+
const user = await callApi({
|
|
52
|
+
endpointId: 'get_user',
|
|
53
|
+
payload: { id: request.userId },
|
|
54
|
+
});
|
|
55
|
+
const orders = await connection.db
|
|
56
|
+
.collection('orders')
|
|
57
|
+
.find({ user_id: user.id })
|
|
58
|
+
.toArray();
|
|
59
|
+
return { user, orders };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
FetchOrderWithUser.meta = {
|
|
63
|
+
checkRead: true,
|
|
64
|
+
checkWrite: false,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default FetchOrderWithUser;
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### Schema Validation
|
|
71
|
+
|
|
72
|
+
A [JSON Schema](https://json-schema.org/) schema can be used to validate the connection and request properties before they are passed to the request function. The [ajv-errors](https://ajv.js.org/packages/ajv-errors.html) package is used to provide useful error messages if the schema validation fails. Attached the json-schema to the request function with property `schema`, to validate request input before the request function is called.
|
|
73
|
+
|
|
74
|
+
#### Examples
|
|
75
|
+
|
|
76
|
+
###### A simplified version of the AxiosHttp request:
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
import axios from 'axios';
|
|
80
|
+
import { mergeObjects } from '@lowdefy/helpers';
|
|
81
|
+
|
|
82
|
+
import schema from '../schema.js';
|
|
83
|
+
|
|
84
|
+
async function AxiosHttp({ request, connection }) {
|
|
85
|
+
const config = mergeObjects([connection, request]);
|
|
86
|
+
const res = await axios(config);
|
|
87
|
+
const { status, statusText, headers, method, path, data } = res;
|
|
88
|
+
return { status, statusText, headers, method, path, data };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
AxiosHttp.schema = schema; // Attached json-schema used to validate request input before the request function is called.
|
|
92
|
+
AxiosHttp.meta = {
|
|
93
|
+
checkRead: false,
|
|
94
|
+
checkWrite: false,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export default AxiosHttp;
|
|
98
|
+
```
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Developing Plugins
|
|
2
|
+
|
|
3
|
+
To develop plugins and publish plugins to npm or to use unpublished, project-specific plugins, we recommend using a pnpm monorepo with the plugin packages and a Lowdefy app, as demonstrated in [this example](https://github.com/lowdefy/lowdefy-example-plugins). The [Lowdefy default plugin packages](https://github.com/lowdefy/lowdefy/tree/main/packages/plugins) can also be used as examples.
|
|
4
|
+
|
|
5
|
+
> In this documentation the collection of blocks, connections, actions, etc. will be referred to as _types_, not to be confused with Typescript types.
|
|
6
|
+
|
|
7
|
+
> The Lowdefy plugin system is under active development and breaking changes might be made in a minor version update.
|
|
8
|
+
|
|
9
|
+
A plugin package needs to include the following:
|
|
10
|
+
|
|
11
|
+
- A `package.json` file which declares the package and exports the plugin types.
|
|
12
|
+
- A `types.js` file which exports the plugin type names.
|
|
13
|
+
- Files that export all the types as named exports.
|
|
14
|
+
- The code for the types exposed by the plugin package.
|
|
15
|
+
|
|
16
|
+
### package.json
|
|
17
|
+
|
|
18
|
+
Lowdefy plugin packages should be an [ECMAScript module](https://nodejs.org/api/esm.html#modules-ecmascript-modules). To configure a package as an ECMAScript module, the `type` field in the `package.json` file should be set to `"module"`.
|
|
19
|
+
|
|
20
|
+
Lowdefy uses the [`exports`](https://nodejs.org/api/packages.html#exports) package entry points to map to the `types.js` and plugin named export files.
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"name": "plugin-name",
|
|
25
|
+
"version": "1.0.0",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"exports": {
|
|
28
|
+
"./actions": "./src/actions.js",
|
|
29
|
+
"./auth/callbacks": "./src/auth/callbacks.js",
|
|
30
|
+
"./auth/events": "./src/auth/events.js",
|
|
31
|
+
"./auth/providers": "./src/auth/providers.js",
|
|
32
|
+
"./blocks": "./src/blocks.js",
|
|
33
|
+
"./connections": "./src/connections.js",
|
|
34
|
+
"./metas": "./src/metas.js",
|
|
35
|
+
"./operators/client": "./src/operators/client.js",
|
|
36
|
+
"./operators/server": "./src/operators/server.js",
|
|
37
|
+
"./types": "./src/types.js"
|
|
38
|
+
},
|
|
39
|
+
"files": ["src/*"],
|
|
40
|
+
"dependencies": {}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The `./types` entry point is required, and the other entry points are only required if the package exports the corresponding types. Block packages should also export `./metas` for build-time schema generation.
|
|
45
|
+
|
|
46
|
+
You can change your folder structure in your package, but then you need to change the `exports` entry points in the `package.json` file to match your file structure.
|
|
47
|
+
|
|
48
|
+
##### Compiling React for blocks
|
|
49
|
+
|
|
50
|
+
To develop a block plugin, the React code needs to compiled before it can be used in the Lowdefy app. The [Lowdefy plugin example](https://github.com/lowdefy/lowdefy-example-plugins) has an example of this configured using SWC.
|
|
51
|
+
|
|
52
|
+
### types.js
|
|
53
|
+
|
|
54
|
+
The `types.js` file declares the different types provided by the plugin package. This is used by the Lowdefy build process to map plugin types used in the Lowdefy config to plugin packages.
|
|
55
|
+
|
|
56
|
+
The file should export an object that contains the type names (the names used in the Lowdefy config) for all the different types provided by the package.
|
|
57
|
+
|
|
58
|
+
###### Example with all types
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
export default {
|
|
62
|
+
actions: ['Action'],
|
|
63
|
+
auth: {
|
|
64
|
+
callbacks: ['Callback'],
|
|
65
|
+
events: ['Event'],
|
|
66
|
+
provider: ['Provider'],
|
|
67
|
+
},
|
|
68
|
+
blocks: ['Block'],
|
|
69
|
+
connections: ['Connection'],
|
|
70
|
+
requests: ['Request'],
|
|
71
|
+
operators: {
|
|
72
|
+
build: ['_build_operator', '_shared_operator'],
|
|
73
|
+
client: ['_client_operator', '_shared_operator'],
|
|
74
|
+
server: ['_server_operator', '_shared_operator'],
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The type names do not need to be hard coded, and can be generated by importing the plugin types.
|
|
80
|
+
|
|
81
|
+
###### Example `types.js` for connections
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
import * as connections from './connections.js';
|
|
85
|
+
|
|
86
|
+
export default {
|
|
87
|
+
connections: Object.keys(connections),
|
|
88
|
+
requests: Object.keys(connections)
|
|
89
|
+
.map((connection) => Object.keys(connections[connection].requests))
|
|
90
|
+
.flat(),
|
|
91
|
+
};
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
###### Example `types.js` for blocks
|
|
95
|
+
|
|
96
|
+
Block packages use `extractBlockTypes` from `@lowdefy/block-utils` to derive type information from block metadata files. Each block has a `meta.js` file containing its category, icons, events, CSS keys, and property schema. These are re-exported via a `metas.js` barrel file:
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
// src/metas.js — re-export each block's metadata
|
|
100
|
+
export { default as MyBlock } from './blocks/MyBlock/meta.js';
|
|
101
|
+
export { default as MyOtherBlock } from './blocks/MyOtherBlock/meta.js';
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
// src/types.js — derive types from metas
|
|
106
|
+
import { extractBlockTypes } from '@lowdefy/block-utils';
|
|
107
|
+
import * as metas from './metas.js';
|
|
108
|
+
|
|
109
|
+
export default extractBlockTypes(metas);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The `extractBlockTypes` function returns `{ blocks, icons, blockMetas }` which the build pipeline uses to resolve plugin types without loading React components or heavy dependencies.
|
|
113
|
+
|
|
114
|
+
See the [default Lowdefy block packages](https://github.com/lowdefy/lowdefy/tree/main/packages/plugins/blocks) for full examples.
|
|
115
|
+
|
|
116
|
+
### Named export files
|
|
117
|
+
|
|
118
|
+
A file that exports the plugin types as named exports is needed for each kind of plugin-types. This is so that the Lowdefy server can import these directly to allow for tree shaking to reduce bundle size by excluding unused code. In the default configuration these file names correspond to the package entry point paths, for example, the files are named `actions.js`, `blocks.js`, and `operators/client.js`, but different file names and folder structures can be configured.
|
|
119
|
+
|
|
120
|
+
###### Example `actions.js` file
|
|
121
|
+
```js
|
|
122
|
+
export { default as MyAction } from './actions/MyAction.js';
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
In the Lowdefy server, the types will be imported as:
|
|
126
|
+
```js
|
|
127
|
+
import { MyAction as MyAction } from 'my-plugin-package/actions';
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Plugin code
|
|
131
|
+
|
|
132
|
+
Most types are written as standard Javascript functions, and blocks are written as React components. The function names should be in _CapitalCase_, except for operators which should be written in _snake_case_ and start with an underscore.
|
|
133
|
+
|
|
134
|
+
Lowdefy uses object [destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) to provide parameters to the type functions. These parameters include the parameters or properties defined by the user in the Lowdefy configuration. Any operators used in the configuration will be evaluated before it is passed to the type function. Other relevant metadata and helper functions are also passed to the plugin function.
|
|
135
|
+
|
|
136
|
+
### Localizing plugin strings
|
|
137
|
+
|
|
138
|
+
Plugins can ship locale-keyed default messages so consumers see translated strings out-of-the-box, and translate strings at runtime via `methods.translate(key, values)` in block / action code. See [i18n](/i18n) for the full plugin-author contract — `./messages` export shape, `package.json` `exports` registration, namespacing convention, and merge precedence.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Plugins
|
|
2
|
+
|
|
3
|
+
Lowdefy plugins provides an interface to extend the platform functionality with custom javascript code. Plugins are installed into the Lowdefy app during the Lowdefy build process, and as a result are included as part of the Vite client build output. This enables plugin developers to use any npm packages when building Lowdefy plugins.
|
|
4
|
+
|
|
5
|
+
Lowdefy plugins can be used to create custom blocks, connections, requests, action, operators and auth providers, adapters, callbacks and events. These plugins are written as standard [npm modules](https://docs.npmjs.com/about-packages-and-modules#about-modules), so community plugins can be published to npm, and the workspace and git protocols can be used for project specific or private plugins. Since plugins are npm packages most Javascript features and third-party npm packages can be used in plugins.
|
|
6
|
+
|
|
7
|
+
## Using a plugin
|
|
8
|
+
|
|
9
|
+
To use a plugin in an app, the plugin name and version should be specified in the `plugins` section of the Lowdefy config. Once the plugin is included, the types (blocks, connections, etc) defined in the plugin can be used anywhere in the app.
|
|
10
|
+
|
|
11
|
+
If two plugins export types with the same type name, user defined plugins will override the default Lowdefy types, and user defined plugins will overwrite other plugins defined before them in the plugins array.
|
|
12
|
+
|
|
13
|
+
The `typePrefix` property can be used to prevent plugins with the same name overwriting each other. If specified, the type names of all plugins in that package will have the prefix appended. For example, if a package contains a block with type `Button` and the typePrefix is set to `Other`, that block can be used with type name `OtherButton`, and the default Lowdefy `Button` block will still be available.
|
|
14
|
+
|
|
15
|
+
###### Example
|
|
16
|
+
|
|
17
|
+
```yaml
|
|
18
|
+
plugins:
|
|
19
|
+
# plugins installed remotely from npm
|
|
20
|
+
- name: npm-plugin
|
|
21
|
+
version: 1.0.0
|
|
22
|
+
# local plugins in pnpm monorepo
|
|
23
|
+
- name: local-plugin
|
|
24
|
+
version: workspace:*
|
|
25
|
+
# Add a prefix to type names
|
|
26
|
+
- name: other-plugin
|
|
27
|
+
version: 1.0.0
|
|
28
|
+
typePrefix: Other
|
|
29
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Operator Plugins
|
|
2
|
+
|
|
3
|
+
Operators are synchronous functions that are used in Lowdefy to express logic. If you need to use asynchronous code, or promises, write an [action](/plugins-actions) instead. Operator names should start with a single underscore, as this is used by the Lowdefy engine to identify and evaluate operators. By convention, operator functions are written in _snake_case_.
|
|
4
|
+
|
|
5
|
+
Operators are used in both the web client and server runtimes. Some operators make sense in both environments, but other operators only work in one or the other. The `types.js` file declares the server and client operators exported by an app in the `operators.client` and `operators.server` fields. The operator named exports are exported under the `./operators/client` and `./operators/server` package entry points for client and server operators respectively. The same function can be exported for both the client and the server runtimes, or two different implementations can be exported in each file.
|
|
6
|
+
|
|
7
|
+
Client parameters:
|
|
8
|
+
- `actions: object`: The actions data object.
|
|
9
|
+
- `args: array`: If the operator is used inside a `_function` operator, `args` is an array of arguments passed to the function.
|
|
10
|
+
- `basePath: string`: The configured app URL basePath.
|
|
11
|
+
- `event: object`: The event data object.
|
|
12
|
+
- `globals: object`: Commonly used Javascript global objects. These are passed to the operator for easier testing.
|
|
13
|
+
- `document: object`: The browser [`window.document`](https://developer.mozilla.org/en-US/docs/Web/API/Document/Document) global variable.
|
|
14
|
+
- `window: object`: The browser [`window`](https://developer.mozilla.org/en-US/docs/Web/API/Window) global variable.
|
|
15
|
+
- `home: object`:
|
|
16
|
+
- `configured: boolean`: True if the user configured a home pageId.
|
|
17
|
+
- `pageId: string`: The pageId of the home page. Either the user configured home pageId, or the pageId of the first page visible to the user in the default menu.
|
|
18
|
+
- `input: object`: The input data object.
|
|
19
|
+
- `lowdefyGlobal: `: The Lowdefy global data object.
|
|
20
|
+
- `menus: array`: The app menus.
|
|
21
|
+
- `methodName: string`: The name of the method if the operator is called with a method (as `_operator.methodName`).
|
|
22
|
+
- `operators: object`: An object with all the operator functions used on the client.
|
|
23
|
+
- `pageId: string`: The pageId of the page the operator is evaluated on.
|
|
24
|
+
- `params: any`: The `params` defined by the user in the Lowdefy configuration. Operators are evaluated before the params are passed to the operator. No validation is performed on this object.
|
|
25
|
+
- `requests: object`: The requests data object.
|
|
26
|
+
- `runtime: string`: The runtime is set to the constant `"browser"`.
|
|
27
|
+
- `state: object`: The state data object.
|
|
28
|
+
- `user: object`: The user data object.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
Server parameters:
|
|
32
|
+
- `args: array`: If the operator is used inside a `_function` operator, `args` is an array of arguments passed to the function.
|
|
33
|
+
- `methodName: string`: The name of the method if the operator is called with a method (as `_operator.methodName`).
|
|
34
|
+
- `operators: object`: An object with all the operator functions used on the server.
|
|
35
|
+
- `params: any`: The `params` defined by the user in the Lowdefy configuration. Operators are evaluated before the params are passed to the operator. No validation is performed on this object.
|
|
36
|
+
- `payload: object`: The payload object passed to a request. Operators are evaluated on the client before the payload is passed to the server request function.
|
|
37
|
+
- `runtime: string`: The runtime is set to the constant `"node"`.
|
|
38
|
+
- `secrets: object`: The secrets data object.
|
|
39
|
+
- `user: object`: The user data object.
|
|
40
|
+
|
|
41
|
+
### Pure Functions
|
|
42
|
+
|
|
43
|
+
Operators in Lowdefy are usually pure functions - given a specific input they always give the same output. If a operator is used in blocks, they must be pure functions because operators are evaluated every time a page is re-rendered. Operators in block properties are evaluated recursively until the evaluated result matches the previous evaluated result.
|
|
44
|
+
|
|
45
|
+
#### Examples
|
|
46
|
+
|
|
47
|
+
###### An operator that multiplies a number by 11:
|
|
48
|
+
```js
|
|
49
|
+
function _times_eleven({ params }) {
|
|
50
|
+
return params.number * 11;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default _times_eleven;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
###### The _eq operator:
|
|
57
|
+
```js
|
|
58
|
+
import { type } from '@lowdefy/helpers';
|
|
59
|
+
|
|
60
|
+
function _eq({ params }) {
|
|
61
|
+
if (!type.isArray(params)) {
|
|
62
|
+
throw new Error('_eq takes an array type as input.');
|
|
63
|
+
}
|
|
64
|
+
if (params.length !== 2) {
|
|
65
|
+
throw new Error('_eq takes an array of length 2 as input.');
|
|
66
|
+
}
|
|
67
|
+
return params[0] === params[1];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default _eq;
|
|
71
|
+
```
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Testing Authentication
|
|
2
|
+
|
|
3
|
+
When your app uses authentication, e2e tests need a way to set user sessions without going through a real login flow. The `@lowdefy/e2e-utils` package provides cookie-based user injection that works with Lowdefy's auth system.
|
|
4
|
+
|
|
5
|
+
When you run e2e tests, a special e2e server is used instead of the production server. This server reads the test user from a browser cookie instead of using the Auth.js engine, so your tests can set any user object and have it work with Lowdefy's page protection and role-based access control.
|
|
6
|
+
|
|
7
|
+
## Setting a default user
|
|
8
|
+
|
|
9
|
+
Define a default test user in your `mocks.yaml` file. This user is applied to every test automatically:
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
# e2e/mocks.yaml
|
|
13
|
+
user:
|
|
14
|
+
name: Test User
|
|
15
|
+
email: test@example.com
|
|
16
|
+
roles:
|
|
17
|
+
- admin
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The user object maps directly to `lowdefy.user` with no transforms. Whatever you set here is exactly what your app receives.
|
|
21
|
+
|
|
22
|
+
> Your app must have <code>auth</code> configured in <code>lowdefy.yaml</code> for page protection to work. The e2e server respects the same <code>pages.public</code>, <code>pages.protected</code>, and <code>pages.roles</code> settings as the production server.
|
|
23
|
+
|
|
24
|
+
## Setting users per test
|
|
25
|
+
|
|
26
|
+
Use `ldf.user()` to set or change the user for a specific test:
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
import { test } from './fixtures.js';
|
|
30
|
+
|
|
31
|
+
test('admin can access dashboard', async ({ ldf }) => {
|
|
32
|
+
await ldf.user({ name: 'Admin', roles: ['admin'] });
|
|
33
|
+
await ldf.goto('/dashboard');
|
|
34
|
+
await ldf.url().expect.toBe('/dashboard');
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
If you have a default user in `mocks.yaml`, calling `ldf.user()` overrides it for that test only. Other tests still use the default.
|
|
39
|
+
|
|
40
|
+
## Testing without a user
|
|
41
|
+
|
|
42
|
+
Clear the user with `ldf.user(null)` to test unauthenticated access:
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
test('unauthenticated user is redirected to login', async ({ ldf }) => {
|
|
46
|
+
await ldf.user(null);
|
|
47
|
+
await ldf.goto('/dashboard');
|
|
48
|
+
await ldf.url().expect.toBe('/login');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('public page loads without login', async ({ ldf }) => {
|
|
52
|
+
await ldf.user(null);
|
|
53
|
+
await ldf.goto('/login');
|
|
54
|
+
await ldf.url().expect.toBe('/login');
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
When no user is set and the requested page is protected, the server redirects to the 404 page. If the 404 page has logic to redirect to a login page, the user will be redirected there.
|
|
59
|
+
|
|
60
|
+
## Testing role-based access
|
|
61
|
+
|
|
62
|
+
Set specific roles on the user to test role-based page protection:
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
test('viewer cannot access admin page', async ({ ldf }) => {
|
|
66
|
+
await ldf.user({ name: 'Viewer', roles: ['viewer'] });
|
|
67
|
+
await ldf.goto('/admin-settings');
|
|
68
|
+
// Redirects because user lacks 'admin' role
|
|
69
|
+
await ldf.url().expect.toBe('/login');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('admin can access admin page', async ({ ldf }) => {
|
|
73
|
+
await ldf.user({ name: 'Admin', roles: ['admin'] });
|
|
74
|
+
await ldf.goto('/admin-settings');
|
|
75
|
+
await ldf.url().expect.toBe('/admin-settings');
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Complete example
|
|
80
|
+
|
|
81
|
+
Here is a full test file that covers common authentication scenarios:
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
import { test } from './fixtures.js';
|
|
85
|
+
|
|
86
|
+
test('protected page loads with authenticated user', async ({ ldf }) => {
|
|
87
|
+
// Default user from mocks.yaml is used (has admin role)
|
|
88
|
+
await ldf.goto('/home');
|
|
89
|
+
await ldf.url().expect.toBe('/home');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('public page loads without user', async ({ ldf }) => {
|
|
93
|
+
await ldf.user(null);
|
|
94
|
+
await ldf.goto('/login');
|
|
95
|
+
await ldf.url().expect.toBe('/login');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('protected page redirects without user', async ({ ldf }) => {
|
|
99
|
+
await ldf.user(null);
|
|
100
|
+
await ldf.goto('/home');
|
|
101
|
+
await ldf.url().expect.toBe('/login');
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Limitations
|
|
106
|
+
|
|
107
|
+
- **Sign-in and sign-out are not supported.** If your app uses the Login or Logout actions, the e2e server will throw an error. Test login flows by setting the user directly with `ldf.user()` instead.
|
|
108
|
+
- **No session callbacks.** The user object is passed through as-is. If your production app transforms the session via callbacks (for example, mapping `userFields`), those transforms do not run in e2e tests.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
The `playwright.config.js` file in your `e2e/` directory controls how tests are run. The `@lowdefy/e2e-utils/config` module provides helper functions that set up sensible defaults for Lowdefy apps.
|
|
4
|
+
|
|
5
|
+
## Default configuration
|
|
6
|
+
|
|
7
|
+
The simplest configuration uses all defaults:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
import { createConfig } from '@lowdefy/e2e-utils/config';
|
|
11
|
+
|
|
12
|
+
export default createConfig();
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This will:
|
|
16
|
+
- Look for your Lowdefy app in the current directory
|
|
17
|
+
- Build and start on port 3000
|
|
18
|
+
- Run tests from the `e2e/` directory matching `**/*.spec.js`
|
|
19
|
+
- Take screenshots only on failure
|
|
20
|
+
|
|
21
|
+
## Configuration options
|
|
22
|
+
|
|
23
|
+
| Option | Type | Default | Description |
|
|
24
|
+
|--------|------|---------|-------------|
|
|
25
|
+
| `appDir` | string | `'./'` | Path to your Lowdefy app root |
|
|
26
|
+
| `buildDir` | string | `'.lowdefy/server/build'` | Build output directory (relative to appDir) |
|
|
27
|
+
| `mocksFile` | string | `'e2e/mocks.yaml'` | Path to static mocks file (relative to appDir) |
|
|
28
|
+
| `port` | number | `3000` | Port for the test server |
|
|
29
|
+
| `testDir` | string | `'e2e'` | Directory containing test files |
|
|
30
|
+
| `testMatch` | string | `'**/*.spec.js'` | Glob pattern for test files |
|
|
31
|
+
| `timeout` | number | `180000` | Build + server start timeout in milliseconds (3 minutes) |
|
|
32
|
+
| `screenshot` | string | `'only-on-failure'` | When to take screenshots: `'off'`, `'on'`, or `'only-on-failure'` |
|
|
33
|
+
| `outputDir` | string | `'test-results'` | Directory for test artifacts |
|
|
34
|
+
|
|
35
|
+
###### Custom port and timeout:
|
|
36
|
+
```javascript
|
|
37
|
+
import { createConfig } from '@lowdefy/e2e-utils/config';
|
|
38
|
+
|
|
39
|
+
export default createConfig({
|
|
40
|
+
port: 3001,
|
|
41
|
+
timeout: 300000, // 5 minutes for slow builds
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Multi-app configuration
|
|
46
|
+
|
|
47
|
+
If your project has multiple Lowdefy apps (for example, in a monorepo), use `createMultiAppConfig`:
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
import { createMultiAppConfig } from '@lowdefy/e2e-utils/config';
|
|
51
|
+
|
|
52
|
+
export default createMultiAppConfig({
|
|
53
|
+
apps: [
|
|
54
|
+
{ name: 'admin', appDir: './apps/admin', port: 3001 },
|
|
55
|
+
{ name: 'customer', appDir: './apps/customer', port: 3002 },
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Each app gets its own Playwright project and web server. Tests for each app should be placed in subdirectories matching the app name (for example, `e2e/admin/` and `e2e/customer/`).
|
|
61
|
+
|
|
62
|
+
## Extending the configuration
|
|
63
|
+
|
|
64
|
+
Since `createConfig` returns a standard Playwright configuration object, you can extend it with any Playwright options:
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
import { createConfig } from '@lowdefy/e2e-utils/config';
|
|
68
|
+
|
|
69
|
+
const base = createConfig({ port: 3000 });
|
|
70
|
+
|
|
71
|
+
export default {
|
|
72
|
+
...base,
|
|
73
|
+
retries: 2,
|
|
74
|
+
projects: [
|
|
75
|
+
...base.projects,
|
|
76
|
+
{
|
|
77
|
+
name: 'firefox',
|
|
78
|
+
use: { browserName: 'firefox' },
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Environment variables
|
|
85
|
+
|
|
86
|
+
The configuration sets these environment variables automatically:
|
|
87
|
+
|
|
88
|
+
| Variable | Purpose |
|
|
89
|
+
|----------|---------|
|
|
90
|
+
| `LOWDEFY_BUILD_DIR` | Absolute path to build artifacts — used by test fixtures to generate the block manifest |
|
|
91
|
+
| `LOWDEFY_E2E_MOCKS_FILE` | Absolute path to `mocks.yaml` — used by test fixtures to load static mocks |
|
|
92
|
+
|
|
93
|
+
## Assertion timeouts
|
|
94
|
+
|
|
95
|
+
Assertion methods accept an optional `{ timeout }` parameter. The defaults are:
|
|
96
|
+
|
|
97
|
+
| Assertion type | Default timeout |
|
|
98
|
+
|---------------|----------------|
|
|
99
|
+
| Request assertions (`toFinish`, `toHaveResponse`, `toHavePayload`) | 30 seconds |
|
|
100
|
+
| State assertions (`toBe`) | 5 seconds |
|
|
101
|
+
| URL assertions (`toBe`, `toMatch`) | 5 seconds |
|
|
102
|
+
| Validation assertions (`validationError`, etc.) | 5 seconds |
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
// Override timeout for a slow request
|
|
106
|
+
await ldf.request('generate_report').expect.toFinish({ timeout: 60000 });
|
|
107
|
+
```
|