Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .claude/commands/mxcli-dev/review.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ proactively. Add a row after every review that surfaces something new.
| 7 | Skill/doc table references a function that doesn't exist (e.g. `formatActionStatement()` vs `formatAction()`) | Docs quality | Grep function names before writing: `grep -r "func formatA" mdl/executor/` |
| 8 | "Always X" rule is too absolute for trivial edge cases (e.g. "always write failing test first" for one-char typos) | Docs quality | Soften to "prefer X" or add an exception clause; include the reasoning so readers can judge edge cases |
| 9 | Doc comment promises a fallback/feature that doesn't exist in the code (e.g., "raw-map fallback in the client" when no such fallback was implemented) | Docs quality | Grep for function/type names referenced in doc comments to confirm they exist before committing |
| 10 | New MDL keyword or statement added without a parser example in `mdl-examples/` | Syntax feature | Add a minimal `mdl-examples/doctype-tests/*.mdl` fixture and include the statement in the quick reference before review |
| 11 | Commit titled `fix:` actually introduces new MDL syntax | Scope & atomicity | Split the syntax addition into a `feat:` or proposal-backed PR; keep bug fixes separate from language design |
| 12 | Round-trip bug patched on only one half of parse/write | DESCRIBE/EXEC symmetry | Fix both parser/defaulting and writer serialization, then add tests for missing/default fields and explicit fields |

---

Expand Down
55 changes: 55 additions & 0 deletions .claude/skills/mendix/write-microflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ commit $Product;

