@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,144 @@
|
|
|
1
|
+
# Menus
|
|
2
|
+
|
|
3
|
+
> The menu items are filtered according to pages the user is authorized to see. This can be controlled by making pages public or private, or by assigning roles to authenticated users. See <a href = "/protected-pages-apis">protected pages and APIs</a> and <a href = "/roles">roles</a> for more on how to set this up.
|
|
4
|
+
|
|
5
|
+
Menu objects describe links to pages, within the app or external. Menu lists are filtered to only show pages that the user is authorized to see as a result of public, private or role based access controlled (RBAC) configuration. Blocks such as [PageSiderMenu](/PageSiderMenu) render menu links. If no menu defined, a default menu is created, containing links to all pages defined in the app.
|
|
6
|
+
|
|
7
|
+
By default blocks using a menu, like `PageSiderMenu`, will use the menu with `id: default`. This means that if you create a menu object with `id: default`, this will be used unless another menu is configured and referenced under the block properties, allowing for page specific menu configurations.
|
|
8
|
+
|
|
9
|
+
More than one menu can be configured in an app. As an example, this can be used when two logically different sections in the app need different menus.
|
|
10
|
+
|
|
11
|
+
The menu objects can be referenced by using the [`_menu`](/_menu) operator.
|
|
12
|
+
|
|
13
|
+
The schema for a menu object is:
|
|
14
|
+
|
|
15
|
+
- `id: string`: __Required__ - A identifier for the menu. If it is `default`, it will be used as default by blocks, like [PageHeaderMenu](/PageHeaderMenu), which display a menu.
|
|
16
|
+
- `links: object[]`: An array of `MenuLink` or `MenuGroup` objects that form the links in the menu. `MenuGroups` can be two levels deep.
|
|
17
|
+
|
|
18
|
+
The schema for a `MenuLink` is:
|
|
19
|
+
- `id: string`: __Required__ - A identifier for the link unique to the menu.
|
|
20
|
+
- `type: string`: __Required__ - The type should be `MenuLink`.
|
|
21
|
+
- `pageId: string`: The id of the page to link to. Used as the menu item title if no title is provided.
|
|
22
|
+
- `url: string`: An external url to link to.
|
|
23
|
+
- `class: string | array | object`: CSS classes (including Tailwind utilities) for the item. Flat applies to the item wrapper; use an object with dot-prefixed slot keys (`.element`, `.icon`, `.label`) to target specific parts.
|
|
24
|
+
- `style: object`: CSS styles for the item. Flat applies to the wrapper; use dot-prefixed slot keys (`.element`, `.icon`, `.label`) to target specific parts.
|
|
25
|
+
- `properties: object`: The properties of the menu link. These are:
|
|
26
|
+
- `title: string`: The title to display for the link.
|
|
27
|
+
- `icon: string | object`: The name of a [React Icon](https://react-icons.github.io/react-icons/search) or properties of an Icon block to use as the icon for the link.
|
|
28
|
+
- `danger: boolean`: Apply danger style (switches the item onto antd's `dangerItem*` token set).
|
|
29
|
+
- `disabled: boolean`: Disable the item — blocks clicks and applies a greyed style.
|
|
30
|
+
- `tooltip: string`: Tooltip text shown when the menu is collapsed.
|
|
31
|
+
- `extra: string`: Free-form right-aligned label on the item (e.g. `beta`, `soon`). For real keyboard shortcuts use `shortcut`, which both renders a kbd badge AND wires the key handler. When both are set, `shortcut` sits to the far right of `extra`.
|
|
32
|
+
- `shortcut: string`: Keyboard shortcut for this menu item. Renders a kbd badge floated to the far right of the item AND wires the key handler (fires `onSelect` when pressed). Use `mod` for Cmd/Ctrl. Supports sequences like `g s` and multi-key combos like `mod+shift+q`.
|
|
33
|
+
|
|
34
|
+
The schema for a `MenuGroup` is:
|
|
35
|
+
- `id: string`: __Required__ - A identifier for the group unique to the menu.
|
|
36
|
+
- `type: string`: __Required__ - The type should be `MenuGroup`.
|
|
37
|
+
- `class: string | array | object`: CSS classes for the group's title row. Use the `.popup` slot key (object form) to style the floating SubMenu popup.
|
|
38
|
+
- `style: object`: CSS styles for the group's title row. Use dot-prefixed slot keys to target specific parts.
|
|
39
|
+
- `properties: object`: The properties of the menu group. These are:
|
|
40
|
+
- `title: string`: The title to display for the group.
|
|
41
|
+
- `icon: string | object`: The name of a [React Icon](https://react-icons.github.io/react-icons/search) or properties of an Icon block to use as the icon for the group.
|
|
42
|
+
- `disabled: boolean`: Disable the group — blocks expansion and applies a greyed style.
|
|
43
|
+
- `tooltip: string`: Tooltip text shown when the menu is collapsed.
|
|
44
|
+
- `links: object[]`: An array of `MenuLink` or `MenuGroup` objects that should be grouped together in the group.
|
|
45
|
+
|
|
46
|
+
The schema for a `MenuDivider` is:
|
|
47
|
+
- `id: string`: __Required__ - A identifier for the divider unique to the menu.
|
|
48
|
+
- `type: string`: __Required__ - The type should be `MenuDivider`.
|
|
49
|
+
- `class: string | array | object`: CSS classes for the divider line.
|
|
50
|
+
- `style: object`: CSS styles for the divider line.
|
|
51
|
+
- `properties: object`: The properties of the divider. These are:
|
|
52
|
+
- `dashed: boolean`: Render the divider as a dashed line.
|
|
53
|
+
|
|
54
|
+
###### Menus example:
|
|
55
|
+
```yaml
|
|
56
|
+
lowdefy: LOWDEFY_VERSION
|
|
57
|
+
menus:
|
|
58
|
+
- id: default
|
|
59
|
+
links:
|
|
60
|
+
- id: page1
|
|
61
|
+
type: MenuLink
|
|
62
|
+
pageId: page1
|
|
63
|
+
properties:
|
|
64
|
+
title: Page 1
|
|
65
|
+
icon: AiOutlineFile
|
|
66
|
+
- id: top-group
|
|
67
|
+
properties:
|
|
68
|
+
title: Group
|
|
69
|
+
icon: AiOutlineGroup
|
|
70
|
+
links:
|
|
71
|
+
- id: page2
|
|
72
|
+
type: MenuLink
|
|
73
|
+
pageId: page2 # pageId will be used as link title
|
|
74
|
+
- id: external
|
|
75
|
+
type: MenuLink
|
|
76
|
+
url: https://external.com
|
|
77
|
+
properties:
|
|
78
|
+
title: External site
|
|
79
|
+
- id: nested-group
|
|
80
|
+
type: MenuGroup
|
|
81
|
+
properties:
|
|
82
|
+
title: Nested Group
|
|
83
|
+
links:
|
|
84
|
+
- id: page3
|
|
85
|
+
type: MenuLink
|
|
86
|
+
pageId: page3
|
|
87
|
+
properties:
|
|
88
|
+
title: Page 3
|
|
89
|
+
- id: page-1-and-3
|
|
90
|
+
links:
|
|
91
|
+
- id: page1
|
|
92
|
+
type: MenuLink
|
|
93
|
+
pageId: page1
|
|
94
|
+
properties:
|
|
95
|
+
title: Page 1
|
|
96
|
+
icon: AiOutlineFile
|
|
97
|
+
- id: page3
|
|
98
|
+
type: MenuLink
|
|
99
|
+
pageId: page3
|
|
100
|
+
properties:
|
|
101
|
+
title: Page 3
|
|
102
|
+
icon: AiOutlineControl
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
We can make use of [protected pages and APIs](/protected-pages-apis) and [roles](/roles) to filter the menu items according to the pages that the user has access to.
|
|
106
|
+
|
|
107
|
+
###### lowdefy.yaml
|
|
108
|
+
```yaml
|
|
109
|
+
lowdefy: LOWDEFY_VERSION
|
|
110
|
+
|
|
111
|
+
auth:
|
|
112
|
+
# ...
|
|
113
|
+
pages:
|
|
114
|
+
protected: true
|
|
115
|
+
public:
|
|
116
|
+
- login
|
|
117
|
+
- '404'
|
|
118
|
+
roles:
|
|
119
|
+
admin:
|
|
120
|
+
- page2
|
|
121
|
+
- page3
|
|
122
|
+
user:
|
|
123
|
+
- page1
|
|
124
|
+
|
|
125
|
+
menus:
|
|
126
|
+
- id: default
|
|
127
|
+
links:
|
|
128
|
+
- id: page1
|
|
129
|
+
type: MenuLink
|
|
130
|
+
pageId: page1
|
|
131
|
+
properties:
|
|
132
|
+
title: Page 1
|
|
133
|
+
icon: AiOutlineFile
|
|
134
|
+
- id: page2
|
|
135
|
+
type: MenuLink
|
|
136
|
+
pageId: page2 # pageId will be used as link title
|
|
137
|
+
- id: page3
|
|
138
|
+
type: MenuLink
|
|
139
|
+
pageId: page3
|
|
140
|
+
properties:
|
|
141
|
+
title: Page 3
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
In the example above, public users can only view the `login` and `404` pages, all other pages require the user to be logged in. If the authorized user had been given only the `user` role, they will only be able to see `page1`. In order to see `page2` and `page3`, the authorized user will need to have the `admin` role assigned to them. The menu will be filtered accordingly and will only contain the menu items that the user has access to.
|
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
# Writing Modules
|
|
2
|
+
|
|
3
|
+
This guide covers how to create, test, and publish Lowdefy modules.
|
|
4
|
+
|
|
5
|
+
## Module Project Structure
|
|
6
|
+
|
|
7
|
+
A module is a directory containing a `module.lowdefy.yaml` manifest and supporting config files:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
my-module/
|
|
11
|
+
├── module.lowdefy.yaml # Module manifest (entry point)
|
|
12
|
+
├── pages/
|
|
13
|
+
│ ├── users-list.yaml
|
|
14
|
+
│ ├── user-edit.yaml
|
|
15
|
+
│ └── user-invite.yaml
|
|
16
|
+
├── connections/
|
|
17
|
+
│ └── users-db.yaml
|
|
18
|
+
├── api/
|
|
19
|
+
│ └── invite-user.yaml
|
|
20
|
+
├── components/
|
|
21
|
+
│ ├── user-avatar.yaml
|
|
22
|
+
│ └── user-search.yaml
|
|
23
|
+
└── menus.yaml
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
For monorepos with multiple modules:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
my-modules/
|
|
30
|
+
├── user-admin/
|
|
31
|
+
│ ├── module.lowdefy.yaml
|
|
32
|
+
│ ├── pages/
|
|
33
|
+
│ └── connections/
|
|
34
|
+
├── notifications/
|
|
35
|
+
│ ├── module.lowdefy.yaml
|
|
36
|
+
│ ├── pages/
|
|
37
|
+
│ └── components/
|
|
38
|
+
└── shared/ # Shared files (not a module)
|
|
39
|
+
├── page-layout.yaml
|
|
40
|
+
└── validate-email.yaml
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Modules in a monorepo can reference shared files outside their own directory with `../`. See [Ref Path Resolution](#ref-path-resolution).
|
|
44
|
+
|
|
45
|
+
### Ref Path Resolution
|
|
46
|
+
|
|
47
|
+
`_ref` paths inside a module are relative to the module root (the directory containing `module.lowdefy.yaml`), not to the file doing the `_ref`. The same path works from any file in the module, regardless of nesting depth:
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
# module root: my-module/
|
|
51
|
+
# This ref works the same from:
|
|
52
|
+
# my-module/module.lowdefy.yaml
|
|
53
|
+
# my-module/pages/users-list.yaml
|
|
54
|
+
# my-module/pages/admin/users-edit.yaml
|
|
55
|
+
header:
|
|
56
|
+
_ref: components/page-header.yaml # always resolves to my-module/components/page-header.yaml
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Use `../` only to reference files outside the module — typically shared files in a monorepo:
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
# module root: my-modules/user-admin/
|
|
63
|
+
# _ref resolves to my-modules/shared/page-layout.yaml
|
|
64
|
+
layout:
|
|
65
|
+
_ref: ../shared/page-layout.yaml
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The build prevents `../` from escaping the package root — the git repo root for local (`file:`) modules (falling back to the module directory itself if not in a git repo), or the fetched repo root for `github:` modules. Breaking out throws a config error.
|
|
69
|
+
|
|
70
|
+
## How Modules Stay Scoped and Composable
|
|
71
|
+
|
|
72
|
+
Three mechanisms keep modules working at scale. **Auto-scoping** prefixes every page, connection, API endpoint, notification, and menu item ID with the entry's `id`, so module authors write plain IDs and installing the same module twice under different entry IDs produces no collisions. **`_module.var`** lets any file in the module read the entry's vars at any depth, without the app threading values through nested `_ref`s. **Typed exports** declared in `exports:` mark which pages, components, endpoints, connections, and menus are public, so internal files stay internal and renaming one does not break consumers.
|
|
73
|
+
|
|
74
|
+
## Writing module.lowdefy.yaml
|
|
75
|
+
|
|
76
|
+
The manifest declares the module's interface and contents:
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
name: User Admin
|
|
80
|
+
description: User administration — list, edit, invite users
|
|
81
|
+
|
|
82
|
+
vars:
|
|
83
|
+
roles:
|
|
84
|
+
type: array
|
|
85
|
+
required: true
|
|
86
|
+
description: List of available user roles
|
|
87
|
+
collection:
|
|
88
|
+
type: string
|
|
89
|
+
default: users
|
|
90
|
+
description: MongoDB collection name
|
|
91
|
+
components:
|
|
92
|
+
type: object
|
|
93
|
+
description: Component overrides (table_columns, form_fields)
|
|
94
|
+
request_stages:
|
|
95
|
+
type: object
|
|
96
|
+
description: Request pipeline stage overrides
|
|
97
|
+
|
|
98
|
+
connections:
|
|
99
|
+
- _ref: connections/users-db.yaml
|
|
100
|
+
|
|
101
|
+
pages:
|
|
102
|
+
- _ref: pages/users-list.yaml
|
|
103
|
+
- _ref: pages/user-edit.yaml
|
|
104
|
+
- _ref: pages/user-invite.yaml
|
|
105
|
+
|
|
106
|
+
api:
|
|
107
|
+
- _ref: api/invite-user.yaml
|
|
108
|
+
|
|
109
|
+
notifications:
|
|
110
|
+
- _ref: notifications/invite-user.yaml
|
|
111
|
+
|
|
112
|
+
components:
|
|
113
|
+
- id: user-avatar
|
|
114
|
+
component:
|
|
115
|
+
_ref: components/user-avatar.yaml
|
|
116
|
+
- id: user-search
|
|
117
|
+
component:
|
|
118
|
+
_ref: components/user-search.yaml
|
|
119
|
+
|
|
120
|
+
menus:
|
|
121
|
+
- _ref: menus.yaml
|
|
122
|
+
|
|
123
|
+
# Default Lowdefy plugins (blocks-antd, blocks-basic, etc.) do not
|
|
124
|
+
# need to be declared — only declare non-default plugins.
|
|
125
|
+
plugins:
|
|
126
|
+
- name: "@lowdefy/blocks-aggrid"
|
|
127
|
+
version: "^4"
|
|
128
|
+
|
|
129
|
+
secrets:
|
|
130
|
+
- name: MONGODB
|
|
131
|
+
description: MongoDB connection URI
|
|
132
|
+
- name: SENDGRID_API_KEY
|
|
133
|
+
description: SendGrid API key for sending invitations
|
|
134
|
+
required: false
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Declaring Vars
|
|
138
|
+
|
|
139
|
+
The `vars` section declares what configuration the module accepts. Each variable can specify:
|
|
140
|
+
- `type` — Expected type (`string`, `number`, `boolean`, `array`, `object`).
|
|
141
|
+
- `required` — Whether the consuming app must provide this variable. Defaults to `false`.
|
|
142
|
+
- `default` — Default value used when the app does not provide one.
|
|
143
|
+
- `description` — Documentation for the variable's purpose.
|
|
144
|
+
- `properties` — For `object` vars, declares nested keys with their own type, default, required, and description. Consumer values are merged at the property level — only overridden keys change.
|
|
145
|
+
|
|
146
|
+
Defaults are expressions. They can be literals, or operator trees — `_ref`, `_module.var`, `_build.*`, or any combination. The build resolves a default lazily, only when `_module.var` reads the var and the consumer did not provide a value. Vars with `properties` merge at the property level: the consumer overrides only the keys they provide, and the rest resolve their own defaults independently.
|
|
147
|
+
|
|
148
|
+
Unused vars are never read, so their defaults never resolve and their declared `type` is never checked.
|
|
149
|
+
|
|
150
|
+
```yaml
|
|
151
|
+
vars:
|
|
152
|
+
collection:
|
|
153
|
+
type: string
|
|
154
|
+
default: contacts
|
|
155
|
+
components:
|
|
156
|
+
type: object
|
|
157
|
+
properties:
|
|
158
|
+
table:
|
|
159
|
+
type: boolean
|
|
160
|
+
default: true
|
|
161
|
+
filter:
|
|
162
|
+
type: boolean
|
|
163
|
+
default: true
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
If the consumer provides `components: { filter: false }`, the effective value is `{ table: true, filter: false }`.
|
|
167
|
+
|
|
168
|
+
### Dynamic Defaults
|
|
169
|
+
|
|
170
|
+
Defaults are expressions. A `default:` value can be a literal, a `_ref` to a file, a `_module.var` reference to another var, or any `_build.*` tree. The build resolves a default on read — only when `_module.var` accesses the var and the consumer did not provide a value.
|
|
171
|
+
|
|
172
|
+
The two most common patterns are loading a default from a file and deriving one var from another:
|
|
173
|
+
|
|
174
|
+
```yaml
|
|
175
|
+
vars:
|
|
176
|
+
table_columns:
|
|
177
|
+
default:
|
|
178
|
+
_ref: defaults/table-columns.yaml
|
|
179
|
+
page_title:
|
|
180
|
+
default:
|
|
181
|
+
_module.var: label_plural
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`_ref` lets the module ship a structured default (a block tree, a column schema, a default request body) as its own file. `_module.var` inside a default turns one consumer-provided value into several — set `label_plural: "Users"` and `page_title` derives from it unless the consumer overrides it directly.
|
|
185
|
+
|
|
186
|
+
Defaults scale up to full operator trees and namespace-level merging:
|
|
187
|
+
|
|
188
|
+
```yaml
|
|
189
|
+
vars:
|
|
190
|
+
show_title:
|
|
191
|
+
type: boolean
|
|
192
|
+
default: true
|
|
193
|
+
|
|
194
|
+
# Default built from another var
|
|
195
|
+
page_title:
|
|
196
|
+
type: string
|
|
197
|
+
default:
|
|
198
|
+
_build.if:
|
|
199
|
+
- _module.var: show_title
|
|
200
|
+
- 'Contact Form'
|
|
201
|
+
- null
|
|
202
|
+
|
|
203
|
+
# Namespace var — each property's default resolves on its own
|
|
204
|
+
components:
|
|
205
|
+
type: object
|
|
206
|
+
properties:
|
|
207
|
+
form_fields:
|
|
208
|
+
type: array
|
|
209
|
+
default:
|
|
210
|
+
_ref: components/form_contact.yaml
|
|
211
|
+
show_header:
|
|
212
|
+
type: boolean
|
|
213
|
+
default:
|
|
214
|
+
_module.var: show_title
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
When a consumer passes `components: { show_header: false }`, `show_header` keeps the consumer value and the build still resolves the `form_fields` default — property-level merging plus lazy resolution combine without any extra work from the author.
|
|
218
|
+
|
|
219
|
+
A default that reads another var via `_module.var` triggers that var's resolution first. The build walks the dependency graph on demand, caching results per module entry. Circular `_module.var` graphs between defaults have no natural terminator — the build stack-overflows. Keep default graphs acyclic.
|
|
220
|
+
|
|
221
|
+
## Using _module.var
|
|
222
|
+
|
|
223
|
+
The [`_module.var`](/_module) operator reads module entry variables — it returns the consumer value when provided, or resolves the manifest default on read. It takes a string argument and works at any depth within module files, without threading vars through nested `_ref`s:
|
|
224
|
+
|
|
225
|
+
```yaml
|
|
226
|
+
# Any file in the module, at any nesting depth
|
|
227
|
+
collection:
|
|
228
|
+
_module.var: collection
|
|
229
|
+
|
|
230
|
+
# Nested path access (dot notation)
|
|
231
|
+
show_filter:
|
|
232
|
+
_module.var: components.filter
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`_var` continues to work for normal `_ref`-level vars (local composition between files within the module). `_module.var` is specifically for the vars passed by the app developer.
|
|
236
|
+
|
|
237
|
+
## Using ID Operators
|
|
238
|
+
|
|
239
|
+
Module authors use [`_module.pageId`](/_module), [`_module.connectionId`](/_module), [`_module.endpointId`](/_module), and [`_module.notificationId`](/_module) to reference sibling resources. These resolve to the fully scoped ID at build time:
|
|
240
|
+
|
|
241
|
+
```yaml
|
|
242
|
+
# Link to a sibling page
|
|
243
|
+
- id: link_to_edit
|
|
244
|
+
type: Link
|
|
245
|
+
params:
|
|
246
|
+
pageId:
|
|
247
|
+
_module.pageId: user-edit
|
|
248
|
+
|
|
249
|
+
# Reference a module connection
|
|
250
|
+
connectionId:
|
|
251
|
+
_module.connectionId: users-db
|
|
252
|
+
|
|
253
|
+
# Call a module API endpoint
|
|
254
|
+
- id: call_invite
|
|
255
|
+
type: CallAPI
|
|
256
|
+
params:
|
|
257
|
+
endpointId:
|
|
258
|
+
_module.endpointId: invite-user
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Use [`_module.id`](/_module) for the raw module entry ID:
|
|
262
|
+
|
|
263
|
+
```yaml
|
|
264
|
+
label:
|
|
265
|
+
_string.concat:
|
|
266
|
+
- _module.id: true
|
|
267
|
+
- " - User Admin"
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Exposing Components
|
|
271
|
+
|
|
272
|
+
Components are reusable config fragments that consuming apps can `_ref` into any page. Declare them in the manifest:
|
|
273
|
+
|
|
274
|
+
```yaml
|
|
275
|
+
components:
|
|
276
|
+
- id: user-avatar
|
|
277
|
+
component:
|
|
278
|
+
_ref: components/user-avatar.yaml
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Components can use both `_module.var` (module-level config) and `_var` (per-ref vars passed at the point of inclusion):
|
|
282
|
+
|
|
283
|
+
```yaml
|
|
284
|
+
# components/contact-selector.yaml
|
|
285
|
+
id:
|
|
286
|
+
_var:
|
|
287
|
+
key: field_id
|
|
288
|
+
default: contact
|
|
289
|
+
type: Selector
|
|
290
|
+
properties:
|
|
291
|
+
label:
|
|
292
|
+
_module.var: contact_label
|
|
293
|
+
options:
|
|
294
|
+
_request: get-contacts.contacts
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
The consuming app includes it:
|
|
298
|
+
|
|
299
|
+
```yaml
|
|
300
|
+
blocks:
|
|
301
|
+
- _ref:
|
|
302
|
+
module: crm
|
|
303
|
+
component: contact-selector
|
|
304
|
+
vars:
|
|
305
|
+
field_id: primary_contact
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Declaring Dependencies
|
|
309
|
+
|
|
310
|
+
When your module needs resources from another module — pages to link to, components to embed, APIs to call — declare those dependencies in `module.lowdefy.yaml`:
|
|
311
|
+
|
|
312
|
+
```yaml
|
|
313
|
+
dependencies:
|
|
314
|
+
- id: contacts
|
|
315
|
+
description: Contact management (selector component, detail pages)
|
|
316
|
+
- id: layout
|
|
317
|
+
description: Page layout components
|
|
318
|
+
- id: events
|
|
319
|
+
description: Audit event logging
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Each dependency has an `id` (the abstract name you use in your module content) and a `description` (documents what kind of module is expected). The `id` is your choice — it's not tied to any specific entry name.
|
|
323
|
+
|
|
324
|
+
## Declaring Exports
|
|
325
|
+
|
|
326
|
+
Declare what your module exposes so the build can validate cross-module references:
|
|
327
|
+
|
|
328
|
+
```yaml
|
|
329
|
+
exports:
|
|
330
|
+
pages:
|
|
331
|
+
- id: company-list
|
|
332
|
+
- id: company-detail
|
|
333
|
+
components:
|
|
334
|
+
- id: company-selector
|
|
335
|
+
description: Dropdown selector for picking companies
|
|
336
|
+
menus:
|
|
337
|
+
- id: default
|
|
338
|
+
api:
|
|
339
|
+
- id: save-company
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Omit empty sections. The build validates that cross-module references target declared exports.
|
|
343
|
+
|
|
344
|
+
## Using Cross-Module References
|
|
345
|
+
|
|
346
|
+
### Linking to Pages in Another Module
|
|
347
|
+
|
|
348
|
+
Use the object form of `_module.pageId` with a `module` field:
|
|
349
|
+
|
|
350
|
+
```yaml
|
|
351
|
+
# Link to a page in the contacts dependency
|
|
352
|
+
- id: view-contact
|
|
353
|
+
type: Link
|
|
354
|
+
params:
|
|
355
|
+
pageId:
|
|
356
|
+
_module.pageId:
|
|
357
|
+
id: contact-detail
|
|
358
|
+
module: contacts
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
The string form (`_module.pageId: company-detail`) continues to reference the current module's own pages.
|
|
362
|
+
|
|
363
|
+
### Calling APIs in Another Module
|
|
364
|
+
|
|
365
|
+
```yaml
|
|
366
|
+
- id: log-event
|
|
367
|
+
type: CallAPI
|
|
368
|
+
params:
|
|
369
|
+
endpointId:
|
|
370
|
+
_module.endpointId:
|
|
371
|
+
id: log-event
|
|
372
|
+
module: events
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Embedding Components from Another Module
|
|
376
|
+
|
|
377
|
+
Use `_ref: { module, component }` where `module` is the abstract dependency name:
|
|
378
|
+
|
|
379
|
+
```yaml
|
|
380
|
+
blocks:
|
|
381
|
+
- _ref:
|
|
382
|
+
module: contacts
|
|
383
|
+
component: contact-selector
|
|
384
|
+
vars:
|
|
385
|
+
field_id: primary_contact
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
The embedded component resolves in its source module's context. Its `_module.var` and `_module.*Id` operators read from the source module's entry — not yours. Per-ref `_var` works for customizing the component at the point of inclusion.
|
|
389
|
+
|
|
390
|
+
### Including Menus from Another Module
|
|
391
|
+
|
|
392
|
+
Use `_build.array.concat` to combine module and local links:
|
|
393
|
+
|
|
394
|
+
```yaml
|
|
395
|
+
links:
|
|
396
|
+
_build.array.concat:
|
|
397
|
+
- _ref:
|
|
398
|
+
module: contacts
|
|
399
|
+
menu: default
|
|
400
|
+
- _ref: menus/company-links.yaml
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Cross-module `_ref` supports `component` and `menu` types only. Pages, connections, and APIs have dedicated ID operators.
|
|
404
|
+
|
|
405
|
+
## Menu Export Convention
|
|
406
|
+
|
|
407
|
+
Module menus should export only one level of links — flat MenuLink items, no nested MenuGroups. The app controls grouping. This keeps module menus composable: the app can wrap the links in any MenuGroup structure it needs.
|
|
408
|
+
|
|
409
|
+
```yaml
|
|
410
|
+
# menus.yaml — good: flat links only
|
|
411
|
+
id: default
|
|
412
|
+
links:
|
|
413
|
+
- id: users
|
|
414
|
+
type: MenuLink
|
|
415
|
+
pageId:
|
|
416
|
+
_module.pageId: users-list
|
|
417
|
+
properties:
|
|
418
|
+
title: Users
|
|
419
|
+
icon: AiOutlineUser
|
|
420
|
+
- id: invite
|
|
421
|
+
type: MenuLink
|
|
422
|
+
pageId:
|
|
423
|
+
_module.pageId: user-invite
|
|
424
|
+
properties:
|
|
425
|
+
title: Invite User
|
|
426
|
+
icon: AiOutlineUserAdd
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
The app wraps these in a MenuGroup:
|
|
430
|
+
|
|
431
|
+
```yaml
|
|
432
|
+
# lowdefy.yaml — app controls grouping
|
|
433
|
+
menus:
|
|
434
|
+
- id: default
|
|
435
|
+
links:
|
|
436
|
+
- id: user-admin
|
|
437
|
+
type: MenuGroup
|
|
438
|
+
properties:
|
|
439
|
+
title: User Admin
|
|
440
|
+
links:
|
|
441
|
+
_ref:
|
|
442
|
+
module: user-admin
|
|
443
|
+
menu: default
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## Data-Like Exports
|
|
447
|
+
|
|
448
|
+
Components can export any config — not just UI blocks. Enum maps, config templates, and schema fragments all work through the same `components` mechanism:
|
|
449
|
+
|
|
450
|
+
```yaml
|
|
451
|
+
# In module.lowdefy.yaml
|
|
452
|
+
vars:
|
|
453
|
+
event_types:
|
|
454
|
+
type: object
|
|
455
|
+
default: {}
|
|
456
|
+
change_stamp:
|
|
457
|
+
type: object
|
|
458
|
+
default:
|
|
459
|
+
timestamp:
|
|
460
|
+
_user: updated_at
|
|
461
|
+
|
|
462
|
+
components:
|
|
463
|
+
# Enum map merged with app additions
|
|
464
|
+
- id: event_types
|
|
465
|
+
component:
|
|
466
|
+
_build.object.assign:
|
|
467
|
+
- _ref: ../shared/enums/event_types.yaml
|
|
468
|
+
- _module.var: event_types
|
|
469
|
+
|
|
470
|
+
# Config template with var override
|
|
471
|
+
- id: change_stamp
|
|
472
|
+
component:
|
|
473
|
+
_module.var: change_stamp
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Consumers use the same `_ref: { module, component }` syntax. The `key` argument lets consumers extract nested values from the resolved content:
|
|
477
|
+
|
|
478
|
+
```yaml
|
|
479
|
+
# Get the full enum map
|
|
480
|
+
event_types:
|
|
481
|
+
_ref:
|
|
482
|
+
module: events
|
|
483
|
+
component: event_types
|
|
484
|
+
|
|
485
|
+
# Get a single event type's icon
|
|
486
|
+
icon:
|
|
487
|
+
_ref:
|
|
488
|
+
module: events
|
|
489
|
+
component: event_types
|
|
490
|
+
key: login.icon
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
## Declaring Plugin Dependencies
|
|
494
|
+
|
|
495
|
+
Modules declare required plugins with semver version constraints. The build validates these against the app's `plugins` array. Default Lowdefy plugins do not need to be declared — only declare non-default plugins your module requires:
|
|
496
|
+
|
|
497
|
+
```yaml
|
|
498
|
+
plugins:
|
|
499
|
+
- name: "@lowdefy/blocks-aggrid"
|
|
500
|
+
version: "^4"
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Use `^major` for major version pinning — the standard convention.
|
|
504
|
+
|
|
505
|
+
## Declaring Secrets
|
|
506
|
+
|
|
507
|
+
The `secrets` array is an allowlist. The module can only access secrets it explicitly declares:
|
|
508
|
+
|
|
509
|
+
```yaml
|
|
510
|
+
secrets:
|
|
511
|
+
- name: MONGODB
|
|
512
|
+
description: MongoDB connection URI
|
|
513
|
+
- name: SENDGRID_API_KEY
|
|
514
|
+
description: SendGrid API key
|
|
515
|
+
required: false
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
## Testing Locally
|
|
519
|
+
|
|
520
|
+
Create a test app that imports the module via local path:
|
|
521
|
+
|
|
522
|
+
```yaml
|
|
523
|
+
# test-app/lowdefy.yaml
|
|
524
|
+
lowdefy: LOWDEFY_VERSION
|
|
525
|
+
|
|
526
|
+
modules:
|
|
527
|
+
- id: user-admin
|
|
528
|
+
source: "file:.."
|
|
529
|
+
vars:
|
|
530
|
+
roles:
|
|
531
|
+
_ref: config/test-roles.yaml
|
|
532
|
+
collection: test_users
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Run `lowdefy dev` in the test-app directory. The `file:..` path reads from disk and picks up module changes on rebuild. Include the module multiple times with different vars to test multi-instance behaviour.
|
|
536
|
+
|
|
537
|
+
## Publishing to GitHub
|
|
538
|
+
|
|
539
|
+
Publish your module by pushing to a GitHub repository and creating tagged releases:
|
|
540
|
+
|
|
541
|
+
```bash
|
|
542
|
+
git tag v1.0.0
|
|
543
|
+
git push origin v1.0.0
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
Consumers reference the tag:
|
|
547
|
+
|
|
548
|
+
```yaml
|
|
549
|
+
source: "github:my-org/user-admin@v1.0.0"
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
For monorepos, the path specifies the subdirectory:
|
|
553
|
+
|
|
554
|
+
```yaml
|
|
555
|
+
source: "github:my-org/modules/user-admin@v1.0.0"
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
## Versioning Strategy
|
|
559
|
+
|
|
560
|
+
- **Tags** (e.g., `v1.0.0`) — Recommended for production. Immutable and cacheable.
|
|
561
|
+
- **Branches** (e.g., `main`) — Useful during development. Re-fetched on each build.
|
|
562
|
+
- **Commit SHAs** — Most precise. Immutable.
|
|
563
|
+
|
|
564
|
+
Follow [semantic versioning](https://semver.org/): increment the major version for breaking changes to the vars interface, minor for new features, patch for fixes.
|