update BlockService to use the new type union feature of tsgen. strongly type the arguments to BlockService.SendCommand

This commit is contained in:
sawka 2024-06-12 13:47:13 -07:00
parent 605b9ea048
commit 083e00227e
13 changed files with 195 additions and 101 deletions

View File

@ -14,14 +14,13 @@ import (
"github.com/wavetermdev/thenextwave/pkg/util/utilfn" "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") fd, err := os.Create("frontend/types/gotypes.d.ts")
if err != nil { if err != nil {
return err return err
} }
defer fd.Close() defer fd.Close()
fmt.Fprintf(os.Stderr, "generating types file to %s\n", fd.Name()) fmt.Fprintf(os.Stderr, "generating types file to %s\n", fd.Name())
tsTypesMap := make(map[reflect.Type]string)
tsgen.GenerateWaveObjTypes(tsTypesMap) tsgen.GenerateWaveObjTypes(tsTypesMap)
err = tsgen.GenerateServiceTypes(tsTypesMap) err = tsgen.GenerateServiceTypes(tsTypesMap)
if err != nil { if err != nil {
@ -37,8 +36,8 @@ func generateTypesFile() error {
keys = append(keys, key) keys = append(keys, key)
} }
sort.Slice(keys, func(i, j int) bool { sort.Slice(keys, func(i, j int) bool {
iname, _ := tsgen.TypeToTSType(keys[i]) iname, _ := tsgen.TypeToTSType(keys[i], tsTypesMap)
jname, _ := tsgen.TypeToTSType(keys[j]) jname, _ := tsgen.TypeToTSType(keys[j], tsTypesMap)
return iname < jname return iname < jname
}) })
for _, key := range keys { for _, key := range keys {
@ -51,7 +50,7 @@ func generateTypesFile() error {
return nil return nil
} }
func generateServicesFile() error { func generateServicesFile(tsTypesMap map[reflect.Type]string) error {
fd, err := os.Create("frontend/app/store/services.ts") fd, err := os.Create("frontend/app/store/services.ts")
if err != nil { if err != nil {
return err return err
@ -65,7 +64,7 @@ func generateServicesFile() error {
orderedKeys := utilfn.GetOrderedMapKeys(service.ServiceMap) orderedKeys := utilfn.GetOrderedMapKeys(service.ServiceMap)
for _, serviceName := range orderedKeys { for _, serviceName := range orderedKeys {
serviceObj := service.ServiceMap[serviceName] serviceObj := service.ServiceMap[serviceName]
svcStr := tsgen.GenerateServiceClass(serviceName, serviceObj) svcStr := tsgen.GenerateServiceClass(serviceName, serviceObj, tsTypesMap)
fmt.Fprint(fd, svcStr) fmt.Fprint(fd, svcStr)
fmt.Fprint(fd, "\n") fmt.Fprint(fd, "\n")
} }
@ -78,12 +77,13 @@ func main() {
fmt.Fprintf(os.Stderr, "Error validating service map: %v\n", err) fmt.Fprintf(os.Stderr, "Error validating service map: %v\n", err)
os.Exit(1) os.Exit(1)
} }
err = generateTypesFile() tsTypesMap := make(map[reflect.Type]string)
err = generateTypesFile(tsTypesMap)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error generating types file: %v\n", err) fmt.Fprintf(os.Stderr, "Error generating types file: %v\n", err)
os.Exit(1) os.Exit(1)
} }
err = generateServicesFile() err = generateServicesFile(tsTypesMap)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error generating services file: %v\n", err) fmt.Fprintf(os.Stderr, "Error generating services file: %v\n", err)
os.Exit(1) os.Exit(1)

View File

@ -49,7 +49,7 @@ const Block = ({ blockId, onClose }: BlockProps) => {
} else if (blockData.view === "plot") { } else if (blockData.view === "plot") {
blockElem = <PlotView />; blockElem = <PlotView />;
} else if (blockData.view === "codeedit") { } else if (blockData.view === "codeedit") {
blockElem = <CodeEditView />; blockElem = <CodeEditView text={null} />;
} }
return ( return (
<div className="block" ref={blockRef}> <div className="block" ref={blockRef}>

View File

@ -8,7 +8,7 @@ import * as WOS from "./wos";
// blockservice.BlockService (block) // blockservice.BlockService (block)
class BlockServiceType { class BlockServiceType {
// send command to block // send command to block
SendCommand(blockid: string, command: MetaType): Promise<void> { SendCommand(blockid: string, cmd: BlockCommand): Promise<void> {
return WOS.callBackendService("block", "SendCommand", Array.from(arguments)) return WOS.callBackendService("block", "SendCommand", Array.from(arguments))
} }
} }

View File

@ -49,7 +49,10 @@ function handleResize(fitAddon: FitAddon, blockId: string, term: Terminal) {
const oldCols = term.cols; const oldCols = term.cols;
fitAddon.fit(); fitAddon.fit();
if (oldRows !== term.rows || oldCols !== term.cols) { 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); services.BlockService.SendCommand(blockId, resizeCommand);
} }
} }
@ -78,17 +81,13 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
newTerm.loadAddon(newFitAddon); newTerm.loadAddon(newFitAddon);
newTerm.open(connectElemRef.current); newTerm.open(connectElemRef.current);
newFitAddon.fit(); newFitAddon.fit();
// BlockService.SendCommand(blockId, {
// command: "controller:input",
// termsize: { rows: newTerm.rows, cols: newTerm.cols },
// });
services.BlockService.SendCommand(blockId, { services.BlockService.SendCommand(blockId, {
command: "controller:input", command: "controller:input",
termsize: { rows: newTerm.rows, cols: newTerm.cols }, termsize: { rows: newTerm.rows, cols: newTerm.cols },
}); });
newTerm.onData((data) => { newTerm.onData((data) => {
const b64data = btoa(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); services.BlockService.SendCommand(blockId, inputCmd);
}); });

View File

@ -14,6 +14,10 @@ declare global {
meta: MetaType; meta: MetaType;
}; };
type BlockCommand = {
command: string;
} & ( BlockMessageCommand | BlockInputCommand | BlockSetViewCommand | BlockSetMetaCommand );
// wstore.BlockDef // wstore.BlockDef
type BlockDef = { type BlockDef = {
controller?: string; controller?: string;
@ -22,6 +26,32 @@ declare global {
meta?: MetaType; 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 // wstore.Client
type Client = WaveObj & { type Client = WaveObj & {
mainwindowid: string; mainwindowid: string;
@ -62,7 +92,7 @@ declare global {
type MetaType = {[key: string]: any} type MetaType = {[key: string]: any}
// servicemeta.MethodMeta // tsgenmeta.MethodMeta
type MethodMeta = { type MethodMeta = {
Desc: string; Desc: string;
ArgNames: string[]; ArgNames: string[];

View File

@ -9,6 +9,7 @@ import (
"reflect" "reflect"
"github.com/wavetermdev/thenextwave/pkg/shellexec" "github.com/wavetermdev/thenextwave/pkg/shellexec"
"github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta"
) )
const CommandKey = "command" const CommandKey = "command"
@ -21,16 +22,33 @@ const (
) )
var CommandToTypeMap = map[string]reflect.Type{ var CommandToTypeMap = map[string]reflect.Type{
BlockCommand_Message: reflect.TypeOf(MessageCommand{}), BlockCommand_Message: reflect.TypeOf(BlockMessageCommand{}),
BlockCommand_Input: reflect.TypeOf(InputCommand{}), BlockCommand_Input: reflect.TypeOf(BlockInputCommand{}),
BlockCommand_SetView: reflect.TypeOf(SetViewCommand{}), BlockCommand_SetView: reflect.TypeOf(BlockSetViewCommand{}),
BlockCommand_SetMeta: reflect.TypeOf(SetMetaCommand{}), 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 { type BlockCommand interface {
GetCommand() string GetCommand() string
} }
type BlockCommandWrapper struct {
BlockCommand
}
func ParseCmdMap(cmdMap map[string]any) (BlockCommand, error) { func ParseCmdMap(cmdMap map[string]any) (BlockCommand, error) {
cmdType, ok := cmdMap[CommandKey].(string) cmdType, ok := cmdMap[CommandKey].(string)
if !ok { if !ok {
@ -52,40 +70,40 @@ func ParseCmdMap(cmdMap map[string]any) (BlockCommand, error) {
return cmd.(BlockCommand), nil return cmd.(BlockCommand), nil
} }
type MessageCommand struct { type BlockMessageCommand struct {
Command string `json:"command"` Command string `json:"command" tstype:"\"message\""`
Message string `json:"message"` Message string `json:"message"`
} }
func (mc *MessageCommand) GetCommand() string { func (mc *BlockMessageCommand) GetCommand() string {
return BlockCommand_Message return BlockCommand_Message
} }
type InputCommand struct { type BlockInputCommand struct {
Command string `json:"command"` Command string `json:"command" tstype:"\"controller:input\""`
InputData64 string `json:"inputdata64"` InputData64 string `json:"inputdata64,omitempty"`
SigName string `json:"signame,omitempty"` SigName string `json:"signame,omitempty"`
TermSize *shellexec.TermSize `json:"termsize,omitempty"` TermSize *shellexec.TermSize `json:"termsize,omitempty"`
} }
func (ic *InputCommand) GetCommand() string { func (ic *BlockInputCommand) GetCommand() string {
return BlockCommand_Input return BlockCommand_Input
} }
type SetViewCommand struct { type BlockSetViewCommand struct {
Command string `json:"command"` Command string `json:"command" tstype:"\"setview\""`
View string `json:"view"` View string `json:"view"`
} }
func (svc *SetViewCommand) GetCommand() string { func (svc *BlockSetViewCommand) GetCommand() string {
return BlockCommand_SetView return BlockCommand_SetView
} }
type SetMetaCommand struct { type BlockSetMetaCommand struct {
Command string `json:"command"` Command string `json:"command" tstype:"\"setmeta\""`
Meta map[string]any `json:"meta"` Meta map[string]any `json:"meta"`
} }
func (smc *SetMetaCommand) GetCommand() string { func (smc *BlockSetMetaCommand) GetCommand() string {
return BlockCommand_SetMeta return BlockCommand_SetMeta
} }

View File

@ -40,7 +40,7 @@ type BlockController struct {
Status string Status string
ShellProc *shellexec.ShellProc ShellProc *shellexec.ShellProc
ShellInputCh chan *InputCommand ShellInputCh chan *BlockInputCommand
} }
func (bc *BlockController) WithLock(f func()) { func (bc *BlockController) WithLock(f func()) {
@ -149,7 +149,7 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts) error {
bc.ShellProc.Close() bc.ShellProc.Close()
return err return err
} }
shellInputCh := make(chan *InputCommand) shellInputCh := make(chan *BlockInputCommand)
bc.ShellInputCh = shellInputCh bc.ShellInputCh = shellInputCh
go func() { go func() {
defer func() { defer func() {
@ -234,7 +234,7 @@ func (bc *BlockController) Run(bdata *wstore.Block) {
for genCmd := range bc.InputCh { for genCmd := range bc.InputCh {
switch cmd := genCmd.(type) { switch cmd := genCmd.(type) {
case *InputCommand: case *BlockInputCommand:
fmt.Printf("INPUT: %s | %q\n", bc.BlockId, cmd.InputData64) fmt.Printf("INPUT: %s | %q\n", bc.BlockId, cmd.InputData64)
if bc.ShellInputCh != nil { if bc.ShellInputCh != nil {
bc.ShellInputCh <- cmd bc.ShellInputCh <- cmd
@ -293,10 +293,10 @@ func ProcessStaticCommand(blockId string, cmdGen BlockCommand) error {
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn() defer cancelFn()
switch cmd := cmdGen.(type) { switch cmd := cmdGen.(type) {
case *MessageCommand: case *BlockMessageCommand:
log.Printf("MESSAGE: %s | %q\n", blockId, cmd.Message) log.Printf("MESSAGE: %s | %q\n", blockId, cmd.Message)
return nil return nil
case *SetViewCommand: case *BlockSetViewCommand:
log.Printf("SETVIEW: %s | %q\n", blockId, cmd.View) log.Printf("SETVIEW: %s | %q\n", blockId, cmd.View)
block, err := wstore.DBGet[*wstore.Block](ctx, blockId) block, err := wstore.DBGet[*wstore.Block](ctx, blockId)
if err != nil { if err != nil {
@ -308,7 +308,7 @@ func ProcessStaticCommand(blockId string, cmdGen BlockCommand) error {
return fmt.Errorf("error updating block: %w", err) return fmt.Errorf("error updating block: %w", err)
} }
return nil return nil
case *SetMetaCommand: case *BlockSetMetaCommand:
log.Printf("SETMETA: %s | %v\n", blockId, cmd.Meta) log.Printf("SETMETA: %s | %v\n", blockId, cmd.Meta)
block, err := wstore.DBGet[*wstore.Block](ctx, blockId) block, err := wstore.DBGet[*wstore.Block](ctx, blockId)
if err != nil { if err != nil {

View File

@ -9,25 +9,21 @@ import (
"time" "time"
"github.com/wavetermdev/thenextwave/pkg/blockcontroller" "github.com/wavetermdev/thenextwave/pkg/blockcontroller"
"github.com/wavetermdev/thenextwave/pkg/service/servicemeta" "github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta"
) )
type BlockService struct{} type BlockService struct{}
const DefaultTimeout = 2 * time.Second const DefaultTimeout = 2 * time.Second
func (bs *BlockService) SendCommand_Meta() servicemeta.MethodMeta { func (bs *BlockService) SendCommand_Meta() tsgenmeta.MethodMeta {
return servicemeta.MethodMeta{ return tsgenmeta.MethodMeta{
Desc: "send command to block", Desc: "send command to block",
ArgNames: []string{"blockid", "command"}, ArgNames: []string{"blockid", "cmd"},
} }
} }
func (bs *BlockService) SendCommand(blockId string, cmdMap map[string]any) error { func (bs *BlockService) SendCommand(blockId string, cmd blockcontroller.BlockCommand) error {
cmd, err := blockcontroller.ParseCmdMap(cmdMap)
if err != nil {
return fmt.Errorf("error parsing command map: %w", err)
}
if strings.HasPrefix(cmd.GetCommand(), "controller:") { if strings.HasPrefix(cmd.GetCommand(), "controller:") {
bc := blockcontroller.GetBlockController(blockId) bc := blockcontroller.GetBlockController(blockId)
if bc == nil { if bc == nil {

View File

@ -12,7 +12,7 @@ import (
"time" "time"
"github.com/wavetermdev/thenextwave/pkg/blockcontroller" "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/waveobj"
"github.com/wavetermdev/thenextwave/pkg/wstore" "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 return &waveobj.ORef{OType: fields[0], OID: fields[1]}, nil
} }
func (svc *ObjectService) GetObject_Meta() servicemeta.MethodMeta { func (svc *ObjectService) GetObject_Meta() tsgenmeta.MethodMeta {
return servicemeta.MethodMeta{ return tsgenmeta.MethodMeta{
Desc: "get wave object by oref", Desc: "get wave object by oref",
ArgNames: []string{"oref"}, ArgNames: []string{"oref"},
} }
@ -50,8 +50,8 @@ func (svc *ObjectService) GetObject(orefStr string) (waveobj.WaveObj, error) {
return obj, nil return obj, nil
} }
func (svc *ObjectService) GetObjects_Meta() servicemeta.MethodMeta { func (svc *ObjectService) GetObjects_Meta() tsgenmeta.MethodMeta {
return servicemeta.MethodMeta{ return tsgenmeta.MethodMeta{
ArgNames: []string{"orefs"}, ArgNames: []string{"orefs"},
ReturnDesc: "objects", ReturnDesc: "objects",
} }
@ -92,8 +92,8 @@ func updatesRtn(ctx context.Context, rtnVal map[string]any) (any, error) {
return rtnVal, nil return rtnVal, nil
} }
func (svc *ObjectService) AddTabToWorkspace_Meta() servicemeta.MethodMeta { func (svc *ObjectService) AddTabToWorkspace_Meta() tsgenmeta.MethodMeta {
return servicemeta.MethodMeta{ return tsgenmeta.MethodMeta{
ArgNames: []string{"uiContext", "tabName", "activateTab"}, ArgNames: []string{"uiContext", "tabName", "activateTab"},
ReturnDesc: "tabId", ReturnDesc: "tabId",
} }
@ -120,8 +120,8 @@ func (svc *ObjectService) AddTabToWorkspace(uiContext wstore.UIContext, tabName
return tab.OID, wstore.ContextGetUpdatesRtn(ctx), nil return tab.OID, wstore.ContextGetUpdatesRtn(ctx), nil
} }
func (svc *ObjectService) SetActiveTab_Meta() servicemeta.MethodMeta { func (svc *ObjectService) SetActiveTab_Meta() tsgenmeta.MethodMeta {
return servicemeta.MethodMeta{ return tsgenmeta.MethodMeta{
ArgNames: []string{"uiContext", "tabId"}, ArgNames: []string{"uiContext", "tabId"},
} }
} }
@ -158,8 +158,8 @@ func (svc *ObjectService) SetActiveTab(uiContext wstore.UIContext, tabId string)
return updates, nil return updates, nil
} }
func (svc *ObjectService) CreateBlock_Meta() servicemeta.MethodMeta { func (svc *ObjectService) CreateBlock_Meta() tsgenmeta.MethodMeta {
return servicemeta.MethodMeta{ return tsgenmeta.MethodMeta{
ArgNames: []string{"uiContext", "blockDef", "rtOpts"}, ArgNames: []string{"uiContext", "blockDef", "rtOpts"},
ReturnDesc: "blockId", ReturnDesc: "blockId",
} }
@ -185,8 +185,8 @@ func (svc *ObjectService) CreateBlock(uiContext wstore.UIContext, blockDef *wsto
return blockData.OID, wstore.ContextGetUpdatesRtn(ctx), nil return blockData.OID, wstore.ContextGetUpdatesRtn(ctx), nil
} }
func (svc *ObjectService) DeleteBlock_Meta() servicemeta.MethodMeta { func (svc *ObjectService) DeleteBlock_Meta() tsgenmeta.MethodMeta {
return servicemeta.MethodMeta{ return tsgenmeta.MethodMeta{
ArgNames: []string{"uiContext", "blockId"}, ArgNames: []string{"uiContext", "blockId"},
} }
} }
@ -203,8 +203,8 @@ func (svc *ObjectService) DeleteBlock(uiContext wstore.UIContext, blockId string
return wstore.ContextGetUpdatesRtn(ctx), nil return wstore.ContextGetUpdatesRtn(ctx), nil
} }
func (svc *ObjectService) CloseTab_Meta() servicemeta.MethodMeta { func (svc *ObjectService) CloseTab_Meta() tsgenmeta.MethodMeta {
return servicemeta.MethodMeta{ return tsgenmeta.MethodMeta{
ArgNames: []string{"uiContext", "tabId"}, ArgNames: []string{"uiContext", "tabId"},
} }
} }
@ -244,8 +244,8 @@ func (svc *ObjectService) CloseTab(uiContext wstore.UIContext, tabId string) (ws
return wstore.ContextGetUpdatesRtn(ctx), nil return wstore.ContextGetUpdatesRtn(ctx), nil
} }
func (svc *ObjectService) UpdateObjectMeta_Meta() servicemeta.MethodMeta { func (svc *ObjectService) UpdateObjectMeta_Meta() tsgenmeta.MethodMeta {
return servicemeta.MethodMeta{ return tsgenmeta.MethodMeta{
ArgNames: []string{"uiContext", "oref", "meta"}, ArgNames: []string{"uiContext", "oref", "meta"},
} }
} }
@ -265,8 +265,8 @@ func (svc *ObjectService) UpdateObjectMeta(uiContext wstore.UIContext, orefStr s
return wstore.ContextGetUpdatesRtn(ctx), nil return wstore.ContextGetUpdatesRtn(ctx), nil
} }
func (svc *ObjectService) UpdateObject_Meta() servicemeta.MethodMeta { func (svc *ObjectService) UpdateObject_Meta() tsgenmeta.MethodMeta {
return servicemeta.MethodMeta{ return tsgenmeta.MethodMeta{
ArgNames: []string{"uiContext", "waveObj", "returnUpdates"}, ArgNames: []string{"uiContext", "waveObj", "returnUpdates"},
} }
} }

View File

@ -9,11 +9,12 @@ import (
"reflect" "reflect"
"strings" "strings"
"github.com/wavetermdev/thenextwave/pkg/blockcontroller"
"github.com/wavetermdev/thenextwave/pkg/service/blockservice" "github.com/wavetermdev/thenextwave/pkg/service/blockservice"
"github.com/wavetermdev/thenextwave/pkg/service/clientservice" "github.com/wavetermdev/thenextwave/pkg/service/clientservice"
"github.com/wavetermdev/thenextwave/pkg/service/fileservice" "github.com/wavetermdev/thenextwave/pkg/service/fileservice"
"github.com/wavetermdev/thenextwave/pkg/service/objectservice" "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/waveobj"
"github.com/wavetermdev/thenextwave/pkg/wstore" "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 waveObjRType = reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem()
var waveObjSliceRType = reflect.TypeOf([]waveobj.WaveObj{}) var waveObjSliceRType = reflect.TypeOf([]waveobj.WaveObj{})
var waveObjMapRType = reflect.TypeOf(map[string]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 waveObjUpdateRType = reflect.TypeOf(wstore.WaveObjUpdate{})
var uiContextRType = reflect.TypeOf((*wstore.UIContext)(nil)).Elem() var uiContextRType = reflect.TypeOf((*wstore.UIContext)(nil)).Elem()
var blockCommandRType = reflect.TypeOf((*blockcontroller.BlockCommand)(nil)).Elem()
type WebCallType struct { type WebCallType struct {
Service string `json:"service"` Service string `json:"service"`
@ -89,12 +91,25 @@ func convertComplex(argType reflect.Type, jsonArg any) (any, error) {
} }
func isSpecialWaveArgType(argType reflect.Type) bool { 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) { func convertSpecial(argType reflect.Type, jsonArg any) (any, error) {
jsonType := reflect.TypeOf(jsonArg) jsonType := reflect.TypeOf(jsonArg)
if argType == waveObjRType { if argType == blockCommandRType {
return convertBlockCommand(argType, jsonArg)
} else if argType == waveObjRType {
if jsonType.Kind() != reflect.Map { if jsonType.Kind() != reflect.Map {
return nil, fmt.Errorf("cannot convert %T to %s", jsonArg, argType) return nil, fmt.Errorf("cannot convert %T to %s", jsonArg, argType)
} }

View File

@ -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
}

View File

@ -10,9 +10,10 @@ import (
"reflect" "reflect"
"strings" "strings"
"github.com/wavetermdev/thenextwave/pkg/blockcontroller"
"github.com/wavetermdev/thenextwave/pkg/eventbus" "github.com/wavetermdev/thenextwave/pkg/eventbus"
"github.com/wavetermdev/thenextwave/pkg/service" "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/waveobj"
"github.com/wavetermdev/thenextwave/pkg/wstore" "github.com/wavetermdev/thenextwave/pkg/wstore"
) )
@ -69,7 +70,7 @@ func isFieldOmitEmpty(field reflect.StructField) bool {
return false 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() { switch t.Kind() {
case reflect.String: case reflect.String:
return "string", nil return "string", nil
@ -80,7 +81,7 @@ func TypeToTSType(t reflect.Type) (string, []reflect.Type) {
case reflect.Bool: case reflect.Bool:
return "boolean", nil return "boolean", nil
case reflect.Slice, reflect.Array: case reflect.Slice, reflect.Array:
elemType, subTypes := TypeToTSType(t.Elem()) elemType, subTypes := TypeToTSType(t.Elem(), tsTypesMap)
if elemType == "" { if elemType == "" {
return "", nil return "", nil
} }
@ -92,7 +93,7 @@ func TypeToTSType(t reflect.Type) (string, []reflect.Type) {
if t == metaRType { if t == metaRType {
return "MetaType", nil return "MetaType", nil
} }
elemType, subTypes := TypeToTSType(t.Elem()) elemType, subTypes := TypeToTSType(t.Elem(), tsTypesMap)
if elemType == "" { if elemType == "" {
return "", nil return "", nil
} }
@ -100,10 +101,10 @@ func TypeToTSType(t reflect.Type) (string, []reflect.Type) {
case reflect.Struct: case reflect.Struct:
return t.Name(), []reflect.Type{t} return t.Name(), []reflect.Type{t}
case reflect.Ptr: case reflect.Ptr:
return TypeToTSType(t.Elem()) return TypeToTSType(t.Elem(), tsTypesMap)
case reflect.Interface: case reflect.Interface:
if t == waveObjRType { if _, ok := tsTypesMap[t]; ok {
return "WaveObj", nil return t.Name(), nil
} }
return "any", nil return "any", nil
default: default:
@ -115,7 +116,7 @@ var tsRenameMap = map[string]string{
"Window": "WaveWindow", "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 var buf bytes.Buffer
waveObjType := reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem() waveObjType := reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem()
tsTypeName := rtype.Name() tsTypeName := rtype.Name()
@ -149,10 +150,10 @@ func generateTSTypeInternal(rtype reflect.Type) (string, []reflect.Type) {
} }
tsTypeTag := field.Tag.Get("tstype") tsTypeTag := field.Tag.Get("tstype")
if tsTypeTag != "" { 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 continue
} }
tsType, fieldSubTypes := TypeToTSType(field.Type) tsType, fieldSubTypes := TypeToTSType(field.Type, tsTypesMap)
if tsType == "" { if tsType == "" {
continue continue
} }
@ -179,7 +180,32 @@ func GenerateWaveObjTSType() string {
func GenerateMetaType() string { func GenerateMetaType() string {
return "type MetaType = {[key: string]: any}\n" 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) { 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 { if rtype.Kind() != reflect.Struct {
return return
} }
tsType, subTypes := generateTSTypeInternal(rtype) tsType, subTypes := generateTSTypeInternal(rtype, tsTypesMap)
tsTypesMap[rtype] = tsType tsTypesMap[rtype] = tsType
for _, subType := range subTypes { for _, subType := range subTypes {
GenerateTSType(subType, tsTypesMap) GenerateTSType(subType, tsTypesMap)
@ -229,7 +255,7 @@ func hasUpdatesReturn(method reflect.Method) bool {
return false 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 var sb strings.Builder
mayReturnUpdates := hasUpdatesReturn(method) mayReturnUpdates := hasUpdatesReturn(method)
if (meta.Desc != "" || meta.ReturnDesc != "" || mayReturnUpdates) && !isFirst { 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 { if inType == contextRType || inType == uiContextRType {
continue continue
} }
tsTypeName, _ := TypeToTSType(inType) tsTypeName, _ := TypeToTSType(inType, tsTypesMap)
var argName string var argName string
if idx-1 < len(meta.ArgNames) { if idx-1 < len(meta.ArgNames) {
argName = meta.ArgNames[idx-1] // subtract 1 for receiver 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 { if outType == updatesRtnRType {
continue continue
} }
tsTypeName, _ := TypeToTSType(outType) tsTypeName, _ := TypeToTSType(outType, tsTypesMap)
sb.WriteString(fmt.Sprintf("Promise<%s>", tsTypeName)) sb.WriteString(fmt.Sprintf("Promise<%s>", tsTypeName))
wroteRtn = true wroteRtn = true
} }
@ -291,11 +317,11 @@ func GenerateMethodSignature(serviceName string, method reflect.Method, meta ser
return sb.String() 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) 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) serviceType := reflect.TypeOf(serviceObj)
var sb strings.Builder var sb strings.Builder
tsServiceName := serviceType.Elem().Name() tsServiceName := serviceType.Elem().Name()
@ -309,14 +335,14 @@ func GenerateServiceClass(serviceName string, serviceObj any) string {
if strings.HasSuffix(method.Name, "_Meta") { if strings.HasSuffix(method.Name, "_Meta") {
continue continue
} }
var meta servicemeta.MethodMeta var meta tsgenmeta.MethodMeta
metaMethod, found := serviceType.MethodByName(method.Name + "_Meta") metaMethod, found := serviceType.MethodByName(method.Name + "_Meta")
if found { if found {
serviceObjVal := reflect.ValueOf(serviceObj) serviceObjVal := reflect.ValueOf(serviceObj)
metaVal := metaMethod.Func.Call([]reflect.Value{serviceObjVal}) 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(GenerateMethodBody(serviceName, method, meta))
sb.WriteString(" }\n") sb.WriteString(" }\n")
isFirst = false isFirst = false
@ -327,6 +353,7 @@ func GenerateServiceClass(serviceName string, serviceObj any) string {
} }
func GenerateWaveObjTypes(tsTypesMap map[reflect.Type]string) { func GenerateWaveObjTypes(tsTypesMap map[reflect.Type]string) {
GenerateTSTypeUnion(blockcontroller.CommandTypeUnionMeta(), tsTypesMap)
GenerateTSType(reflect.TypeOf(waveobj.ORef{}), tsTypesMap) GenerateTSType(reflect.TypeOf(waveobj.ORef{}), tsTypesMap)
GenerateTSType(reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem(), tsTypesMap) GenerateTSType(reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem(), tsTypesMap)
GenerateTSType(reflect.TypeOf(map[string]any{}), tsTypesMap) GenerateTSType(reflect.TypeOf(map[string]any{}), tsTypesMap)

View File

@ -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
}