Skip to content

Commit 9997a3c

Browse files
akoclaude
andcommitted
feat: implement DESCRIBE NANOFLOW with activities and control flows
DESCRIBE NANOFLOW now outputs re-executable CREATE OR REPLACE NANOFLOW MDL with parameters, return type, folder, and a BEGIN...END block listing all activities and control flows (reuses microflow formatter). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e99a356 commit 9997a3c

File tree

4 files changed

+126
-0
lines changed

4 files changed

+126
-0
lines changed

mdl/ast/ast_query.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ const (
284284
DescribeContractAction // DESCRIBE CONTRACT ACTION Service.ActionName [FORMAT mdl]
285285
DescribeContractMessage // DESCRIBE CONTRACT MESSAGE Service.MessageName
286286
DescribeJsonStructure // DESCRIBE JSON STRUCTURE Module.Name
287+
DescribeNanoflow // DESCRIBE NANOFLOW Module.Name
287288
DescribeImportMapping // DESCRIBE IMPORT MAPPING Module.Name
288289
DescribeExportMapping // DESCRIBE EXPORT MAPPING Module.Name
289290
)
@@ -351,6 +352,8 @@ func (t DescribeObjectType) String() string {
351352
return "CONTRACT MESSAGE"
352353
case DescribeJsonStructure:
353354
return "JSON STRUCTURE"
355+
case DescribeNanoflow:
356+
return "NANOFLOW"
354357
case DescribeImportMapping:
355358
return "IMPORT MAPPING"
356359
case DescribeExportMapping:

mdl/executor/cmd_microflows_show.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,122 @@ func (e *Executor) describeMicroflow(name ast.QualifiedName) error {
395395
return nil
396396
}
397397

398+
// describeNanoflow generates re-executable CREATE OR REPLACE NANOFLOW MDL output
399+
// with activities and control flows listed as comments.
400+
func (e *Executor) describeNanoflow(name ast.QualifiedName) error {
401+
h, err := e.getHierarchy()
402+
if err != nil {
403+
return fmt.Errorf("failed to build hierarchy: %w", err)
404+
}
405+
406+
// Build entity name lookup
407+
entityNames := make(map[model.ID]string)
408+
domainModels, _ := e.reader.ListDomainModels()
409+
for _, dm := range domainModels {
410+
modName := h.GetModuleName(dm.ContainerID)
411+
for _, entity := range dm.Entities {
412+
entityNames[entity.ID] = modName + "." + entity.Name
413+
}
414+
}
415+
416+
// Build microflow/nanoflow name lookup (used for call actions)
417+
microflowNames := make(map[model.ID]string)
418+
allMicroflows, _ := e.reader.ListMicroflows()
419+
for _, mf := range allMicroflows {
420+
microflowNames[mf.ID] = h.GetQualifiedName(mf.ContainerID, mf.Name)
421+
}
422+
423+
// Find the nanoflow
424+
allNanoflows, err := e.reader.ListNanoflows()
425+
if err != nil {
426+
return fmt.Errorf("failed to list nanoflows: %w", err)
427+
}
428+
429+
for _, nf := range allNanoflows {
430+
microflowNames[nf.ID] = h.GetQualifiedName(nf.ContainerID, nf.Name)
431+
}
432+
433+
var targetNf *microflows.Nanoflow
434+
for _, nf := range allNanoflows {
435+
modID := h.FindModuleID(nf.ContainerID)
436+
modName := h.GetModuleName(modID)
437+
if modName == name.Module && nf.Name == name.Name {
438+
targetNf = nf
439+
break
440+
}
441+
}
442+
443+
if targetNf == nil {
444+
return fmt.Errorf("nanoflow not found: %s", name)
445+
}
446+
447+
var lines []string
448+
449+
// Documentation
450+
if targetNf.Documentation != "" {
451+
lines = append(lines, "/**")
452+
for docLine := range strings.SplitSeq(targetNf.Documentation, "\n") {
453+
lines = append(lines, " * "+docLine)
454+
}
455+
lines = append(lines, " */")
456+
}
457+
458+
// CREATE NANOFLOW header
459+
qualifiedName := name.Module + "." + name.Name
460+
if len(targetNf.Parameters) > 0 {
461+
lines = append(lines, fmt.Sprintf("CREATE OR REPLACE NANOFLOW %s (", qualifiedName))
462+
for i, param := range targetNf.Parameters {
463+
paramType := "Object"
464+
if param.Type != nil {
465+
paramType = e.formatMicroflowDataType(param.Type, entityNames)
466+
}
467+
comma := ","
468+
if i == len(targetNf.Parameters)-1 {
469+
comma = ""
470+
}
471+
lines = append(lines, fmt.Sprintf(" $%s: %s%s", param.Name, paramType, comma))
472+
}
473+
lines = append(lines, ")")
474+
} else {
475+
lines = append(lines, fmt.Sprintf("CREATE OR REPLACE NANOFLOW %s ()", qualifiedName))
476+
}
477+
478+
// Return type
479+
if targetNf.ReturnType != nil {
480+
returnType := e.formatMicroflowDataType(targetNf.ReturnType, entityNames)
481+
if returnType != "Void" && returnType != "" {
482+
lines = append(lines, fmt.Sprintf("RETURNS %s", returnType))
483+
}
484+
}
485+
486+
// Folder
487+
if folderPath := h.BuildFolderPath(targetNf.ContainerID); folderPath != "" {
488+
lines = append(lines, fmt.Sprintf("FOLDER '%s'", folderPath))
489+
}
490+
491+
// BEGIN block with activities
492+
lines = append(lines, "BEGIN")
493+
494+
// Wrap nanoflow in a Microflow to reuse formatMicroflowActivities
495+
if targetNf.ObjectCollection != nil && len(targetNf.ObjectCollection.Objects) > 0 {
496+
wrapperMf := &microflows.Microflow{
497+
ObjectCollection: targetNf.ObjectCollection,
498+
}
499+
activityLines := e.formatMicroflowActivities(wrapperMf, entityNames, microflowNames)
500+
for _, line := range activityLines {
501+
lines = append(lines, " "+line)
502+
}
503+
} else {
504+
lines = append(lines, " -- No activities")
505+
}
506+
507+
lines = append(lines, "END;")
508+
lines = append(lines, "/")
509+
510+
fmt.Fprintln(e.output, strings.Join(lines, "\n"))
511+
return nil
512+
}
513+
398514
// describeMicroflowToString generates MDL source for a microflow and returns it as a string
399515
// along with a source map mapping node IDs to line ranges.
400516
func (e *Executor) describeMicroflowToString(name ast.QualifiedName) (string, map[string]elkSourceRange, error) {

mdl/executor/executor.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,8 @@ func (e *Executor) execDescribe(s *ast.DescribeStmt) error {
837837
return e.describeAssociation(s.Name)
838838
case ast.DescribeMicroflow:
839839
return e.describeMicroflow(s.Name)
840+
case ast.DescribeNanoflow:
841+
return e.describeNanoflow(s.Name)
840842
case ast.DescribeModule:
841843
return e.describeModule(s.Name.Module, s.WithAll)
842844
case ast.DescribePage:

mdl/visitor/visitor_query.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,11 @@ func (b *Builder) ExitDescribeStatement(ctx *parser.DescribeStatementContext) {
825825
ObjectType: ast.DescribeMicroflow,
826826
Name: name,
827827
})
828+
} else if ctx.NANOFLOW() != nil {
829+
b.statements = append(b.statements, &ast.DescribeStmt{
830+
ObjectType: ast.DescribeNanoflow,
831+
Name: name,
832+
})
828833
} else if ctx.WORKFLOW() != nil {
829834
b.statements = append(b.statements, &ast.DescribeStmt{
830835
ObjectType: ast.DescribeWorkflow,

0 commit comments

Comments
 (0)