yao/trace/jsapi/node.go
Max e781e8f591 Refactor trace node management to support multiple parents and enhance type handling
- Updated the TraceNode structure to allow multiple parent IDs, facilitating implicit joins in trace management.
- Enhanced the TraceNodeOption to include a type field for better categorization of nodes.
- Refactored related methods in the trace manager and node handling to accommodate the new parent structure and type field.
- Improved test coverage for trace node creation and retrieval, ensuring robust handling of the new multi-parent functionality.
2025-11-21 17:33:00 +08:00

315 lines
8.5 KiB
Go

package jsapi
import (
"fmt"
"github.com/yaoapp/gou/runtime/v8/bridge"
"github.com/yaoapp/yao/trace/types"
"rogchap.com/v8go"
)
// Helper functions
func parseTraceInput(obj *v8go.Object, ctx *v8go.Context) (types.TraceInput, error) {
goVal, err := bridge.GoValue(obj.Value, ctx)
if err != nil {
return nil, err
}
return goVal, nil
}
func parseTraceNodeOption(obj *v8go.Object) types.TraceNodeOption {
option := types.TraceNodeOption{}
if labelVal, err := obj.Get("label"); err == nil && !labelVal.IsNullOrUndefined() {
option.Label = labelVal.String()
}
if typeVal, err := obj.Get("type"); err == nil && !typeVal.IsNullOrUndefined() {
option.Type = typeVal.String()
}
if iconVal, err := obj.Get("icon"); err == nil && !iconVal.IsNullOrUndefined() {
option.Icon = iconVal.String()
}
if descVal, err := obj.Get("description"); err == nil && !descVal.IsNullOrUndefined() {
option.Description = descVal.String()
}
if autoCompleteVal, err := obj.Get("autoCompleteParent"); err == nil && !autoCompleteVal.IsNullOrUndefined() {
boolVal := autoCompleteVal.Boolean()
option.AutoCompleteParent = &boolVal
}
return option
}
// NewNodeObject creates a JavaScript Node object (pure JS object, no Go mapping)
func NewNodeObject(v8ctx *v8go.Context, node types.Node) (*v8go.Value, error) {
jsObject := v8go.NewObjectTemplate(v8ctx.Isolate())
// Set primitive fields
jsObject.Set("id", node.ID())
// Set methods
jsObject.Set("Info", nodeInfoMethod(v8ctx.Isolate(), node))
jsObject.Set("Debug", nodeDebugMethod(v8ctx.Isolate(), node))
jsObject.Set("Error", nodeErrorMethod(v8ctx.Isolate(), node))
jsObject.Set("Warn", nodeWarnMethod(v8ctx.Isolate(), node))
jsObject.Set("Add", nodeAddMethod(v8ctx.Isolate(), node))
jsObject.Set("Parallel", nodeParallelMethod(v8ctx.Isolate(), node))
jsObject.Set("SetOutput", nodeSetOutputMethod(v8ctx.Isolate(), node))
jsObject.Set("SetMetadata", nodeSetMetadataMethod(v8ctx.Isolate(), node))
jsObject.Set("Complete", nodeCompleteMethod(v8ctx.Isolate(), node))
jsObject.Set("Fail", nodeFailMethod(v8ctx.Isolate(), node))
// Create instance
instance, err := jsObject.NewInstance(v8ctx)
if err != nil {
return nil, err
}
return instance.Value, nil
}
// Node method templates
func nodeInfoMethod(iso *v8go.Isolate, node types.Node) *v8go.FunctionTemplate {
return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
args := info.Args()
if len(args) > 0 {
node.Info(args[0].String())
}
return info.This().Value
})
}
func nodeDebugMethod(iso *v8go.Isolate, node types.Node) *v8go.FunctionTemplate {
return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
args := info.Args()
if len(args) > 0 {
node.Debug(args[0].String())
}
return info.This().Value
})
}
func nodeErrorMethod(iso *v8go.Isolate, node types.Node) *v8go.FunctionTemplate {
return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
args := info.Args()
if len(args) > 0 {
node.Error(args[0].String())
}
return info.This().Value
})
}
func nodeWarnMethod(iso *v8go.Isolate, node types.Node) *v8go.FunctionTemplate {
return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
args := info.Args()
if len(args) > 0 {
node.Warn(args[0].String())
}
return info.This().Value
})
}
func nodeAddMethod(iso *v8go.Isolate, node types.Node) *v8go.FunctionTemplate {
return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
ctx := info.Context()
args := info.Args()
if len(args) < 2 {
return bridge.JsException(ctx, "Add requires 2 arguments: (input, option)")
}
// Parse input
inputObj, err := args[0].AsObject()
if err != nil {
return bridge.JsException(ctx, "first argument must be an object")
}
input, _ := parseTraceInput(inputObj, ctx)
// Parse option
optionObj, err := args[1].AsObject()
if err != nil {
return bridge.JsException(ctx, "second argument must be an object")
}
option := parseTraceNodeOption(optionObj)
// Call node.Add
childNode, err := node.Add(input, option)
if err != nil {
return bridge.JsException(ctx, err.Error())
}
// Create child node JS object
childNodeObj, err := NewNodeObject(ctx, childNode)
if err != nil {
return bridge.JsException(ctx, err.Error())
}
return childNodeObj
})
}
func nodeParallelMethod(iso *v8go.Isolate, node types.Node) *v8go.FunctionTemplate {
return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
ctx := info.Context()
args := info.Args()
if len(args) < 1 {
return bridge.JsException(ctx, "Parallel requires an array argument")
}
// Parse array
arrayObj, err := args[0].AsObject()
if err != nil {
return bridge.JsException(ctx, "argument must be an array")
}
lengthVal, err := arrayObj.Get("length")
if err != nil {
return bridge.JsException(ctx, "invalid array")
}
length := int(lengthVal.Int32())
parallelInputs := make([]types.TraceParallelInput, 0, length)
for i := 0; i < length; i++ {
itemVal, err := arrayObj.GetIdx(uint32(i))
if err != nil {
continue
}
itemObj, err := itemVal.AsObject()
if err != nil {
continue
}
// Parse input
var input types.TraceInput
if inputVal, err := itemObj.Get("input"); err == nil && inputVal.IsObject() {
inputObjInner, _ := inputVal.AsObject()
input, _ = parseTraceInput(inputObjInner, ctx)
}
// Parse option
var option types.TraceNodeOption
if optionVal, err := itemObj.Get("option"); err == nil && optionVal.IsObject() {
optionObjInner, _ := optionVal.AsObject()
option = parseTraceNodeOption(optionObjInner)
}
parallelInputs = append(parallelInputs, types.TraceParallelInput{
Input: input,
Option: option,
})
}
// Call node.Parallel
childNodes, err := node.Parallel(parallelInputs)
if err != nil {
return bridge.JsException(ctx, err.Error())
}
// Create array of node objects
result := make([]interface{}, len(childNodes))
for i, childNode := range childNodes {
childNodeObj, err := NewNodeObject(ctx, childNode)
if err != nil {
continue
}
result[i] = childNodeObj
}
jsVal, err := bridge.JsValue(ctx, result)
if err != nil {
return bridge.JsException(ctx, err.Error())
}
return jsVal
})
}
func nodeSetOutputMethod(iso *v8go.Isolate, node types.Node) *v8go.FunctionTemplate {
return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
ctx := info.Context()
args := info.Args()
if len(args) < 1 {
return bridge.JsException(ctx, "SetOutput requires an output argument")
}
output, err := bridge.GoValue(args[0], ctx)
if err != nil {
return bridge.JsException(ctx, err.Error())
}
if err := node.SetOutput(output); err != nil {
return bridge.JsException(ctx, err.Error())
}
return info.This().Value
})
}
func nodeSetMetadataMethod(iso *v8go.Isolate, node types.Node) *v8go.FunctionTemplate {
return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
ctx := info.Context()
args := info.Args()
if len(args) < 2 {
return bridge.JsException(ctx, "SetMetadata requires 2 arguments: (key, value)")
}
key := args[0].String()
value, err := bridge.GoValue(args[1], ctx)
if err != nil {
return bridge.JsException(ctx, err.Error())
}
if err := node.SetMetadata(key, value); err != nil {
return bridge.JsException(ctx, err.Error())
}
return info.This().Value
})
}
func nodeCompleteMethod(iso *v8go.Isolate, node types.Node) *v8go.FunctionTemplate {
return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
ctx := info.Context()
args := info.Args()
// Optional output parameter
if len(args) > 0 {
output, err := bridge.GoValue(args[0], ctx)
if err != nil {
return bridge.JsException(ctx, err.Error())
}
if err := node.Complete(output); err != nil {
return bridge.JsException(ctx, err.Error())
}
} else {
if err := node.Complete(); err != nil {
return bridge.JsException(ctx, err.Error())
}
}
return info.This().Value
})
}
func nodeFailMethod(iso *v8go.Isolate, node types.Node) *v8go.FunctionTemplate {
return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
ctx := info.Context()
args := info.Args()
if len(args) < 1 {
return bridge.JsException(ctx, "Fail requires an error message")
}
errMsg := args[0].String()
if err := node.Fail(fmt.Errorf("%s", errMsg)); err != nil {
return bridge.JsException(ctx, err.Error())
}
return info.This().Value
})
}