commit 5d84d26c3abbe6a4f0064debd685a1cfef83670a Author: Dan Jones Date: Fri Dec 27 15:19:49 2024 -0600 🎉 Commit of go-promises diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f1c37ad --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.task/ diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..f469ccf --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,26 @@ +# https://taskfile.dev + +version: '3' + +tasks: + wasm-compile: + desc: "Compile wasm" + sources: + - '**/*go' + generates: + - internal/assets/app.wasm + cmds: + - GOOS=js GOARCH=wasm go build -o internal/assets/app.wasm ./internal/cmd/wasm/ + get-wasm-exec: + desc: "Copies wasm_exec.js into internal/assets" + generates: + - internal/assets/wasm_exec.js + cmds: + - cp -v "$(go env GOROOT)"/misc/wasm/wasm_exec.js internal/assets/ + run-server: + desc: "Run HTTP server" + deps: + - get-wasm-exec + - wasm-compile + cmds: + - go run ./internal/cmd/server/ diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..c5af77d --- /dev/null +++ b/errors.go @@ -0,0 +1,10 @@ +package promises + +import "syscall/js" + +func GoErrorToJSError(err error) js.Value { + if err == nil { + return js.Null() + } + return NewGlobal("Error", err.Error()) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..643b4d0 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module codeberg.org/danjones000/go-promises + +go 1.23.4 diff --git a/internal/add.go b/internal/add.go new file mode 100644 index 0000000..0b04b79 --- /dev/null +++ b/internal/add.go @@ -0,0 +1,5 @@ +package internal + +func Add(a, b int) int { + return a + b +} diff --git a/internal/assets/.gitignore b/internal/assets/.gitignore new file mode 100644 index 0000000..3edfba4 --- /dev/null +++ b/internal/assets/.gitignore @@ -0,0 +1,2 @@ +app.wasm +wasm_exec.js diff --git a/internal/assets/index.html b/internal/assets/index.html new file mode 100644 index 0000000..a465f44 --- /dev/null +++ b/internal/assets/index.html @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/internal/cmd/server/main.go b/internal/cmd/server/main.go new file mode 100644 index 0000000..089a5e8 --- /dev/null +++ b/internal/cmd/server/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "fmt" + "net/http" +) + +func main() { + err := http.ListenAndServe(":9090", http.FileServer(http.Dir("./internal/assets"))) + if err != nil { + fmt.Println("Failed to start server", err) + return + } +} diff --git a/internal/cmd/wasm/main.go b/internal/cmd/wasm/main.go new file mode 100644 index 0000000..bff7177 --- /dev/null +++ b/internal/cmd/wasm/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "errors" + "fmt" + "syscall/js" + + promises "codeberg.org/danjones000/go-promises" + "codeberg.org/danjones000/go-promises/internal" +) + +func main() { + fmt.Println("Some go") + + js.Global().Set("add", promises.PromisifyGoFunc(func(_ js.Value, args []js.Value) (any, error) { + if len(args) < 2 { + return nil, errors.New("add called with too few arguments") + } + a := args[0] + b := args[1] + + if a.Type() != js.TypeNumber { + return nil, fmt.Errorf("First argument must be a number. %s given", a.Type()) + } + + if b.Type() != js.TypeNumber { + return nil, fmt.Errorf("secong argument must be a number. %s given", b.Type()) + } + + return internal.Add(a.Int(), b.Int()), nil + })) + + js.Global().Set("getUser", promises.PromisifyGoFunc(func(this js.Value, args []js.Value) (any, error) { + if len(args) < 2 { + return nil, errors.New("getUser called with too few arguments") + } + name := args[0] + age := args[1] + + if name.Type() != js.TypeString { + return nil, fmt.Errorf("First argument must be a string. %s given", name.Type()) + } + if age.Type() != js.TypeNumber { + return nil, fmt.Errorf("Second argument must be a string. %s given", age.Type()) + } + return internal.GetUser(name.String(), age.Int()), nil + })) + + <-make(chan struct{}) +} diff --git a/internal/struct.go b/internal/struct.go new file mode 100644 index 0000000..9daef80 --- /dev/null +++ b/internal/struct.go @@ -0,0 +1,10 @@ +package internal + +type User struct { + Name string `json:"name"` + Age int `json:"age"` +} + +func GetUser(name string, age int) User { + return User{name, age} +} diff --git a/new.go b/new.go new file mode 100644 index 0000000..6c62c0a --- /dev/null +++ b/new.go @@ -0,0 +1,8 @@ +package promises + +import "syscall/js" + +func NewGlobal(thing string, args ...any) js.Value { + newThing := js.Global().Get(thing) + return newThing.New(args...) +} diff --git a/promisify.go b/promisify.go new file mode 100644 index 0000000..88aad2e --- /dev/null +++ b/promisify.go @@ -0,0 +1,28 @@ +package promises + +import "syscall/js" + +type JSWrapper func(this js.Value, args []js.Value) (any, error) + +func PromisifyGoFunc(caller JSWrapper) js.Func { + return js.FuncOf(func(this js.Value, args []js.Value) any { + return NewGlobal("Promise", js.FuncOf(func(_ js.Value, promArgs []js.Value) any { + resolve := promArgs[0] + reject := promArgs[1] + go func() { + val, err := caller(this, args) + if err != nil { + reject.Invoke(GoErrorToJSError(err)) + } else { + jsVal, jsErr := ValueOf(val) + if jsErr != nil { + reject.Invoke(GoErrorToJSError(jsErr)) + } else { + resolve.Invoke(jsVal) + } + } + }() + return nil + })) + }) +} diff --git a/value.go b/value.go new file mode 100644 index 0000000..d149ab3 --- /dev/null +++ b/value.go @@ -0,0 +1,67 @@ +package promises + +import ( + "encoding/json" + "fmt" + "syscall/js" +) + +func ValueOf(val any) (js.Value, error) { + switch v := val.(type) { + case js.Value: + return v, nil + case js.Func: + return js.ValueOf(v), nil + case nil: + return js.Null(), nil + case bool: + return js.ValueOf(v), nil + case int: + return js.ValueOf(v), nil + case int8: + return js.ValueOf(v), nil + case int16: + return js.ValueOf(v), nil + case int32: + return js.ValueOf(v), nil + case int64: + return js.ValueOf(v), nil + case uint: + return js.ValueOf(v), nil + case uint8: + return js.ValueOf(v), nil + case uint16: + return js.ValueOf(v), nil + case uint32: + return js.ValueOf(v), nil + case uint64: + return js.ValueOf(v), nil + case float32: + return js.ValueOf(v), nil + case float64: + return js.ValueOf(v), nil + case string: + return js.ValueOf(v), nil + case []any: + return js.ValueOf(v), nil + case map[string]any: + return js.ValueOf(v), nil + case []byte: + return js.ValueOf(string(v)), nil + default: + by, marshalErr := json.Marshal(val) + if marshalErr == nil { + m := make(map[string]any) + unmarshallErr := json.Unmarshal(by, &m) + if unmarshallErr == nil { + return js.ValueOf(m), nil + } + s := []any{} + unmarshallErr = json.Unmarshal(by, &s) + if unmarshallErr == nil { + return js.ValueOf(s), nil + } + } + } + return js.Value{}, fmt.Errorf("unable to create a JS value for %T", val) +}