Skip to content

Commit 475e4f1

Browse files
authored
Merge pull request #301 from retran/pr4-nanoflows-all
feat: full nanoflow support — CREATE, DROP, SHOW, DESCRIBE, DIFF, MERMAID, security, and agentic skill
2 parents 1613f08 + e9dfbc3 commit 475e4f1

92 files changed

Lines changed: 19329 additions & 10961 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/mendix/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Detailed syntax for each MDL document type:
1919
|-------|---------|----------|
2020
| [mdl-entities.md](mdl-entities.md) | Entity, attribute, association syntax | Creating domain models |
2121
| [write-microflows.md](write-microflows.md) | Microflow syntax reference | Writing microflow logic |
22+
| [write-nanoflows.md](write-nanoflows.md) | Nanoflow syntax reference | Writing client-side nanoflow logic |
2223
| [write-oql-queries.md](write-oql-queries.md) | OQL query syntax | Creating VIEW entities |
2324
| [create-page.md](create-page.md) | Page and widget syntax | Creating pages |
2425
| [fragments.md](fragments.md) | Fragment (reusable widget group) syntax | Reusing widget patterns across pages |

.claude/skills/mendix/create-page.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ actionbutton widgetName (caption: 'Caption', action: ACTION_TYPE [, buttonstyle:
194194
- `action: delete` - Delete object
195195
- `action: microflow Module.MicroflowName` - Call microflow
196196
- `action: microflow Module.MicroflowName(Param: $value)` - Call microflow with parameters
197+
- `action: nanoflow Module.NanoflowName` - Call nanoflow (client-side)
198+
- `action: nanoflow Module.NanoflowName(Param: $value)` - Call nanoflow with parameters
197199
- `action: show_page Module.PageName` - Navigate to page
198200
- `action: show_page Module.PageName(Param: $value)` - Navigate with parameters
199201
- `action: show_page Module.PageName($Param = $value)` - Also accepted (microflow-style)
@@ -341,6 +343,7 @@ column colActions (caption: 'Actions') {
341343
| `datasource: database from Module.Entity` | Direct database query |
342344
| `datasource: $Variable` | Variable bound (requires DATAVIEW parent with entity) |
343345
| `datasource: microflow Module.GetData()` | Microflow datasource |
346+
| `datasource: nanoflow Module.GetData()` | Nanoflow datasource (client-side, no server roundtrip) |
344347
| `datasource: selection widgetName` | Listen to selection from another widget |
345348
| `datasource: association path` | Retrieve by association from context (ByAssociation) |
346349
| `datasource: $currentObject/Module.Assoc` | Sugar for `association` — same semantics, reads more naturally |

.claude/skills/mendix/manage-security.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,21 @@ grant execute on microflow MyModule.ACT_Customer_Create to MyModule.User, MyModu
109109
revoke execute on microflow MyModule.ACT_Customer_Create from MyModule.User;
110110
```
111111

112+
### Nanoflow Access
113+
114+
```sql
115+
-- Grant execute access (same syntax as microflows)
116+
grant execute on nanoflow MyModule.NF_ValidateCart to MyModule.User, MyModule.Admin;
117+
118+
-- Revoke from specific roles
119+
revoke execute on nanoflow MyModule.NF_ValidateCart from MyModule.User;
120+
121+
-- Show current access
122+
show access on nanoflow MyModule.NF_ValidateCart;
123+
```
124+
125+
> **Note:** Security roles persist through DROP+CREATE of the same nanoflow name within a session (by design, for refactor-in-place workflows).
126+
112127
### Page Access
113128

114129
```sql

.claude/skills/mendix/validation-microflows.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Use this skill when:
1010
- Building conditional validation chains
1111
- Creating action microflows that call validation microflows
1212

13+
> **Nanoflow validation:** The same patterns apply to nanoflows (`create nanoflow` instead of `create microflow`). Use nanoflows for client-side validation when server roundtrips are unnecessary — validation feedback renders instantly without a network call.
14+
1315
## The Validation Pattern
1416

1517
Mendix validation follows a two-microflow pattern:
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Mendix Nanoflow Skill
2+
3+
This skill provides guidance for writing Mendix nanoflows in MDL syntax. Nanoflows share syntax with microflows but execute client-side with restricted capabilities.
4+
5+
## When to Use This Skill
6+
7+
Use this skill when:
8+
- Writing CREATE NANOFLOW statements
9+
- Debugging nanoflow validation errors
10+
- Understanding nanoflow restrictions vs microflows
11+
12+
## Key Differences from Microflows
13+
14+
| Aspect | Microflow | Nanoflow |
15+
|--------|-----------|----------|
16+
| **Execution** | Server-side | Client-side (browser/mobile) |
17+
| **Database access** | Full | No direct access |
18+
| **Transactions** | Supported | Not supported |
19+
| **Java actions** | Supported | Not supported |
20+
| **JavaScript actions** | Not supported | Supported |
21+
| **File downloads** | Supported | Not supported |
22+
| **Error handling** | Full `ON ERROR` blocks | Per-action `ON ERROR` supported; `ErrorEvent` (raise error) forbidden |
23+
| **Offline** | Not available | Available |
24+
25+
## Nanoflow Structure
26+
27+
```mdl
28+
/**
29+
* Nanoflow description
30+
*
31+
* @param $Parameter1 Description
32+
* @returns Description of return value
33+
*/
34+
CREATE [OR MODIFY] NANOFLOW Module.NAV_Name (
35+
$Parameter1: type
36+
)
37+
RETURNS ReturnType AS $Result
38+
FOLDER 'FolderPath'
39+
BEGIN
40+
-- Nanoflow logic here
41+
RETURN $Result;
42+
END;
43+
```
44+
45+
## Naming Convention
46+
47+
Nanoflow names use the `NAV_` prefix by convention:
48+
- `NAV_ValidateCart` — client-side validation
49+
- `NAV_ShowDetails` — page navigation
50+
- `NAV_ToggleFilter` — UI state toggle
51+
52+
## Supported Activities
53+
54+
### Object Operations (in-memory only)
55+
```mdl
56+
$Item = CREATE Sales.CartItem (Quantity = 1);
57+
CHANGE $Item (Quantity = $Item/Quantity + 1);
58+
```
59+
60+
### Calling Other Flows
61+
```mdl
62+
$Result = CALL NANOFLOW Sales.NAV_ValidateCart (Cart = $Cart);
63+
$ServerResult = CALL MICROFLOW Sales.ACT_SubmitOrder (Order = $Order);
64+
$JsResult = CALL JAVASCRIPT ACTION MyModule.MyJsAction (Param = $Value);
65+
```
66+
67+
### UI Activities
68+
```mdl
69+
SHOW PAGE Sales.CartDetail ($Cart = $Cart);
70+
CLOSE PAGE;
71+
VALIDATION FEEDBACK $Item/Quantity MESSAGE 'Quantity must be at least 1';
72+
```
73+
74+
### Logging and Variables
75+
```mdl
76+
LOG INFO 'Cart updated with ' + toString($ItemCount) + ' items';
77+
DECLARE $IsValid Boolean = true;
78+
SET $IsValid = false;
79+
```
80+
81+
### Control Flow
82+
```mdl
83+
IF $Cart/ItemCount = 0 THEN
84+
VALIDATION FEEDBACK $Cart/ItemCount MESSAGE 'Cart is empty';
85+
RETURN false;
86+
ELSE
87+
SHOW PAGE Sales.Checkout ($Cart = $Cart);
88+
RETURN true;
89+
END IF;
90+
```
91+
92+
## Disallowed Activities
93+
94+
These will produce validation errors (12 case branches covering 22 action types in `nanoflow_validation.go`):
95+
- `ErrorEvent` / `RAISE ERROR` — not available in nanoflows
96+
- `CALL JAVA ACTION` — Java actions cannot run client-side
97+
- `EXECUTE DATABASE QUERY` — direct SQL requires server
98+
- `CALL EXTERNAL ACTION` — external actions are server-side
99+
- `SHOW HOME PAGE` — home page navigation is server-side
100+
- `CALL REST SERVICE` / `SEND REST REQUEST` — REST calls are server-side
101+
- `IMPORT FROM MAPPING` / `EXPORT TO MAPPING` — mapping operations are server-side
102+
- `TRANSFORM JSON` — JSON transformations are server-side
103+
- `DOWNLOAD FILE` — file downloads require server-side processing
104+
- All **workflow actions** (11 types: CallWorkflow, OpenWorkflow, SetTaskOutcome, OpenUserTask, etc.)
105+
106+
**Note:** Object operations (CREATE, CHANGE, COMMIT, DELETE, RETRIEVE) ARE allowed in nanoflows — they operate in-memory on the client.
107+
108+
## Return Type Restrictions
109+
110+
Binary return type is NOT allowed in nanoflows.
111+
112+
## Security (GRANT/REVOKE)
113+
114+
```mdl
115+
GRANT EXECUTE ON NANOFLOW Shop.NAV_Filter TO Shop.User, Shop.Admin;
116+
REVOKE EXECUTE ON NANOFLOW Shop.NAV_Filter FROM Shop.User;
117+
```
118+
119+
## Management Commands
120+
121+
```mdl
122+
SHOW NANOFLOWS
123+
SHOW NANOFLOWS IN MyModule
124+
DESCRIBE NANOFLOW MyModule.NAV_ShowDetails
125+
DROP NANOFLOW MyModule.NAV_ShowDetails;
126+
MOVE NANOFLOW Sales.NAV_OpenCart TO FOLDER 'UI/Navigation';
127+
```
128+
129+
## Common Mistakes
130+
131+
1. **Using Java actions** — Use CALL JAVASCRIPT ACTION instead.
132+
2. **Using ErrorEvent** — Nanoflows cannot raise errors directly. Handle errors per-action with ON ERROR.
133+
3. **Expecting transactions** — Nanoflows have no rollback. Design for idempotency.
134+
4. **File operations** — DOWNLOAD FILE is server-only.
135+
5. **Binary return types** — Not supported in nanoflows.
136+
6. **REST/external calls** — REST calls and external actions are server-only.
137+
138+
## Validation Checklist
139+
140+
- [ ] No ErrorEvent / raise error
141+
- [ ] No Java action calls
142+
- [ ] No REST calls, external action calls, or database queries
143+
- [ ] No file download operations
144+
- [ ] No import/export mapping or JSON transformation
145+
- [ ] No workflow actions
146+
- [ ] No show home page
147+
- [ ] No binary return type
148+
- [ ] Parameters and return types are nanoflow-compatible
149+
- [ ] JavaDoc documentation present
150+
- [ ] NAV_ naming prefix used

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
1818
- **OpenAPI import for REST clients**`CREATE REST CLIENT` now accepts `OpenAPI: 'path/or/url'` to auto-generate a consumed REST service document from an OpenAPI 3.0 spec (JSON or YAML); operations, path/query parameters, request bodies, response types, resource groups (tags), and Basic auth are derived automatically; spec content is stored in `OpenApiFile` for Studio Pro parity (#207)
1919
- **DESCRIBE CONTRACT OPERATION FROM OPENAPI** — Preview what would be generated from an OpenAPI spec without writing to the project
2020

21+
- **Flow bug fixes** — Module existence validation for SHOW NANOFLOWS and SHOW MICROFLOWS, numeric return literals no longer get spurious `$` prefix, empty flow names rejected at create time, `NanoflowCallAction` error handling type resolved correctly, `not()` expression spacing preserved on roundtrip, JavaScript action call rendering in DESCRIBE output
22+
- **Nanoflow diff support**`mxcli diff` now detects and displays nanoflow changes (previously silently skipped)
23+
- **Nanoflow validation parity**`mxcli check` now runs full body validation on nanoflows (undeclared variables, missing returns, branch scoping) via shared `validateFlowBody` helper; `allNames()` includes nanoflows for forward-reference detection
24+
- **DownloadFileStmt denylist**`DOWNLOAD FILE` added to nanoflow disallowed action list
25+
- **JavaScript action MDL syntax**`call javascript action Module.ActionName(params)` now fully supported in CREATE NANOFLOW/MICROFLOW bodies: grammar, parser, builder, serializer, and roundtrip
26+
- **LSP snippet completions** — Added `CREATE NANOFLOW (with params)`, `CALL MICROFLOW`, `CALL NANOFLOW`, `CALL JAVASCRIPT ACTION`, `CALL JAVA ACTION` snippets
27+
- **Flow builder cache**`lookupMicroflowReturnType` and `lookupNanoflowReturnType` now cache results with lazy-load bool flags, avoiding repeated `ListNanoflows`/`ListMicroflows` calls; nanoflow lookup uses `GetRawUnitByName` fast path
28+
- **Parser fixes** — Negative literals no longer greedily consumed by lexer (`-?` removed from `NUMBER_LITERAL`); XPath multi-predicate brackets correctly split and re-wrapped; multi-line string literals in change/create object expressions escaped; `ExclusiveSplit` (if/else) inside loop bodies now emits correct `end if;`
29+
- **Empty action params** — JS and Java action parameters with empty/nil values are omitted from DESCRIBE output instead of rendering as `...` or bare `= )`
30+
- **Association retrieve roundtrip fidelity**`retrieve $X from $Y/Module.Association` syntax preserved on roundtrip (previously converted to `from Entity where Assoc = $Y`)
31+
- **DESCRIBE empty-then optimization** — If/else blocks with empty true branches are swapped and condition negated for readable output
32+
2133
### Changed
2234

2335
- **MDL string literal escapes**`mdlQuote`/`unquoteString` now treat `\n`, `\r`, `\t`, and `\\` inside single-quoted literals as escape sequences (previously a literal backslash followed by the letter). This is a compatibility break for any MDL script that intentionally embedded a raw `\n` / `\t` / `\\` as two characters; such scripts must now double the backslash (`\\n` to preserve the two-character form). Applies to `LOG` messages, `@caption`/`@annotation` text, and other string literals round-tripped via the describer.

CLAUDE.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,7 @@ Regenerate after modifying `MDLLexer.g4` or `MDLParser.g4`: `make grammar`. See
425425
- `.claude/skills/version-awareness.md` - **CHECK project version first** - Run `show features` before using version-gated syntax
426426
- `.claude/skills/design-mdl-syntax.md` - **READ before designing new MDL syntax** - Design principles, decision framework, anti-patterns, checklist
427427
- `.claude/skills/write-microflows.md` - Microflow syntax, common mistakes, validation checklist
428+
- `.claude/skills/write-nanoflows.md` - Nanoflow syntax, restrictions, disallowed activities, validation checklist
428429
- `.claude/skills/create-page.md` - Page/widget syntax reference
429430
- `.claude/skills/alter-page.md` - ALTER PAGE/SNIPPET in-place modifications (SET, INSERT, DROP, REPLACE, SET Layout)
430431
- `.claude/skills/overview-pages.md` - CRUD page patterns
@@ -439,9 +440,9 @@ Regenerate after modifying `MDLLexer.g4` or `MDLParser.g4`: `make grammar`. See
439440
- `.claude/skills/database-connections.md` - External database connections from microflows
440441
- `.claude/skills/test-microflows.md` - **READ for testing work** - Test annotations, file formats, Docker setup requirement
441442

442-
### Mendix Microflow Idioms (MUST follow)
443+
### Mendix Microflow/Nanoflow Idioms (MUST follow)
443444

444-
These rules apply whenever generating microflow MDL. Violations are caught by `mxcli check`.
445+
These rules apply whenever generating microflow or nanoflow MDL. Violations are caught by `mxcli check`.
445446

446447
1. **NEVER create empty list variables as loop sources.** If processing imported data, accept the list as a microflow parameter — `declare $Items list of ... = empty` followed by `loop $item in $Items` is always wrong.
447448
2. **NEVER use nested LOOPs for list matching.** Loop over the primary list and use `retrieve $match from $TargetList where key = $item/key limit 1` for O(N) lookup. Nested loops are O(N^2).
@@ -464,7 +465,7 @@ Full syntax tables for all MDL statements (microflows, pages, security, navigati
464465
- MPR v1/v2 reading and writing
465466
- Domain model (entities, attributes, associations)
466467
- ALTER ENTITY (add/rename/modify/drop attributes, indexes, documentation)
467-
- Microflows/Nanoflows with 60+ activity types
468+
- Microflows/Nanoflows with 60+ activity types, JavaScript action calls, nanoflow validation parity
468469
- Pages with 50+ widget types
469470
- ALTER PAGE/SNIPPET (SET, INSERT, DROP, REPLACE operations on widget trees)
470471
- Image widgets (IMAGE, STATICIMAGE, DYNAMICIMAGE) with Width/Height properties

cmd/mxcli/cmd_describe.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,11 @@ Example:
187187
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
188188
os.Exit(1)
189189
}
190+
} else if upper == "NANOFLOW" {
191+
if err := exec.NanoflowELK(name); err != nil {
192+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
193+
os.Exit(1)
194+
}
190195
} else if upper == "PAGE" {
191196
if err := exec.PageWireframeJSON(name); err != nil {
192197
fmt.Fprintf(os.Stderr, "Error: %v\n", err)

cmd/mxcli/lsp_completion.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ var mdlCreateSnippets = []protocol.CompletionItem{
181181
snippet("CREATE MICROFLOW", "CREATE MICROFLOW ${1:Module}.${2:MicroflowName}\nBEGIN\n\t$0\nEND;", "Create a new microflow"),
182182
snippet("CREATE MICROFLOW (with params)", "CREATE MICROFLOW ${1:Module}.${2:MicroflowName}\n(\n\t$$${3:Param}: ${4:Module.Entity}\n)\nRETURNS ${5:Boolean} AS $$${6:Result}\nBEGIN\n\t$0\nEND;", "Create microflow with parameters"),
183183
snippet("CREATE NANOFLOW", "CREATE NANOFLOW ${1:Module}.${2:NanoflowName}\nBEGIN\n\t$0\nEND;", "Create a new nanoflow"),
184+
snippet("CREATE NANOFLOW (with params)", "CREATE NANOFLOW ${1:Module}.${2:NanoflowName}\n(\n\t$$${3:Param}: ${4:Module.Entity}\n)\nRETURNS ${5:Boolean} AS $$${6:Result}\nBEGIN\n\t$0\nEND;", "Create nanoflow with parameters"),
184185
snippet("CREATE ENUMERATION", "CREATE ENUMERATION ${1:Module}.${2:EnumName}\n(\n\t'${3:Value1}' '${4:Caption1}',\n\t'${5:Value2}' '${6:Caption2}'\n);", "Create a new enumeration"),
185186
snippet("CREATE CONSTANT", "CREATE CONSTANT ${1:Module}.${2:ConstantName}\nTYPE ${3|String,Integer,Long,Decimal,Boolean,DateTime|}\nDEFAULT ${4:'value'};", "Create a new constant"),
186187
snippet("CREATE PAGE", "CREATE PAGE ${1:Module}.${2:PageName}\n(\n\tTitle: '${3:Page Title}',\n\tLayout: ${4:Atlas_Core.Atlas_Default}\n)\n{\n\t$0\n}", "Create a new page"),
@@ -199,6 +200,10 @@ var mdlStatementSnippets = []protocol.CompletionItem{
199200
snippet("RETRIEVE ... FROM $Var/Assoc", "RETRIEVE $$${1:List} FROM $$${2:Parent}/${3:Module.AssociationName};", "Retrieve by association"),
200201
snippet("DATAVIEW", "DATAVIEW ${1:dvName} (DataSource: $$${2:Var}) {\n\t$0\n}", "Data view widget"),
201202
snippet("INDEX", "INDEX (${1:AttributeName});", "Entity index"),
203+
snippet("CALL MICROFLOW", "$$${1:Result} = CALL MICROFLOW ${2:Module.MicroflowName}(${3});", "Call a microflow"),
204+
snippet("CALL NANOFLOW", "$$${1:Result} = CALL NANOFLOW ${2:Module.NanoflowName}(${3});", "Call a nanoflow"),
205+
snippet("CALL JAVASCRIPT ACTION", "$$${1:Result} = CALL JAVASCRIPT ACTION ${2:Module.ActionName}(${3});", "Call a JavaScript action"),
206+
snippet("CALL JAVA ACTION", "$$${1:Result} = CALL JAVA ACTION ${2:Module.ActionName}(${3});", "Call a Java action"),
202207
}
203208

204209
// inferCompletionTypes examines the line prefix and returns the ObjectType

docs-site/src/language/nanoflows.md

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Nanoflows are client-side logic flows that execute in the user's browser or on m
1515
| **Close page** | Supported | Supported |
1616
| **Network** | Requires server round-trip | No network call (fast) |
1717
| **Offline** | Not available offline | Available offline |
18-
| **Error handling** | `ON ERROR` blocks | Limited error handling |
18+
| **Error handling** | `ON ERROR` blocks | Per-action `ON ERROR` (no `ErrorEvent`) |
1919

2020
## When to Use Which
2121

@@ -36,7 +36,7 @@ Nanoflows are client-side logic flows that execute in the user's browser or on m
3636
## CREATE NANOFLOW Syntax
3737

3838
```sql
39-
CREATE [OR REPLACE] NANOFLOW <Module.Name>
39+
CREATE [OR MODIFY] NANOFLOW <Module.Name>
4040
[FOLDER '<path>']
4141
BEGIN
4242
[<declarations>]
@@ -70,6 +70,9 @@ $Result = CALL NANOFLOW Sales.NAV_ValidateCart (Cart = $Cart);
7070

7171
-- Call a microflow (triggers server round-trip)
7272
$ServerResult = CALL MICROFLOW Sales.ACT_SubmitOrder (Order = $Order);
73+
74+
-- Call a JavaScript action
75+
$HasNetwork = CALL JAVASCRIPT ACTION NanoflowCommons.HasConnectivity();
7376
```
7477

7578
### UI Activities
@@ -110,13 +113,18 @@ END IF;
110113

111114
The following activities are server-only and cannot be used in nanoflows:
112115

113-
- `RETRIEVE ... FROM Module.Entity WHERE ...` (database retrieval)
114-
- `COMMIT`
115-
- `DELETE`
116-
- `ROLLBACK`
117-
- `CALL JAVA ACTION`
118-
- `EXECUTE DATABASE QUERY`
119-
- `ON ERROR { ... }` (full error handler blocks)
116+
- `CALL JAVA ACTION` — Java actions cannot run client-side
117+
- `ErrorEvent` / `RAISE ERROR` — error events are not available in nanoflows
118+
- `DOWNLOAD FILE` — file downloads require server-side processing
119+
- `CALL REST SERVICE` / `SEND REST REQUEST` — REST calls are server-side
120+
- `IMPORT FROM MAPPING` / `EXPORT TO MAPPING` — mapping operations are server-side
121+
- `EXECUTE DATABASE QUERY` — direct SQL requires server
122+
- `TRANSFORM JSON` — JSON transformations are server-side
123+
- `SHOW HOME PAGE` — home page navigation is server-side
124+
- `CALL EXTERNAL ACTION` — external actions are server-side
125+
- All **workflow actions** (call/open workflow, set task outcome, user task, etc.)
126+
127+
> **Note:** Per-action error handling (`on error continue`) IS supported in nanoflows. Only `ErrorEvent` (raise error as a standalone flow action) is forbidden. Note that `on error rollback` is syntactically valid but only rolls back in-memory changes — nanoflows have no database transactions.
120128
121129
## SHOW and DESCRIBE
122130

@@ -179,3 +187,7 @@ BEGIN
179187
SHOW PAGE Sales.Order_Detail ($Order = $Order);
180188
END;
181189
```
190+
191+
## Security
192+
193+
Nanoflow access control uses GRANT/REVOKE to specify which module roles can execute a nanoflow. See [Grant & Revoke](./grant-revoke.md) and [Document Access](./document-access.md) for full syntax and examples.

0 commit comments

Comments
 (0)