diff --git a/cmd/generate/main-generate.go b/cmd/generate/main-generate.go index d7548ac8..a0944250 100644 --- a/cmd/generate/main-generate.go +++ b/cmd/generate/main-generate.go @@ -14,14 +14,13 @@ import ( "github.com/wavetermdev/thenextwave/pkg/util/utilfn" ) -func generateTypesFile() error { +func generateTypesFile(tsTypesMap map[reflect.Type]string) error { fd, err := os.Create("frontend/types/gotypes.d.ts") if err != nil { return err } defer fd.Close() fmt.Fprintf(os.Stderr, "generating types file to %s\n", fd.Name()) - tsTypesMap := make(map[reflect.Type]string) tsgen.GenerateWaveObjTypes(tsTypesMap) err = tsgen.GenerateServiceTypes(tsTypesMap) if err != nil { @@ -37,8 +36,8 @@ func generateTypesFile() error { keys = append(keys, key) } sort.Slice(keys, func(i, j int) bool { - iname, _ := tsgen.TypeToTSType(keys[i]) - jname, _ := tsgen.TypeToTSType(keys[j]) + iname, _ := tsgen.TypeToTSType(keys[i], tsTypesMap) + jname, _ := tsgen.TypeToTSType(keys[j], tsTypesMap) return iname < jname }) for _, key := range keys { @@ -51,7 +50,7 @@ func generateTypesFile() error { return nil } -func generateServicesFile() error { +func generateServicesFile(tsTypesMap map[reflect.Type]string) error { fd, err := os.Create("frontend/app/store/services.ts") if err != nil { return err @@ -65,7 +64,7 @@ func generateServicesFile() error { orderedKeys := utilfn.GetOrderedMapKeys(service.ServiceMap) for _, serviceName := range orderedKeys { serviceObj := service.ServiceMap[serviceName] - svcStr := tsgen.GenerateServiceClass(serviceName, serviceObj) + svcStr := tsgen.GenerateServiceClass(serviceName, serviceObj, tsTypesMap) fmt.Fprint(fd, svcStr) fmt.Fprint(fd, "\n") } @@ -78,12 +77,13 @@ func main() { fmt.Fprintf(os.Stderr, "Error validating service map: %v\n", err) os.Exit(1) } - err = generateTypesFile() + tsTypesMap := make(map[reflect.Type]string) + err = generateTypesFile(tsTypesMap) if err != nil { fmt.Fprintf(os.Stderr, "Error generating types file: %v\n", err) os.Exit(1) } - err = generateServicesFile() + err = generateServicesFile(tsTypesMap) if err != nil { fmt.Fprintf(os.Stderr, "Error generating services file: %v\n", err) os.Exit(1) diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx index 6dc99bb1..12d154ae 100644 --- a/frontend/app/block/block.tsx +++ b/frontend/app/block/block.tsx @@ -49,7 +49,7 @@ const Block = ({ blockId, onClose }: BlockProps) => { } else if (blockData.view === "plot") { blockElem = ; } else if (blockData.view === "codeedit") { - blockElem = ; + blockElem = ; } return (
diff --git a/frontend/app/store/services.ts b/frontend/app/store/services.ts index d3a14152..132a306b 100644 --- a/frontend/app/store/services.ts +++ b/frontend/app/store/services.ts @@ -8,7 +8,7 @@ import * as WOS from "./wos"; // blockservice.BlockService (block) class BlockServiceType { // send command to block - SendCommand(blockid: string, command: MetaType): Promise { + SendCommand(blockid: string, cmd: BlockCommand): Promise { return WOS.callBackendService("block", "SendCommand", Array.from(arguments)) } } diff --git a/frontend/app/view/term.tsx b/frontend/app/view/term.tsx index 347bea65..2e5a743c 100644 --- a/frontend/app/view/term.tsx +++ b/frontend/app/view/term.tsx @@ -49,7 +49,10 @@ function handleResize(fitAddon: FitAddon, blockId: string, term: Terminal) { const oldCols = term.cols; fitAddon.fit(); if (oldRows !== term.rows || oldCols !== term.cols) { - const resizeCommand = { command: "controller:input", termsize: { rows: term.rows, cols: term.cols } }; + const resizeCommand: BlockInputCommand = { + command: "controller:input", + termsize: { rows: term.rows, cols: term.cols }, + }; services.BlockService.SendCommand(blockId, resizeCommand); } } @@ -78,17 +81,13 @@ const TerminalView = ({ blockId }: { blockId: string }) => { newTerm.loadAddon(newFitAddon); newTerm.open(connectElemRef.current); newFitAddon.fit(); - // BlockService.SendCommand(blockId, { - // command: "controller:input", - // termsize: { rows: newTerm.rows, cols: newTerm.cols }, - // }); services.BlockService.SendCommand(blockId, { command: "controller:input", termsize: { rows: newTerm.rows, cols: newTerm.cols }, }); newTerm.onData((data) => { const b64data = btoa(data); - const inputCmd = { command: "controller:input", blockid: blockId, inputdata64: b64data }; + const inputCmd: BlockInputCommand = { command: "controller:input", inputdata64: b64data }; services.BlockService.SendCommand(blockId, inputCmd); }); diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index e505a95d..4e28ee4f 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -14,6 +14,10 @@ declare global { meta: MetaType; }; + type BlockCommand = { + command: string; + } & ( BlockMessageCommand | BlockInputCommand | BlockSetViewCommand | BlockSetMetaCommand ); + // wstore.BlockDef type BlockDef = { controller?: string; @@ -22,6 +26,32 @@ declare global { meta?: MetaType; }; + // blockcontroller.BlockInputCommand + type BlockInputCommand = { + command: "controller:input"; + inputdata64?: string; + signame?: string; + termsize?: TermSize; + }; + + // blockcontroller.BlockMessageCommand + type BlockMessageCommand = { + command: "message"; + message: string; + }; + + // blockcontroller.BlockSetMetaCommand + type BlockSetMetaCommand = { + command: "setmeta"; + meta: MetaType; + }; + + // blockcontroller.BlockSetViewCommand + type BlockSetViewCommand = { + command: "setview"; + view: string; + }; + // wstore.Client type Client = WaveObj & { mainwindowid: string; @@ -62,7 +92,7 @@ declare global { type MetaType = {[key: string]: any} - // servicemeta.MethodMeta + // tsgenmeta.MethodMeta type MethodMeta = { Desc: string; ArgNames: string[]; diff --git a/pkg/blockcontroller/blockcommand.go b/pkg/blockcontroller/blockcommand.go index fb0a65fe..6b662809 100644 --- a/pkg/blockcontroller/blockcommand.go +++ b/pkg/blockcontroller/blockcommand.go @@ -9,6 +9,7 @@ import ( "reflect" "github.com/wavetermdev/thenextwave/pkg/shellexec" + "github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta" ) const CommandKey = "command" @@ -21,16 +22,33 @@ const ( ) var CommandToTypeMap = map[string]reflect.Type{ - BlockCommand_Message: reflect.TypeOf(MessageCommand{}), - BlockCommand_Input: reflect.TypeOf(InputCommand{}), - BlockCommand_SetView: reflect.TypeOf(SetViewCommand{}), - BlockCommand_SetMeta: reflect.TypeOf(SetMetaCommand{}), + BlockCommand_Message: reflect.TypeOf(BlockMessageCommand{}), + BlockCommand_Input: reflect.TypeOf(BlockInputCommand{}), + BlockCommand_SetView: reflect.TypeOf(BlockSetViewCommand{}), + BlockCommand_SetMeta: reflect.TypeOf(BlockSetMetaCommand{}), +} + +func CommandTypeUnionMeta() tsgenmeta.TypeUnionMeta { + return tsgenmeta.TypeUnionMeta{ + BaseType: reflect.TypeOf((*BlockCommand)(nil)).Elem(), + TypeFieldName: "command", + Types: []reflect.Type{ + reflect.TypeOf(BlockMessageCommand{}), + reflect.TypeOf(BlockInputCommand{}), + reflect.TypeOf(BlockSetViewCommand{}), + reflect.TypeOf(BlockSetMetaCommand{}), + }, + } } type BlockCommand interface { GetCommand() string } +type BlockCommandWrapper struct { + BlockCommand +} + func ParseCmdMap(cmdMap map[string]any) (BlockCommand, error) { cmdType, ok := cmdMap[CommandKey].(string) if !ok { @@ -52,40 +70,40 @@ func ParseCmdMap(cmdMap map[string]any) (BlockCommand, error) { return cmd.(BlockCommand), nil } -type MessageCommand struct { - Command string `json:"command"` +type BlockMessageCommand struct { + Command string `json:"command" tstype:"\"message\""` Message string `json:"message"` } -func (mc *MessageCommand) GetCommand() string { +func (mc *BlockMessageCommand) GetCommand() string { return BlockCommand_Message } -type InputCommand struct { - Command string `json:"command"` - InputData64 string `json:"inputdata64"` +type BlockInputCommand struct { + Command string `json:"command" tstype:"\"controller:input\""` + InputData64 string `json:"inputdata64,omitempty"` SigName string `json:"signame,omitempty"` TermSize *shellexec.TermSize `json:"termsize,omitempty"` } -func (ic *InputCommand) GetCommand() string { +func (ic *BlockInputCommand) GetCommand() string { return BlockCommand_Input } -type SetViewCommand struct { - Command string `json:"command"` +type BlockSetViewCommand struct { + Command string `json:"command" tstype:"\"setview\""` View string `json:"view"` } -func (svc *SetViewCommand) GetCommand() string { +func (svc *BlockSetViewCommand) GetCommand() string { return BlockCommand_SetView } -type SetMetaCommand struct { - Command string `json:"command"` +type BlockSetMetaCommand struct { + Command string `json:"command" tstype:"\"setmeta\""` Meta map[string]any `json:"meta"` } -func (smc *SetMetaCommand) GetCommand() string { +func (smc *BlockSetMetaCommand) GetCommand() string { return BlockCommand_SetMeta } diff --git a/pkg/blockcontroller/blockcontroller.go b/pkg/blockcontroller/blockcontroller.go index e9fafb26..007190bd 100644 --- a/pkg/blockcontroller/blockcontroller.go +++ b/pkg/blockcontroller/blockcontroller.go @@ -40,7 +40,7 @@ type BlockController struct { Status string ShellProc *shellexec.ShellProc - ShellInputCh chan *InputCommand + ShellInputCh chan *BlockInputCommand } func (bc *BlockController) WithLock(f func()) { @@ -149,7 +149,7 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts) error { bc.ShellProc.Close() return err } - shellInputCh := make(chan *InputCommand) + shellInputCh := make(chan *BlockInputCommand) bc.ShellInputCh = shellInputCh go func() { defer func() { @@ -234,7 +234,7 @@ func (bc *BlockController) Run(bdata *wstore.Block) { for genCmd := range bc.InputCh { switch cmd := genCmd.(type) { - case *InputCommand: + case *BlockInputCommand: fmt.Printf("INPUT: %s | %q\n", bc.BlockId, cmd.InputData64) if bc.ShellInputCh != nil { bc.ShellInputCh <- cmd @@ -293,10 +293,10 @@ func ProcessStaticCommand(blockId string, cmdGen BlockCommand) error { ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) defer cancelFn() switch cmd := cmdGen.(type) { - case *MessageCommand: + case *BlockMessageCommand: log.Printf("MESSAGE: %s | %q\n", blockId, cmd.Message) return nil - case *SetViewCommand: + case *BlockSetViewCommand: log.Printf("SETVIEW: %s | %q\n", blockId, cmd.View) block, err := wstore.DBGet[*wstore.Block](ctx, blockId) if err != nil { @@ -308,7 +308,7 @@ func ProcessStaticCommand(blockId string, cmdGen BlockCommand) error { return fmt.Errorf("error updating block: %w", err) } return nil - case *SetMetaCommand: + case *BlockSetMetaCommand: log.Printf("SETMETA: %s | %v\n", blockId, cmd.Meta) block, err := wstore.DBGet[*wstore.Block](ctx, blockId) if err != nil { diff --git a/pkg/service/blockservice/blockservice.go b/pkg/service/blockservice/blockservice.go index b9b14548..76e91ccc 100644 --- a/pkg/service/blockservice/blockservice.go +++ b/pkg/service/blockservice/blockservice.go @@ -9,25 +9,21 @@ import ( "time" "github.com/wavetermdev/thenextwave/pkg/blockcontroller" - "github.com/wavetermdev/thenextwave/pkg/service/servicemeta" + "github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta" ) type BlockService struct{} const DefaultTimeout = 2 * time.Second -func (bs *BlockService) SendCommand_Meta() servicemeta.MethodMeta { - return servicemeta.MethodMeta{ +func (bs *BlockService) SendCommand_Meta() tsgenmeta.MethodMeta { + return tsgenmeta.MethodMeta{ Desc: "send command to block", - ArgNames: []string{"blockid", "command"}, + ArgNames: []string{"blockid", "cmd"}, } } -func (bs *BlockService) SendCommand(blockId string, cmdMap map[string]any) error { - cmd, err := blockcontroller.ParseCmdMap(cmdMap) - if err != nil { - return fmt.Errorf("error parsing command map: %w", err) - } +func (bs *BlockService) SendCommand(blockId string, cmd blockcontroller.BlockCommand) error { if strings.HasPrefix(cmd.GetCommand(), "controller:") { bc := blockcontroller.GetBlockController(blockId) if bc == nil { diff --git a/pkg/service/objectservice/objectservice.go b/pkg/service/objectservice/objectservice.go index 8d5e0d25..a65db3c7 100644 --- a/pkg/service/objectservice/objectservice.go +++ b/pkg/service/objectservice/objectservice.go @@ -12,7 +12,7 @@ import ( "time" "github.com/wavetermdev/thenextwave/pkg/blockcontroller" - "github.com/wavetermdev/thenextwave/pkg/service/servicemeta" + "github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta" "github.com/wavetermdev/thenextwave/pkg/waveobj" "github.com/wavetermdev/thenextwave/pkg/wstore" ) @@ -29,8 +29,8 @@ func parseORef(oref string) (*waveobj.ORef, error) { return &waveobj.ORef{OType: fields[0], OID: fields[1]}, nil } -func (svc *ObjectService) GetObject_Meta() servicemeta.MethodMeta { - return servicemeta.MethodMeta{ +func (svc *ObjectService) GetObject_Meta() tsgenmeta.MethodMeta { + return tsgenmeta.MethodMeta{ Desc: "get wave object by oref", ArgNames: []string{"oref"}, } @@ -50,8 +50,8 @@ func (svc *ObjectService) GetObject(orefStr string) (waveobj.WaveObj, error) { return obj, nil } -func (svc *ObjectService) GetObjects_Meta() servicemeta.MethodMeta { - return servicemeta.MethodMeta{ +func (svc *ObjectService) GetObjects_Meta() tsgenmeta.MethodMeta { + return tsgenmeta.MethodMeta{ ArgNames: []string{"orefs"}, ReturnDesc: "objects", } @@ -92,8 +92,8 @@ func updatesRtn(ctx context.Context, rtnVal map[string]any) (any, error) { return rtnVal, nil } -func (svc *ObjectService) AddTabToWorkspace_Meta() servicemeta.MethodMeta { - return servicemeta.MethodMeta{ +func (svc *ObjectService) AddTabToWorkspace_Meta() tsgenmeta.MethodMeta { + return tsgenmeta.MethodMeta{ ArgNames: []string{"uiContext", "tabName", "activateTab"}, ReturnDesc: "tabId", } @@ -120,8 +120,8 @@ func (svc *ObjectService) AddTabToWorkspace(uiContext wstore.UIContext, tabName return tab.OID, wstore.ContextGetUpdatesRtn(ctx), nil } -func (svc *ObjectService) SetActiveTab_Meta() servicemeta.MethodMeta { - return servicemeta.MethodMeta{ +func (svc *ObjectService) SetActiveTab_Meta() tsgenmeta.MethodMeta { + return tsgenmeta.MethodMeta{ ArgNames: []string{"uiContext", "tabId"}, } } @@ -158,8 +158,8 @@ func (svc *ObjectService) SetActiveTab(uiContext wstore.UIContext, tabId string) return updates, nil } -func (svc *ObjectService) CreateBlock_Meta() servicemeta.MethodMeta { - return servicemeta.MethodMeta{ +func (svc *ObjectService) CreateBlock_Meta() tsgenmeta.MethodMeta { + return tsgenmeta.MethodMeta{ ArgNames: []string{"uiContext", "blockDef", "rtOpts"}, ReturnDesc: "blockId", } @@ -185,8 +185,8 @@ func (svc *ObjectService) CreateBlock(uiContext wstore.UIContext, blockDef *wsto return blockData.OID, wstore.ContextGetUpdatesRtn(ctx), nil } -func (svc *ObjectService) DeleteBlock_Meta() servicemeta.MethodMeta { - return servicemeta.MethodMeta{ +func (svc *ObjectService) DeleteBlock_Meta() tsgenmeta.MethodMeta { + return tsgenmeta.MethodMeta{ ArgNames: []string{"uiContext", "blockId"}, } } @@ -203,8 +203,8 @@ func (svc *ObjectService) DeleteBlock(uiContext wstore.UIContext, blockId string return wstore.ContextGetUpdatesRtn(ctx), nil } -func (svc *ObjectService) CloseTab_Meta() servicemeta.MethodMeta { - return servicemeta.MethodMeta{ +func (svc *ObjectService) CloseTab_Meta() tsgenmeta.MethodMeta { + return tsgenmeta.MethodMeta{ ArgNames: []string{"uiContext", "tabId"}, } } @@ -244,8 +244,8 @@ func (svc *ObjectService) CloseTab(uiContext wstore.UIContext, tabId string) (ws return wstore.ContextGetUpdatesRtn(ctx), nil } -func (svc *ObjectService) UpdateObjectMeta_Meta() servicemeta.MethodMeta { - return servicemeta.MethodMeta{ +func (svc *ObjectService) UpdateObjectMeta_Meta() tsgenmeta.MethodMeta { + return tsgenmeta.MethodMeta{ ArgNames: []string{"uiContext", "oref", "meta"}, } } @@ -265,8 +265,8 @@ func (svc *ObjectService) UpdateObjectMeta(uiContext wstore.UIContext, orefStr s return wstore.ContextGetUpdatesRtn(ctx), nil } -func (svc *ObjectService) UpdateObject_Meta() servicemeta.MethodMeta { - return servicemeta.MethodMeta{ +func (svc *ObjectService) UpdateObject_Meta() tsgenmeta.MethodMeta { + return tsgenmeta.MethodMeta{ ArgNames: []string{"uiContext", "waveObj", "returnUpdates"}, } } diff --git a/pkg/service/service.go b/pkg/service/service.go index 5ddfc156..bc886b15 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -9,11 +9,12 @@ import ( "reflect" "strings" + "github.com/wavetermdev/thenextwave/pkg/blockcontroller" "github.com/wavetermdev/thenextwave/pkg/service/blockservice" "github.com/wavetermdev/thenextwave/pkg/service/clientservice" "github.com/wavetermdev/thenextwave/pkg/service/fileservice" "github.com/wavetermdev/thenextwave/pkg/service/objectservice" - "github.com/wavetermdev/thenextwave/pkg/service/servicemeta" + "github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta" "github.com/wavetermdev/thenextwave/pkg/waveobj" "github.com/wavetermdev/thenextwave/pkg/wstore" ) @@ -31,9 +32,10 @@ var updatesRType = reflect.TypeOf(([]wstore.WaveObjUpdate{})) var waveObjRType = reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem() var waveObjSliceRType = reflect.TypeOf([]waveobj.WaveObj{}) var waveObjMapRType = reflect.TypeOf(map[string]waveobj.WaveObj{}) -var methodMetaRType = reflect.TypeOf(servicemeta.MethodMeta{}) +var methodMetaRType = reflect.TypeOf(tsgenmeta.MethodMeta{}) var waveObjUpdateRType = reflect.TypeOf(wstore.WaveObjUpdate{}) var uiContextRType = reflect.TypeOf((*wstore.UIContext)(nil)).Elem() +var blockCommandRType = reflect.TypeOf((*blockcontroller.BlockCommand)(nil)).Elem() type WebCallType struct { Service string `json:"service"` @@ -89,12 +91,25 @@ func convertComplex(argType reflect.Type, jsonArg any) (any, error) { } func isSpecialWaveArgType(argType reflect.Type) bool { - return argType == waveObjRType || argType == waveObjSliceRType || argType == waveObjMapRType + return argType == waveObjRType || argType == waveObjSliceRType || argType == waveObjMapRType || argType == blockCommandRType +} + +func convertBlockCommand(argType reflect.Type, jsonArg any) (any, error) { + if _, ok := jsonArg.(map[string]any); !ok { + return nil, fmt.Errorf("cannot convert %T to %s", jsonArg, argType) + } + cmd, err := blockcontroller.ParseCmdMap(jsonArg.(map[string]any)) + if err != nil { + return nil, fmt.Errorf("error parsing command map: %w", err) + } + return cmd, nil } func convertSpecial(argType reflect.Type, jsonArg any) (any, error) { jsonType := reflect.TypeOf(jsonArg) - if argType == waveObjRType { + if argType == blockCommandRType { + return convertBlockCommand(argType, jsonArg) + } else if argType == waveObjRType { if jsonType.Kind() != reflect.Map { return nil, fmt.Errorf("cannot convert %T to %s", jsonArg, argType) } diff --git a/pkg/service/servicemeta/servicemeta.go b/pkg/service/servicemeta/servicemeta.go deleted file mode 100644 index a6a59fa2..00000000 --- a/pkg/service/servicemeta/servicemeta.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2024, Command Line Inc. -// SPDX-License-Identifier: Apache-2.0 - -package servicemeta - -type MethodMeta struct { - Desc string - ArgNames []string - ReturnDesc string -} diff --git a/pkg/tsgen/tsgen.go b/pkg/tsgen/tsgen.go index 4d794bc1..100e3cdb 100644 --- a/pkg/tsgen/tsgen.go +++ b/pkg/tsgen/tsgen.go @@ -10,9 +10,10 @@ import ( "reflect" "strings" + "github.com/wavetermdev/thenextwave/pkg/blockcontroller" "github.com/wavetermdev/thenextwave/pkg/eventbus" "github.com/wavetermdev/thenextwave/pkg/service" - "github.com/wavetermdev/thenextwave/pkg/service/servicemeta" + "github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta" "github.com/wavetermdev/thenextwave/pkg/waveobj" "github.com/wavetermdev/thenextwave/pkg/wstore" ) @@ -69,7 +70,7 @@ func isFieldOmitEmpty(field reflect.StructField) bool { return false } -func TypeToTSType(t reflect.Type) (string, []reflect.Type) { +func TypeToTSType(t reflect.Type, tsTypesMap map[reflect.Type]string) (string, []reflect.Type) { switch t.Kind() { case reflect.String: return "string", nil @@ -80,7 +81,7 @@ func TypeToTSType(t reflect.Type) (string, []reflect.Type) { case reflect.Bool: return "boolean", nil case reflect.Slice, reflect.Array: - elemType, subTypes := TypeToTSType(t.Elem()) + elemType, subTypes := TypeToTSType(t.Elem(), tsTypesMap) if elemType == "" { return "", nil } @@ -92,7 +93,7 @@ func TypeToTSType(t reflect.Type) (string, []reflect.Type) { if t == metaRType { return "MetaType", nil } - elemType, subTypes := TypeToTSType(t.Elem()) + elemType, subTypes := TypeToTSType(t.Elem(), tsTypesMap) if elemType == "" { return "", nil } @@ -100,10 +101,10 @@ func TypeToTSType(t reflect.Type) (string, []reflect.Type) { case reflect.Struct: return t.Name(), []reflect.Type{t} case reflect.Ptr: - return TypeToTSType(t.Elem()) + return TypeToTSType(t.Elem(), tsTypesMap) case reflect.Interface: - if t == waveObjRType { - return "WaveObj", nil + if _, ok := tsTypesMap[t]; ok { + return t.Name(), nil } return "any", nil default: @@ -115,7 +116,7 @@ var tsRenameMap = map[string]string{ "Window": "WaveWindow", } -func generateTSTypeInternal(rtype reflect.Type) (string, []reflect.Type) { +func generateTSTypeInternal(rtype reflect.Type, tsTypesMap map[reflect.Type]string) (string, []reflect.Type) { var buf bytes.Buffer waveObjType := reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem() tsTypeName := rtype.Name() @@ -149,10 +150,10 @@ func generateTSTypeInternal(rtype reflect.Type) (string, []reflect.Type) { } tsTypeTag := field.Tag.Get("tstype") if tsTypeTag != "" { - buf.WriteString(fmt.Sprintf(" %s%s: %s;\n", fieldName, optMarker, tsTypeTag)) + buf.WriteString(fmt.Sprintf(" %s%s: %s;\n", fieldName, optMarker, tsTypeTag)) continue } - tsType, fieldSubTypes := TypeToTSType(field.Type) + tsType, fieldSubTypes := TypeToTSType(field.Type, tsTypesMap) if tsType == "" { continue } @@ -179,7 +180,32 @@ func GenerateWaveObjTSType() string { func GenerateMetaType() string { return "type MetaType = {[key: string]: any}\n" +} +func GenerateTSTypeUnion(unionMeta tsgenmeta.TypeUnionMeta, tsTypeMap map[reflect.Type]string) { + rtn := generateTSTypeUnionInternal(unionMeta) + tsTypeMap[unionMeta.BaseType] = rtn + for _, rtype := range unionMeta.Types { + GenerateTSType(rtype, tsTypeMap) + } +} + +func generateTSTypeUnionInternal(unionMeta tsgenmeta.TypeUnionMeta) string { + var buf bytes.Buffer + if unionMeta.Desc != "" { + buf.WriteString(fmt.Sprintf("// %s\n", unionMeta.Desc)) + } + buf.WriteString(fmt.Sprintf("type %s = {\n", unionMeta.BaseType.Name())) + buf.WriteString(fmt.Sprintf(" %s: string;\n", unionMeta.TypeFieldName)) + buf.WriteString("} & ( ") + for idx, rtype := range unionMeta.Types { + if idx > 0 { + buf.WriteString(" | ") + } + buf.WriteString(rtype.Name()) + } + buf.WriteString(" );\n") + return buf.String() } func GenerateTSType(rtype reflect.Type, tsTypesMap map[reflect.Type]string) { @@ -212,7 +238,7 @@ func GenerateTSType(rtype reflect.Type, tsTypesMap map[reflect.Type]string) { if rtype.Kind() != reflect.Struct { return } - tsType, subTypes := generateTSTypeInternal(rtype) + tsType, subTypes := generateTSTypeInternal(rtype, tsTypesMap) tsTypesMap[rtype] = tsType for _, subType := range subTypes { GenerateTSType(subType, tsTypesMap) @@ -229,7 +255,7 @@ func hasUpdatesReturn(method reflect.Method) bool { return false } -func GenerateMethodSignature(serviceName string, method reflect.Method, meta servicemeta.MethodMeta, isFirst bool) string { +func GenerateMethodSignature(serviceName string, method reflect.Method, meta tsgenmeta.MethodMeta, isFirst bool, tsTypesMap map[reflect.Type]string) string { var sb strings.Builder mayReturnUpdates := hasUpdatesReturn(method) if (meta.Desc != "" || meta.ReturnDesc != "" || mayReturnUpdates) && !isFirst { @@ -260,7 +286,7 @@ func GenerateMethodSignature(serviceName string, method reflect.Method, meta ser if inType == contextRType || inType == uiContextRType { continue } - tsTypeName, _ := TypeToTSType(inType) + tsTypeName, _ := TypeToTSType(inType, tsTypesMap) var argName string if idx-1 < len(meta.ArgNames) { argName = meta.ArgNames[idx-1] // subtract 1 for receiver @@ -280,7 +306,7 @@ func GenerateMethodSignature(serviceName string, method reflect.Method, meta ser if outType == updatesRtnRType { continue } - tsTypeName, _ := TypeToTSType(outType) + tsTypeName, _ := TypeToTSType(outType, tsTypesMap) sb.WriteString(fmt.Sprintf("Promise<%s>", tsTypeName)) wroteRtn = true } @@ -291,11 +317,11 @@ func GenerateMethodSignature(serviceName string, method reflect.Method, meta ser return sb.String() } -func GenerateMethodBody(serviceName string, method reflect.Method, meta servicemeta.MethodMeta) string { +func GenerateMethodBody(serviceName string, method reflect.Method, meta tsgenmeta.MethodMeta) string { return fmt.Sprintf(" return WOS.callBackendService(%q, %q, Array.from(arguments))\n", serviceName, method.Name) } -func GenerateServiceClass(serviceName string, serviceObj any) string { +func GenerateServiceClass(serviceName string, serviceObj any, tsTypesMap map[reflect.Type]string) string { serviceType := reflect.TypeOf(serviceObj) var sb strings.Builder tsServiceName := serviceType.Elem().Name() @@ -309,14 +335,14 @@ func GenerateServiceClass(serviceName string, serviceObj any) string { if strings.HasSuffix(method.Name, "_Meta") { continue } - var meta servicemeta.MethodMeta + var meta tsgenmeta.MethodMeta metaMethod, found := serviceType.MethodByName(method.Name + "_Meta") if found { serviceObjVal := reflect.ValueOf(serviceObj) metaVal := metaMethod.Func.Call([]reflect.Value{serviceObjVal}) - meta = metaVal[0].Interface().(servicemeta.MethodMeta) + meta = metaVal[0].Interface().(tsgenmeta.MethodMeta) } - sb.WriteString(GenerateMethodSignature(serviceName, method, meta, isFirst)) + sb.WriteString(GenerateMethodSignature(serviceName, method, meta, isFirst, tsTypesMap)) sb.WriteString(GenerateMethodBody(serviceName, method, meta)) sb.WriteString(" }\n") isFirst = false @@ -327,6 +353,7 @@ func GenerateServiceClass(serviceName string, serviceObj any) string { } func GenerateWaveObjTypes(tsTypesMap map[reflect.Type]string) { + GenerateTSTypeUnion(blockcontroller.CommandTypeUnionMeta(), tsTypesMap) GenerateTSType(reflect.TypeOf(waveobj.ORef{}), tsTypesMap) GenerateTSType(reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem(), tsTypesMap) GenerateTSType(reflect.TypeOf(map[string]any{}), tsTypesMap) diff --git a/pkg/tsgen/tsgenmeta/tsgenmeta.go b/pkg/tsgen/tsgenmeta/tsgenmeta.go new file mode 100644 index 00000000..8e17164a --- /dev/null +++ b/pkg/tsgen/tsgenmeta/tsgenmeta.go @@ -0,0 +1,19 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package tsgenmeta + +import "reflect" + +type MethodMeta struct { + Desc string + ArgNames []string + ReturnDesc string +} + +type TypeUnionMeta struct { + BaseType reflect.Type + Desc string + TypeFieldName string + Types []reflect.Type +}