**Rules:**
- `@annotation` before an activity attaches the note to that activity
- `@annotation` before activity-binding metadata such as `@position`, `@caption`, `@color`, `@excluded`, or `@anchor` stays free-floating when later metadata binds the following activity
- `@annotation` at the end (no following activity) creates a free-floating note
- Escape single quotes by doubling: `@annotation 'Don''t forget'`
- `@position` always appears in DESCRIBE output; `@caption` only when custom; `@color` only when not Default
Expand Down Expand Up @@ -824,6 +825,60 @@ rest call delete 'https://api.example.com/items/{1}' with (

**REST CALL supports full error handling** (`on error continue`, `on error rollback`, custom error handlers).

## Legacy SOAP Web Service Calls

`call web service` preserves legacy Mendix SOAP activities. Prefer REST clients
for new integrations; this syntax exists mainly so existing projects can round-trip
without dropping SOAP actions.

```mdl
-- Structured passthrough form using Mendix document IDs.
$Root = call web service 'sample-service-id'
operation 'FetchSampleItems'
send mapping 'sample-send-mapping-id'
receive mapping 'sample-receive-mapping-id'
timeout 30
on error rollback;

-- Raw escape hatch emitted by describe when the SOAP action has fields that
-- are not expressible yet. The base64 payload is the authoritative BSON action.
$Root = call web service raw 'AQID';
```

**Design note:** service and mapping references are currently opaque Mendix IDs,
not qualified names. Treat this as round-trip support, not a recommended authoring
syntax for new SOAP actions.

## File Downloads

Use `download file` to stream a `System.FileDocument` from a microflow. Add
`show in browser` when Studio Pro's action should open the file inline instead
of forcing a download.

```mdl
download file $GeneratedReport show in browser;
download file $GeneratedExport;
```

## Empty Java-Action Argument (`...`)

When `describe` round-trips a Java-action call that has an unbound parameter
in Studio Pro, it emits `...` as the argument value. This preserves the
underlying empty `BasicCodeActionParameterValue.Argument` so that the next
`describe → exec → describe` cycle stays symmetric.

```mdl
$Total = call java action SampleModule.Recalculate(
CompanyId = ...,
RecalculateAll = true,
ItemList = ...
);
```

`...` is a *round-trip-only* placeholder. New scripts should bind every
parameter to a real expression; reach for `...` only when you're regenerating
MDL from an existing project that already had unbound parameters.

## Error Handling

MDL supports error handling for activities that may fail (microflow calls, commits, external service calls, etc.).
Expand Down
13 changes: 8 additions & 5 deletions cmd/mxcli/lsp_completions_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions docs/01-project/MDL_QUICK_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ authentication basic, session
| List declaration | `declare $list list of Module.Entity = empty;` | |
| Assignment | `set $Var = expression;` | Variable must be declared first |
| Create object | `$Var = create Module.Entity (attr = value);` | |
| Duplicate implicit output | `$Var`, `$Var_2`, `$Var_3` | Describe may alias same-position duplicate outputs for round-trip preservation |
| Change object | `change $entity (attr = value);` | |
| Commit | `commit $entity [with events] [refresh];` | |
| Delete | `delete $entity;` | |
Expand All @@ -223,14 +224,18 @@ authentication basic, session
| Retrieve (Assoc) | `retrieve $list from $Parent/Module.AssocName;` | Retrieve by association |
| Call microflow | `$Result = call microflow Module.Name (Param = $value);` | |
| Call nanoflow | `$Result = call nanoflow Module.Name (Param = $value);` | |
| Call web service | `$Result = call web service 'Module.Service' operation 'OperationName';` | Legacy SOAP; unresolved dangling refs fall back to raw IDs |
| Call web service raw | `$Result = call web service raw 'base64-bson';` | Escape hatch for byte-for-byte legacy SOAP round-trip |
| Show page | `show page Module.PageName ($Param = $value);` | Also accepts `(Param: $value)` |
| Close page | `close page;` | |
| Download file | `download file $FileDocument [show in browser];` | Streams a `System.FileDocument` |
| Validation | `validation feedback $entity/attribute message 'message';` | Requires attribute path + MESSAGE |
| Log | `log info\|warning\|error [node 'name'] 'message';` | |
| Position | `@position(x, y)` | Canvas position (before activity) |
| Caption | `@caption 'text'` | Custom caption (before activity) |
| Color | `@color Green` | Background color (before activity) |
| Annotation | `@annotation 'text'` | Visual note attached to next activity |
| Free annotation | `@annotation 'text'` before `@position(...)` | Free-floating visual note preserved by order |
| IF | `if condition then ... [else ...] end if;` | |
| LOOP | `loop $item in $list begin ... end loop;` | FOR EACH over list |
| WHILE | `while condition begin ... end while;` | Condition-based loop |
Expand Down Expand Up @@ -775,6 +780,7 @@ Module.OrderResponse_CustomerInfo/Module.CustomerInfo as customer {
| Create exposed action | `... exposed as 'caption' in 'Category' as $$ ... $$;` | Toolbox-visible in Studio Pro |
| Drop Java action | `drop java action Module.Name;` | Delete a Java action |
| Call from microflow | `$Result = call java action Module.Name(Param = value);` | Inside BEGIN...END |
| Empty argument | `call java action Module.Name(Param = ...);` | `...` placeholder for an unbound code-action parameter (round-trip only) |

**Parameter Types:** `string`, `integer`, `long`, `decimal`, `boolean`, `datetime`, `Module.Entity`, `list of Module.Entity`, `enum Module.EnumName`, `enumeration(Module.EnumName)`, `stringtemplate(sql)`, `stringtemplate(Oql)`, `entity <pEntity>` (type parameter declaration), bare `pEntity` (type parameter reference).

Expand Down
111 changes: 111 additions & 0 deletions docs/11-proposals/PROPOSAL_ellipsis_placeholder_expression.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Ellipsis Placeholder Expression

Status: Draft

## Summary

Add a single-token expression `...` that represents an unbound /
intentionally-empty argument value in microflow call statements.

```mdl
$Total = call java action SampleModule.Recalculate(
CompanyId = ...,
RecalculateAll = true,
ItemList = ...
);
```

`...` produces a parameter binding with an empty `Argument` string in the
serialized BSON (`Microflows$BasicCodeActionParameterValue.Argument = ""`).
Re-executing a script that contains `...` reproduces the same empty
binding byte-for-byte, so describe → exec → describe stays symmetric for
existing Studio Pro projects that have unbound code-action parameters.

## Motivation

Studio Pro's Java-action call dialog allows a developer to leave individual
parameters empty — for example, when a Java action declares a parameter that
the calling microflow does not yet have a meaningful value for, or when an
external mapping is expected to fill the slot at runtime. The on-disk
representation is a `Microflows$JavaActionParameterMapping` whose `Value` is a
`BasicCodeActionParameterValue` with `Argument: ""`.

Before `...` existed, the describer had two options for these empty bindings:

1. Emit `''` (empty string literal). On re-exec, the visitor would round-trip
to a non-empty single-quote literal whose `Argument` was `''`, not `""`,
and Studio Pro would render the parameter as the literal string `''`.
2. Drop the parameter entirely. Studio Pro would then add a back a
placeholder mapping with a generated value, breaking the round-trip.

Both lose information. `...` lets the describer round-trip the empty binding
without inventing a fake value.

## Syntax

```antlr
atomicExpression
: literal
| ELLIPSIS
| ...
;
```

Where `ELLIPSIS` is the lexer token `'...'`. The token is reserved for this
single use; it is not valid in arithmetic / boolean / comparison
expressions.

## Semantics

- `...` is recognised by the builder via `isPlaceholderExpression` in
`mdl/executor/cmd_microflows_builder_calls.go`.
- Inside a Java-action `callArgument`, `...` produces a
`BasicCodeActionParameterValue` with `Argument: ""`.
- Outside of `callArgument` lists, `...` parses but the builder rejects it
(it never resolves to a runtime value). Future statements may extend the
set of contexts that accept `...` — see Open Questions.

## Examples

```mdl
-- Java action call with two unbound and one bound argument
$Total = call java action SampleModule.Recalculate(
CompanyId = ...,
RecalculateAll = true,
ItemList = ...
);
```

The Mendix BSON for the unbound arguments is:

```
JavaActionParameterMapping {
Parameter: 'SampleModule.Recalculate.CompanyId',
Value: BasicCodeActionParameterValue { Argument: '' }
}
```

## Tests And Examples

- Builder coverage: `TestBuildJavaAction_PlaceholderArgumentPreservesEmptyBasicValue`
in `mdl/executor/cmd_microflows_builder_java_action_test.go`.
- Visitor coverage: `atomicExpression`'s `ELLIPSIS` arm produces
`ast.SourceExpr{Source: "..."}` (see
`mdl/visitor/visitor_microflow_expression.go`).
- Example script: `mdl-examples/doctype-tests/ellipsis_placeholder.test.mdl`.

## Open Questions

- Should `...` be allowed as an argument to `call microflow` and
`call nanoflow` calls as well? Today only Java actions consume the
`BasicCodeActionParameterValue` form, so there is no symmetric BSON
representation, but a future proposal could extend this.
- Should we explicitly document `...` as round-trip-only and warn the linter
when an authored microflow uses `...` outside of a known describe-emitted
context? This would prevent users from authoring scripts that produce
Studio Pro warnings ("unbound parameter") on import.
- Should the surface syntax be `...` or a clearer keyword like
`unspecified` / `default` / `unbound`? `...` was chosen because it is
visually distinct, short, and matches existing "this is intentionally
blank" conventions in other tools (Python's `Ellipsis`, TypeScript's
`never` placeholder, etc.).
91 changes: 91 additions & 0 deletions docs/11-proposals/PROPOSAL_microflow_call_web_service_statement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Microflow Call Web Service Statement

Status: Draft

## Summary

Add MDL support for legacy Mendix SOAP `Microflows$CallWebServiceAction`.

```mdl
$Root = call web service 'SampleSOAP.OrderService'
operation 'FetchSampleItems'
send mapping 'SampleSOAP.OrderRequest'
receive mapping 'SampleSOAP.OrderResponse'
timeout 30;

$Root = call web service 'dangling-service-id'
operation 'FetchSampleItems'
send mapping 'dangling-send-mapping-id'
receive mapping 'dangling-receive-mapping-id';

$Root = call web service raw 'AQID';
```

This proposal is primarily about safe round-trip preservation of existing SOAP
actions. New integrations should prefer consumed REST services or inline REST
calls.

## Motivation

Legacy projects can contain SOAP web service calls. Without an MDL
representation, describe output either drops the activity or emits an
unsupported-action comment that cannot be re-executed into the same model.

The immediate goal is therefore fidelity:

- Parse existing `CallWebServiceAction` BSON.
- Emit an MDL statement that can be executed back into the MPR.
- Preserve unsupported or version-specific BSON fields when the structured
fields are incomplete.

## Syntax

```antlr
callWebServiceStatement
: (VARIABLE EQUALS)? CALL WEB SERVICE
(RAW STRING_LITERAL
| STRING_LITERAL
(OPERATION STRING_LITERAL)?
(SEND MAPPING STRING_LITERAL)?
(RECEIVE MAPPING STRING_LITERAL)?
(TIMEOUT expression)?)
onErrorClause?
SEMICOLON
;
```

## Design Notes

The structured form prefers stable qualified names for the imported web service
and mapping references. During `describe`, mxcli resolves known
`WebServices$ImportedWebService`, `ExportMappings$ExportMapping`, and
`ImportMappings$ImportMapping` IDs through the backend and emits
`Module.DocumentName`.

If a reference is dangling or the backend cannot resolve it, mxcli deliberately
falls back to the raw ID string so unsupported legacy projects still round-trip.

The `raw` form is an explicit escape hatch. Its string is base64-encoded BSON
for the complete action payload and is authoritative when re-executed. It exists
so unsupported SOAP fields can be preserved byte-for-byte until the structured
syntax covers them.

## Tests And Examples

- Parser/visitor coverage: `TestCallWebServiceStatement` and
`TestCallWebServiceRawStatement`.
- Builder/writer coverage: `TestBuildFlowGraph_WebServiceCallCreatesRealAction`,
`TestBuildFlowGraph_WebServiceCallPreservesRawBSON`, and MPR RawBSON tests.
- Example script: `mdl-examples/doctype-tests/call_web_service.test.mdl`.

## Open Questions

- Should the raw payload eventually move to a generic
`raw microflow action '...'` escape hatch instead of remaining under
`call web service raw`?

## Resolved Questions

- Service and mapping references are emitted as `Module.Document` names when
the backend can resolve them. Raw IDs remain the fallback for dangling
references and incomplete project metadata.
Loading
Loading