Update pkg to same Node addon Header #8

Merged
Sirherobrine23 merged 2 commits from rewrite_pkg into main 2025-04-27 06:59:22 +00:00
61 changed files with 13715 additions and 1149 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.node

View File

@ -1,6 +1,7 @@
MIT License MIT License
Copyright (c) 2022 Akshay Ganeshen Copyright (c) 2022 Akshay Ganeshen
Copyright (c) 2024 Matheus Sampaio Queiroga
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,82 +1,3 @@
# napi-go # napi-go
A Go library for building Node.js Native Addons using Node-API. A Go library for building Node.js Native Addons using Node-API.
## Usage
Use `go get` to install the library:
```sh
go get -u github.com/akshayganeshen/napi-go
```
Then use the library to define handlers:
```go
package handlers
import "github.com/akshayganeshen/napi-go"
func MyHandler(env napi.Env, info napi.CallbackInfo) napi.Value {
return nil
}
```
Next, create a `main.go` that registers all module exports:
```go
package main
import "github.com/akshayganeshen/napi-go/entry"
func init() {
entry.Export("myHandler", MyHandler)
}
func main() {}
```
Finally, build the Node.js addon using `go build`:
```sh
go build -buildmode=c-shared -o "example.node" .
```
The output `.node` file can now be imported via `require`:
```js
const example = require("./example.node");
example.myHandler();
```
### JS Helpers
In addition to the Node-API exposed via package `napi`, the `napi-go/js`
package provides functions similar to the `syscall/js` standard library.
```go
package main
import (
"github.com/akshayganeshen/napi-go/entry"
"github.com/akshayganeshen/napi-go/js"
)
func init() {
entry.Export("myCallback", js.AsCallback(MyCallback))
}
func MyCallback(env js.Env, this js.Value, args []js.Value) any {
return map[string]any{
"message": "hello world",
"args": args,
}
}
func main() {}
```
## Examples
Check out the example addons in [`docs/examples`](docs/examples).

85
array.go Normal file
View File

@ -0,0 +1,85 @@
package napi
import (
"iter"
"sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi"
)
type Array struct{ value }
// Convert [ValueType] to [*Array].
func ToArray(o ValueType) *Array { return &Array{o} }
// Create Array.
func CreateArray(env EnvType, size ...int) (*Array, error) {
sizeOf := 0
if len(size) > 0 {
sizeOf = size[0]
}
napiValue, err := napi.Value(nil), error(nil)
if sizeOf == 0 {
napiValue, err = mustValueErr(napi.CreateArray(env.NapiValue()))
} else {
napiValue, err = mustValueErr(napi.CreateArrayWithLength(env.NapiValue(), sizeOf))
}
// Check error exists
if err != nil {
return nil, err
}
return ToArray(N_APIValue(env, napiValue)), nil
}
// Get array length.
func (arr *Array) Length() (int, error) {
return mustValueErr(napi.GetArrayLength(arr.NapiEnv(), arr.NapiValue()))
}
// Delete index elemente from array.
func (arr *Array) Delete(index int) (bool, error) {
return mustValueErr(napi.DeleteElement(arr.NapiEnv(), arr.NapiValue(), index))
}
// Set value in index
func (arr *Array) Set(index int, value ValueType) error {
return singleMustValueErr(napi.SetElement(arr.NapiEnv(), arr.NapiValue(), index, value.NapiValue()))
}
// Get Value from index
func (arr *Array) Get(index int) (ValueType, error) {
napiValue, err := mustValueErr(napi.GetElement(arr.NapiEnv(), arr.NapiValue(), index))
if err != nil {
return nil, err
}
return N_APIValue(arr.Env(), napiValue), nil
}
// Get values with [iter.Seq]
func (arr *Array) Seq() iter.Seq[ValueType] {
length, err := arr.Length()
if err != nil {
return nil
}
return func(yield func(ValueType) bool) {
for index := range length {
if value, err := arr.Get(index); err == nil {
if !yield(value) {
return
}
}
}
}
}
func (arr *Array) Append(values ...ValueType) error {
length, err := arr.Length()
if err != nil {
return err
}
for valueIndex := range values {
if err = arr.Set(length+valueIndex, values[valueIndex]); err != nil {
return err
}
}
return nil
}

20
boolean.go Normal file
View File

@ -0,0 +1,20 @@
package napi
import "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi"
type Boolean struct{ value }
// Convert [ValueType] to [*Boolean]
func ToBoolean(o ValueType) *Boolean { return &Boolean{o} }
func CreateBoolean(env EnvType, value bool) (*Boolean, error) {
v, err := mustValueErr(napi.GetBoolean(env.NapiValue(), value))
if err != nil {
return nil, err
}
return ToBoolean(N_APIValue(env, v)), nil
}
func (bo *Boolean) Value() (bool, error) {
return mustValueErr(napi.GetValueBool(bo.NapiEnv(), bo.NapiValue()))
}

36
buffer.go Normal file
View File

@ -0,0 +1,36 @@
package napi
import "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi"
type Buffer struct{ value }
// Convert [ValueType] to [*Buffer].
func ToBuffer(o ValueType) *Buffer { return &Buffer{o} }
// Create new Buffer with length
func CreateBuffer(env EnvType, length int) (*Buffer, error) {
napiValue, err := mustValueErr(napi.CreateBuffer(env.NapiValue(), length))
if err != nil {
return nil, err
}
return ToBuffer(N_APIValue(env, napiValue)), nil
}
// Copy []byte to Node::Buffer struct
func CopyBuffer(env EnvType, buff []byte) (*Buffer, error) {
napiValue, err := mustValueErr(napi.CreateBufferCopy(env.NapiValue(), buff))
if err != nil {
return nil, err
}
return ToBuffer(N_APIValue(env, napiValue)), nil
}
// Get size of buffer
func (buff *Buffer) Length() (int, error) {
return mustValueErr(napi.GetBufferInfoSize(buff.NapiEnv(), buff.NapiValue()))
}
// return []byte from Buffer value
func (buff *Buffer) Data() ([]byte, error) {
return mustValueErr(napi.GetBufferInfoData(buff.NapiEnv(), buff.NapiValue()))
}

186
c++/napi-inl.deprecated.h Normal file
View File

@ -0,0 +1,186 @@
#ifndef SRC_NAPI_INL_DEPRECATED_H_
#define SRC_NAPI_INL_DEPRECATED_H_
////////////////////////////////////////////////////////////////////////////////
// PropertyDescriptor class
////////////////////////////////////////////////////////////////////////////////
template <typename Getter>
inline PropertyDescriptor PropertyDescriptor::Accessor(
const char* utf8name,
Getter getter,
napi_property_attributes attributes,
void* /*data*/) {
using CbData = details::CallbackData<Getter, Napi::Value>;
// TODO: Delete when the function is destroyed
auto callbackData = new CbData({getter, nullptr});
return PropertyDescriptor({utf8name,
nullptr,
nullptr,
CbData::Wrapper,
nullptr,
nullptr,
attributes,
callbackData});
}
template <typename Getter>
inline PropertyDescriptor PropertyDescriptor::Accessor(
const std::string& utf8name,
Getter getter,
napi_property_attributes attributes,
void* data) {
return Accessor(utf8name.c_str(), getter, attributes, data);
}
template <typename Getter>
inline PropertyDescriptor PropertyDescriptor::Accessor(
napi_value name,
Getter getter,
napi_property_attributes attributes,
void* /*data*/) {
using CbData = details::CallbackData<Getter, Napi::Value>;
// TODO: Delete when the function is destroyed
auto callbackData = new CbData({getter, nullptr});
return PropertyDescriptor({nullptr,
name,
nullptr,
CbData::Wrapper,
nullptr,
nullptr,
attributes,
callbackData});
}
template <typename Getter>
inline PropertyDescriptor PropertyDescriptor::Accessor(
Name name, Getter getter, napi_property_attributes attributes, void* data) {
napi_value nameValue = name;
return PropertyDescriptor::Accessor(nameValue, getter, attributes, data);
}
template <typename Getter, typename Setter>
inline PropertyDescriptor PropertyDescriptor::Accessor(
const char* utf8name,
Getter getter,
Setter setter,
napi_property_attributes attributes,
void* /*data*/) {
using CbData = details::AccessorCallbackData<Getter, Setter>;
// TODO: Delete when the function is destroyed
auto callbackData = new CbData({getter, setter, nullptr});
return PropertyDescriptor({utf8name,
nullptr,
nullptr,
CbData::GetterWrapper,
CbData::SetterWrapper,
nullptr,
attributes,
callbackData});
}
template <typename Getter, typename Setter>
inline PropertyDescriptor PropertyDescriptor::Accessor(
const std::string& utf8name,
Getter getter,
Setter setter,
napi_property_attributes attributes,
void* data) {
return Accessor(utf8name.c_str(), getter, setter, attributes, data);
}
template <typename Getter, typename Setter>
inline PropertyDescriptor PropertyDescriptor::Accessor(
napi_value name,
Getter getter,
Setter setter,
napi_property_attributes attributes,
void* /*data*/) {
using CbData = details::AccessorCallbackData<Getter, Setter>;
// TODO: Delete when the function is destroyed
auto callbackData = new CbData({getter, setter, nullptr});
return PropertyDescriptor({nullptr,
name,
nullptr,
CbData::GetterWrapper,
CbData::SetterWrapper,
nullptr,
attributes,
callbackData});
}
template <typename Getter, typename Setter>
inline PropertyDescriptor PropertyDescriptor::Accessor(
Name name,
Getter getter,
Setter setter,
napi_property_attributes attributes,
void* data) {
napi_value nameValue = name;
return PropertyDescriptor::Accessor(
nameValue, getter, setter, attributes, data);
}
template <typename Callable>
inline PropertyDescriptor PropertyDescriptor::Function(
const char* utf8name,
Callable cb,
napi_property_attributes attributes,
void* /*data*/) {
using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr)));
using CbData = details::CallbackData<Callable, ReturnType>;
// TODO: Delete when the function is destroyed
auto callbackData = new CbData({cb, nullptr});
return PropertyDescriptor({utf8name,
nullptr,
CbData::Wrapper,
nullptr,
nullptr,
nullptr,
attributes,
callbackData});
}
template <typename Callable>
inline PropertyDescriptor PropertyDescriptor::Function(
const std::string& utf8name,
Callable cb,
napi_property_attributes attributes,
void* data) {
return Function(utf8name.c_str(), cb, attributes, data);
}
template <typename Callable>
inline PropertyDescriptor PropertyDescriptor::Function(
napi_value name,
Callable cb,
napi_property_attributes attributes,
void* /*data*/) {
using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr)));
using CbData = details::CallbackData<Callable, ReturnType>;
// TODO: Delete when the function is destroyed
auto callbackData = new CbData({cb, nullptr});
return PropertyDescriptor({nullptr,
name,
CbData::Wrapper,
nullptr,
nullptr,
nullptr,
attributes,
callbackData});
}
template <typename Callable>
inline PropertyDescriptor PropertyDescriptor::Function(
Name name, Callable cb, napi_property_attributes attributes, void* data) {
napi_value nameValue = name;
return PropertyDescriptor::Function(nameValue, cb, attributes, data);
}
#endif // !SRC_NAPI_INL_DEPRECATED_H_

6936
c++/napi-inl.h Normal file

File diff suppressed because it is too large Load Diff

3290
c++/napi.h Normal file

File diff suppressed because it is too large Load Diff

29
date.go Normal file
View File

@ -0,0 +1,29 @@
package napi
import (
"time"
"sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi"
)
type Date struct{ value }
// Convert [ValueType] to [*Date]
func ToDate(o ValueType) *Date { return &Date{o} }
func CreateDate(env EnvType, date time.Time) (*Date, error) {
value, err := mustValueErr(napi.CreateDate(env.NapiValue(), float64(date.UnixMilli())))
if err != nil {
return nil, err
}
return &Date{value: &Value{env: env, valueOf: value}}, nil
}
// Get time from [*Date] object.
func (d Date) Time() (time.Time, error) {
timeFloat, err := mustValueErr(napi.GetDateValue(d.NapiEnv(), d.NapiValue()))
if err != nil {
return time.Time{}, err
}
return time.UnixMilli(int64(timeFloat)), nil
}

View File

@ -1,49 +0,0 @@
package main
import (
"fmt"
"time"
"github.com/akshayganeshen/napi-go"
"github.com/akshayganeshen/napi-go/entry"
)
func init() {
entry.Export("getPromise", GetPromiseHandler)
}
func GetPromiseHandler(env napi.Env, info napi.CallbackInfo) napi.Value {
result, _ := napi.CreatePromise(env)
asyncResourceName, _ := napi.CreateStringUtf8(
env,
"napi-go/async-promise-example",
)
var asyncWork napi.AsyncWork
asyncWork, _ = napi.CreateAsyncWork(
env,
nil, asyncResourceName,
func(env napi.Env) {
fmt.Printf("AsyncExecuteCallback(start)\n")
defer fmt.Printf("AsyncExecuteCallback(stop)\n")
time.Sleep(time.Second)
},
func(env napi.Env, status napi.Status) {
defer napi.DeleteAsyncWork(env, asyncWork)
if status == napi.StatusCancelled {
fmt.Printf("AsyncCompleteCallback(cancelled)\n")
return
}
fmt.Printf("AsyncCompleteCallback\n")
resolution, _ := napi.CreateStringUtf8(env, "resolved")
napi.ResolveDeferred(env, result.Deferred, resolution)
},
)
napi.QueueAsyncWork(env, asyncWork)
return result.Value
}
func main() {}

View File

@ -1,25 +0,0 @@
package main
import (
"github.com/akshayganeshen/napi-go"
"github.com/akshayganeshen/napi-go/entry"
)
func init() {
entry.Export("getCallback", GetCallbackHandler)
}
func GetCallbackHandler(env napi.Env, info napi.CallbackInfo) napi.Value {
result, _ := napi.CreateFunction(
env,
"callback",
func(env napi.Env, info napi.CallbackInfo) napi.Value {
result, _ := napi.CreateStringUtf8(env, "hello world")
return result
},
)
return result
}
func main() {}

View File

@ -1,52 +0,0 @@
package main
import (
"github.com/akshayganeshen/napi-go"
"github.com/akshayganeshen/napi-go/entry"
)
func init() {
entry.Export("describeArgs", DescribeArgsHandler)
}
func DescribeArgsHandler(env napi.Env, info napi.CallbackInfo) napi.Value {
extractedInfo, _ := napi.GetCbInfo(env, info)
result, _ := napi.CreateArrayWithLength(env, len(extractedInfo.Args))
for i, arg := range extractedInfo.Args {
vt, _ := napi.Typeof(env, arg)
dv, _ := napi.CreateStringUtf8(env, DescribeValueType(vt))
napi.SetElement(env, result, i, dv)
}
return result
}
func DescribeValueType(vt napi.ValueType) string {
switch vt {
case napi.ValueTypeUndefined:
return "undefined"
case napi.ValueTypeNull:
return "null"
case napi.ValueTypeBoolean:
return "boolean"
case napi.ValueTypeNumber:
return "number"
case napi.ValueTypeString:
return "string"
case napi.ValueTypeSymbol:
return "symbol"
case napi.ValueTypeObject:
return "object"
case napi.ValueTypeFunction:
return "function"
case napi.ValueTypeExternal:
return "external"
case napi.ValueTypeBigint:
return "bigint"
default:
return "other"
}
}
func main() {}

View File

@ -1,19 +0,0 @@
package main
import (
"fmt"
"github.com/akshayganeshen/napi-go"
"github.com/akshayganeshen/napi-go/entry"
)
func init() {
entry.Export("hello", HelloHandler)
}
func HelloHandler(env napi.Env, info napi.CallbackInfo) napi.Value {
fmt.Println("hello world!")
return nil
}
func main() {}

View File

@ -1,81 +0,0 @@
package main
import (
"fmt"
"time"
"github.com/akshayganeshen/napi-go"
"github.com/akshayganeshen/napi-go/entry"
"github.com/akshayganeshen/napi-go/js"
)
func init() {
entry.Export("getMap", GetMapHandler)
entry.Export("getCallback", js.AsCallback(GetCallback))
entry.Export("getArray", js.AsCallback(GetArray))
entry.Export("getPromiseResolve", js.AsCallback(GetPromiseResolve))
entry.Export("getPromiseReject", js.AsCallback(GetPromiseReject))
}
func GetMapHandler(env napi.Env, info napi.CallbackInfo) napi.Value {
jsEnv := js.AsEnv(env)
return jsEnv.ValueOf(
map[string]any{
"string": "hello world",
"number": 123,
"bool": false,
"undefined": jsEnv.Undefined(),
"null": nil,
"function": jsEnv.FuncOf(
func(env js.Env, this js.Value, args []js.Value) any {
return "hello world"
},
),
},
).Value
}
func GetCallback(env js.Env, this js.Value, args []js.Value) any {
return func(env js.Env, this js.Value, args []js.Value) any {
return map[string]any{
"this": this,
"args": args,
}
}
}
func GetArray(env js.Env, this js.Value, args []js.Value) any {
return []any{
"hello world",
123,
true,
map[string]any{
"key": "value",
},
}
}
func GetPromiseResolve(env js.Env, this js.Value, args []js.Value) any {
promise := env.NewPromise()
go func() {
time.Sleep(time.Second)
promise.Resolve("resolved")
}()
return promise
}
func GetPromiseReject(env js.Env, this js.Value, args []js.Value) any {
promise := env.NewPromise()
go func() {
time.Sleep(time.Second)
promise.Reject(fmt.Errorf("rejected"))
}()
return promise
}
func main() {}

66
entry/entry.go Normal file
View File

@ -0,0 +1,66 @@
package entry
/*
#cgo CFLAGS: -DDEBUG
#cgo CFLAGS: -D_DEBUG
#cgo CFLAGS: -DV8_ENABLE_CHECKS
#cgo CFLAGS: -DNAPI_EXPERIMENTAL
#cgo CFLAGS: -I/usr/local/include/node
#cgo CXXFLAGS: -std=c++11
#cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup
#cgo darwin LDFLAGS: -Wl,-no_pie
#cgo darwin LDFLAGS: -Wl,-search_paths_first
#cgo (darwin && amd64) LDFLAGS: -arch x86_64
#cgo (darwin && arm64) LDFLAGS: -arch arm64
#cgo linux LDFLAGS: -Wl,-unresolved-symbols=ignore-all
#cgo LDFLAGS: -L${SRCDIR}
#include <stdlib.h>
#include "./entry.h"
*/
import "C"
import (
"fmt"
gonapi "sirherobrine23.com.br/Sirherobrine23/napi-go"
"sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi"
)
type registerCallback func(env napi.Env, object napi.Value)
var modFuncInit = []registerCallback{}
//export initializeModule
func initializeModule(cEnv C.napi_env, cExports C.napi_value) C.napi_value {
env, exports := napi.Env(cEnv), napi.Value(cExports)
napi.InitializeInstanceData(env)
defer func() {
if err := recover(); err != nil {
switch v := err.(type) {
case error:
gonapi.ThrowError(gonapi.N_APIEnv(env), "", v.Error())
default:
gonapi.ThrowError(gonapi.N_APIEnv(env), "", fmt.Sprintf("%s", v))
}
}
}()
for _, registerCall := range modFuncInit {
registerCall(env, exports)
}
return cExports
}
func Register(fn func(env gonapi.EnvType, export *gonapi.Object)) {
modFuncInit = append(modFuncInit, func(env napi.Env, object napi.Value) {
registerEnv := gonapi.N_APIEnv(env)
registerObj := gonapi.ToObject(gonapi.N_APIValue(registerEnv, object))
fn(registerEnv, registerObj)
})
}

View File

@ -7,9 +7,9 @@
extern "C" { extern "C" {
#endif /* __cplusplus */ #endif /* __cplusplus */
// InitializeModule is a N-API module initialization function. // initializeModule is a N-API module initialization function.
// InitializeModule is suitable for use as a napi_addon_register_func. // initializeModule is suitable for use as a napi_addon_register_func.
extern napi_value InitializeModule( extern napi_value initializeModule(
napi_env env, napi_env env,
napi_value exports napi_value exports
); );

View File

@ -1,19 +0,0 @@
package entry
import (
"github.com/akshayganeshen/napi-go"
)
type napiGoExport struct {
Name string
Callback napi.Callback
}
var napiGoGlobalExports []napiGoExport
func Export(name string, callback napi.Callback) {
napiGoGlobalExports = append(napiGoGlobalExports, napiGoExport{
Name: name,
Callback: callback,
})
}

View File

@ -2,4 +2,4 @@
#include "./entry.h" #include "./entry.h"
NAPI_MODULE(napiGo, InitializeModule) NAPI_MODULE(napiGo, initializeModule)

View File

@ -1,26 +0,0 @@
package entry
/*
#include <stdlib.h>
#include "./entry.h"
*/
import "C"
import (
"github.com/akshayganeshen/napi-go"
)
//export InitializeModule
func InitializeModule(cEnv C.napi_env, cExports C.napi_value) C.napi_value {
env, exports := napi.Env(cEnv), napi.Value(cExports)
napi.InitializeInstanceData(env)
for _, export := range napiGoGlobalExports {
cb, _ := napi.CreateFunction(env, export.Name, export.Callback)
name, _ := napi.CreateStringUtf8(env, export.Name)
napi.SetProperty(env, exports, name, cb)
}
return cExports
}

View File

@ -1,21 +0,0 @@
package entry
/*
#cgo CFLAGS: -DDEBUG
#cgo CFLAGS: -D_DEBUG
#cgo CFLAGS: -DV8_ENABLE_CHECKS
#cgo CFLAGS: -DNAPI_EXPERIMENTAL
#cgo CFLAGS: -I/usr/local/include/node
#cgo CXXFLAGS: -std=c++11
#cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup
#cgo darwin LDFLAGS: -Wl,-no_pie
#cgo darwin LDFLAGS: -Wl,-search_paths_first
#cgo darwin LDFLAGS: -arch x86_64
#cgo linux LDFLAGS: -Wl,-unresolved-symbols=ignore-all
#cgo LDFLAGS: -L${SRCDIR}
#cgo LDFLAGS: -stdlib=libc++
*/
import "C"

37
error.go Normal file
View File

@ -0,0 +1,37 @@
package napi
import (
"runtime"
"sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi"
)
type Error struct{ value }
func ToError(o ValueType) *Error { return &Error{o} }
func CreateError(env EnvType, msg string) (*Error, error) {
napiMsg, err := CreateString(env, msg)
if err != nil {
return nil, err
}
napiValue, err := mustValueErr(napi.CreateError(env.NapiValue(), nil, napiMsg.NapiValue()))
if err != nil {
return nil, err
}
return ToError(N_APIValue(env, napiValue)), nil
}
func (er *Error) ThrowAsJavaScriptException() error {
return singleMustValueErr(napi.Throw(er.NapiEnv(), er.NapiValue()))
}
// This throws a JavaScript Error with the text provided.
func ThrowError(env EnvType, code, err string) error {
if code == "" {
stackTraceBuf := make([]byte, 8192)
stackTraceSz := runtime.Stack(stackTraceBuf, false)
code = string(stackTraceBuf[:stackTraceSz])
}
return singleMustValueErr(napi.ThrowError(env.NapiValue(), code, err))
}

80
example/test.go Normal file
View File

@ -0,0 +1,80 @@
package main
import (
"encoding/json"
"net/netip"
"sirherobrine23.com.br/Sirherobrine23/napi-go"
"sirherobrine23.com.br/Sirherobrine23/napi-go/entry"
"sirherobrine23.com.br/Sirherobrine23/napi-go/js"
)
type Test struct {
Int int
String string
Sub []any
}
func init() {
entry.Register(func(env napi.EnvType, export *napi.Object) {
inNode, _ := napi.CreateString(env, "from golang napi string")
inNode2, _ := napi.CopyBuffer(env, []byte{1, 0, 244, 21})
toGoReflect := &Test{
Int: 14,
String: "From golang",
Sub: []any{
1,
[]string{"test", "gopher"},
[]bool{false, true},
[]int{23, 244, 10, 2024, 2025, 2000},
map[string]string{"exampleMap": "test"},
map[int]string{1: "one"},
map[bool]string{false: "false", true: "true"},
map[[2]string]string{[2]string{"go"}: "example"},
netip.IPv4Unspecified(),
netip.IPv6Unspecified(),
netip.AddrPortFrom(netip.IPv6Unspecified(), 19132),
nil,
true,
false,
inNode,
inNode2,
func() {
println("called in go")
},
},
}
napiStruct, err := js.ValueOf(env, toGoReflect)
if err != nil {
panic(err)
}
export.Set("goStruct", napiStruct)
fnCall, err := js.GoFuncOf(env, func(call ...any) (string, error) {
d, err := json.MarshalIndent(call, "", " ")
if err == nil {
println(string(d))
}
return string(d), err
})
if err != nil {
panic(err)
}
export.Set("printAny", fnCall)
fnCallStruct, err := js.GoFuncOf(env, func(call ...Test) (string, error) {
d, err := json.MarshalIndent(call, "", " ")
if err == nil {
println(string(d))
}
return string(d), err
})
if err != nil {
panic(err)
}
export.Set("printTestStruct", fnCallStruct)
})
}
func main() {}

96
function.go Normal file
View File

@ -0,0 +1,96 @@
package napi
import (
"fmt"
"sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi"
)
type Function struct{ value }
// Function to call on Javascript caller
type Callback func(env EnvType, this ValueType, args []ValueType) (ValueType, error)
// Convert [ValueType] to [*Function]
func ToFunction(o ValueType) *Function { return &Function{o} }
func CreateFunction(env EnvType, name string, callback Callback) (*Function, error) {
return CreateFunctionNapi(env, name, func(napiEnv napi.Env, info napi.CallbackInfo) napi.Value {
env := N_APIEnv(napiEnv)
cbInfo, err := mustValueErr(napi.GetCbInfo(napiEnv, info))
if err != nil {
ThrowError(env, "", err.Error())
return nil
}
this := N_APIValue(env, cbInfo.This)
args := make([]ValueType, len(cbInfo.Args))
for i, cbArg := range cbInfo.Args {
args[i] = N_APIValue(env, cbArg)
}
defer func() {
if err := recover(); err != nil {
switch v := err.(type) {
case error:
ThrowError(env, "", v.Error())
default:
ThrowError(env, "", fmt.Sprintf("panic recover: %s", err))
}
}
}()
res, err := callback(env, this, args)
switch {
case err != nil:
ThrowError(env, "", err.Error())
return nil
case res == nil:
und, _ := env.Undefined()
return und.NapiValue()
default:
typeOf, _ := res.Type()
if typeOf == TypeError {
ToError(res).ThrowAsJavaScriptException()
return nil
}
return res.NapiValue()
}
})
}
func CreateFunctionNapi(env EnvType, name string, callback napi.Callback) (*Function, error) {
fnCall, err := mustValueErr(napi.CreateFunction(env.NapiValue(), name, callback))
if err != nil {
return nil, err
}
return ToFunction(N_APIValue(env, fnCall)), nil
}
func (fn *Function) internalCall(this napi.Value, argc int, argv []napi.Value) (ValueType, error) {
// napi_call_function(env, global, add_two, argc, argv, &return_val);
res, err := mustValueErr(napi.CallFunction(fn.NapiEnv(), this, fn.NapiValue(), argc, argv))
if err != nil {
return nil, err
}
return N_APIValue(fn.Env(), res), nil
}
// Call function with custom global/this value
func (fn *Function) CallWithGlobal(this ValueType, args ...ValueType) (ValueType, error) {
argc := len(args)
argv := make([]napi.Value, argc)
for index := range argc {
argv[index] = args[index].NapiValue()
}
return fn.internalCall(this.NapiValue(), argc, argv)
}
// Call function with args
func (fn *Function) Call(args ...ValueType) (ValueType, error) {
global, err := fn.Env().Global()
if err != nil {
return nil, err
}
return fn.CallWithGlobal(global, args...)
}

4
go.mod
View File

@ -1,3 +1,3 @@
module github.com/akshayganeshen/napi-go module sirherobrine23.com.br/Sirherobrine23/napi-go
go 1.18 go 1.24

File diff suppressed because it is too large Load Diff

34
internal/napi/key_type.go Normal file
View File

@ -0,0 +1,34 @@
package napi
/*
#include <node/node_api.h>
*/
import "C"
// KeyCollectionMode type
type KeyCollectionMode C.napi_key_collection_mode
const (
KeyIncludePrototypes KeyCollectionMode = C.napi_key_include_prototypes
KeyOwnOnly KeyCollectionMode = C.napi_key_own_only
)
// KeyFilter type
type KeyFilter C.napi_key_filter
const (
KeyAllProperties KeyFilter = C.napi_key_all_properties
KeyWritable KeyFilter = C.napi_key_writable
KeyEnumerable KeyFilter = C.napi_key_enumerable
KeyConfigurable KeyFilter = C.napi_key_configurable
KeySkipStrings KeyFilter = C.napi_key_skip_strings
KeySkipSymbols KeyFilter = C.napi_key_skip_symbols
)
// KeyConversion type
type KeyConversion C.napi_key_conversion
const (
KeyKeepNumbers KeyConversion = C.napi_key_keep_numbers
KeyNumbersToStrings KeyConversion = C.napi_key_numbers_to_strings
)

View File

@ -11,11 +11,11 @@ package napi
#cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup
#cgo darwin LDFLAGS: -Wl,-no_pie #cgo darwin LDFLAGS: -Wl,-no_pie
#cgo darwin LDFLAGS: -Wl,-search_paths_first #cgo darwin LDFLAGS: -Wl,-search_paths_first
#cgo darwin LDFLAGS: -arch x86_64 // #cgo darwin amd64 LDFLAGS: -arch x86_64
// #cgo darwin arm64 LDFLAGS: -arch arm64
#cgo linux LDFLAGS: -Wl,-unresolved-symbols=ignore-all #cgo linux LDFLAGS: -Wl,-unresolved-symbols=ignore-all
#cgo LDFLAGS: -L${SRCDIR} #cgo LDFLAGS: -L${SRCDIR}
#cgo LDFLAGS: -stdlib=libc++
*/ */
import "C" import "C"

View File

@ -1,6 +1,7 @@
package napi package napi
/* /*
#include <stdlib.h>
#include <node/node_api.h> #include <node/node_api.h>
*/ */
import "C" import "C"
@ -113,11 +114,12 @@ func CreateThreadsafeFunction(
func CallThreadsafeFunction( func CallThreadsafeFunction(
fn ThreadsafeFunction, fn ThreadsafeFunction,
mode ThreadsafeFunctionCallMode,
) Status { ) Status {
return Status(C.napi_call_threadsafe_function( return Status(C.napi_call_threadsafe_function(
C.napi_threadsafe_function(fn), C.napi_threadsafe_function(fn),
nil, nil,
C.napi_tsfn_blocking, C.napi_threadsafe_function_call_mode(mode),
)) ))
} }
@ -129,9 +131,108 @@ func AcquireThreadsafeFunction(fn ThreadsafeFunction) Status {
func ReleaseThreadsafeFunction( func ReleaseThreadsafeFunction(
fn ThreadsafeFunction, fn ThreadsafeFunction,
mode ThreadsafeFunctionReleaseMode,
) Status { ) Status {
return Status(C.napi_release_threadsafe_function( return Status(C.napi_release_threadsafe_function(
C.napi_threadsafe_function(fn), C.napi_threadsafe_function(fn),
C.napi_tsfn_release, C.napi_threadsafe_function_release_mode(mode),
)) ))
} }
func GetThreadsafeFunctionContext(
fn ThreadsafeFunction,
) (unsafe.Pointer, Status) {
var context unsafe.Pointer
status := Status(C.napi_get_threadsafe_function_context(
C.napi_threadsafe_function(fn),
&context,
))
return context, status
}
func RefThreadsafeFunction(env Env, fn ThreadsafeFunction) Status {
return Status(C.napi_ref_threadsafe_function(
C.napi_env(env),
C.napi_threadsafe_function(fn),
))
}
func UnrefThreadsafeFunction(env Env, fn ThreadsafeFunction) Status {
return Status(C.napi_unref_threadsafe_function(
C.napi_env(env),
C.napi_threadsafe_function(fn),
))
}
func ThrowSyntaxError(env Env, code, msg string) Status {
codeCStr, msgCStr := C.CString(code), C.CString(msg)
defer C.free(unsafe.Pointer(codeCStr))
defer C.free(unsafe.Pointer(msgCStr))
return Status(C.node_api_throw_syntax_error(
C.napi_env(env),
codeCStr,
msgCStr,
))
}
func CreateSyntaxError(env Env, code, msg Value) (Value, Status) {
var result Value
status := Status(C.node_api_create_syntax_error(
C.napi_env(env),
C.napi_value(code),
C.napi_value(msg),
(*C.napi_value)(unsafe.Pointer(&result)),
))
return result, status
}
func SymbolFor(env Env, description string) (Value, Status) {
var result Value
status := Status(C.node_api_symbol_for(
C.napi_env(env),
C.CString(description),
C.size_t(len(description)),
(*C.napi_value)(unsafe.Pointer(&result)),
))
return result, status
}
func CreatePropertyKeyLatin1(env Env, str string) (Value, Status) {
cstr := C.CString(str)
defer C.free(unsafe.Pointer(cstr))
var result Value
status := Status(C.node_api_create_property_key_latin1(
C.napi_env(env),
cstr,
C.size_t(len([]byte(str))),
(*C.napi_value)(unsafe.Pointer(&result)),
))
return result, status
}
func CreatePropertyKeyUtf16(env Env, str []uint16) (Value, Status) {
var result Value
status := Status(C.node_api_create_property_key_utf16(
C.napi_env(env),
(*C.char16_t)(unsafe.Pointer(&str[0])),
C.size_t(len(str)),
(*C.napi_value)(unsafe.Pointer(&result)),
))
return result, status
}
func CreatePropertyKeyUtf8(env Env, str string) (Value, Status) {
cstr := C.CString(str)
defer C.free(unsafe.Pointer(cstr))
var result Value
status := Status(C.node_api_create_property_key_utf8(
C.napi_env(env),
cstr,
C.size_t(len([]byte(str))),
(*C.napi_value)(unsafe.Pointer(&result)),
))
return result, status
}

View File

@ -0,0 +1,18 @@
package napi
/*
#include <node/node_api.h>
*/
import "C"
type PropertyAttributes C.napi_property_attributes
const (
Default PropertyAttributes = C.napi_default
Writable PropertyAttributes = C.napi_writable
Enumerable PropertyAttributes = C.napi_enumerable
Configurable PropertyAttributes = C.napi_configurable
Static PropertyAttributes = C.napi_static
DefaultMethod PropertyAttributes = C.napi_default_method
DefaultJSProperty PropertyAttributes = C.napi_default_jsproperty
)

View File

@ -0,0 +1,20 @@
package napi
/*
#include <node/node_api.h>
*/
import "C"
type ThreadsafeFunctionReleaseMode C.napi_threadsafe_function_release_mode
const (
Release ThreadsafeFunctionReleaseMode = C.napi_tsfn_release
Abort ThreadsafeFunctionReleaseMode = C.napi_tsfn_abort
)
type ThreadsafeFunctionCallMode C.napi_threadsafe_function_call_mode
const (
NonBlocking ThreadsafeFunctionCallMode = C.napi_tsfn_nonblocking
Blocking ThreadsafeFunctionCallMode = C.napi_tsfn_blocking
)

View File

@ -0,0 +1,22 @@
package napi
/*
#include <node/node_api.h>
*/
import "C"
type TypedArrayType C.napi_typedarray_type
const (
TypedArrayInt8Array TypedArrayType = C.napi_int8_array
TypedArrayUint8Array TypedArrayType = C.napi_uint8_array
TypedArrayUint8ClampedArray TypedArrayType = C.napi_uint8_clamped_array
TypedArrayInt16Array TypedArrayType = C.napi_int16_array
TypedArrayUint16Array TypedArrayType = C.napi_uint16_array
TypedArrayInt32Array TypedArrayType = C.napi_int32_array
TypedArrayUint32Array TypedArrayType = C.napi_uint32_array
TypedArrayFloat32Array TypedArrayType = C.napi_float32_array
TypedArrayFloat64Array TypedArrayType = C.napi_float64_array
TypedArrayBigInt64Array TypedArrayType = C.napi_bigint64_array
TypedArrayBigUint64Array TypedArrayType = C.napi_biguint64_array
)

41
internal/napi/types.go Normal file
View File

@ -0,0 +1,41 @@
package napi
/*
#include <node/node_api.h>
*/
import "C"
import "unsafe"
type PropertyDescriptor struct {
Utf8name string
Name Value
Method Callback
Getter Callback
Setter Callback
Value Value
Attributes PropertyAttributes
Data unsafe.Pointer
}
type Finalize func(env Env, finalizeData, finalizeHint unsafe.Pointer)
func FinalizeToFinalizer(fn Finalize) Finalizer {
return func(env C.napi_env, finalizeData, finalizeHint unsafe.Pointer) {
fn(Env(env), finalizeData, finalizeHint)
}
}
// Finalizer as a C-compatible function pointer type
type Finalizer func(env C.napi_env, finalizeData, finalizeHint unsafe.Pointer)
type Reference struct {
Ref unsafe.Pointer
}
type EscapableHandleScope struct {
Scope unsafe.Pointer
}
type HandleScope struct {
Scope unsafe.Pointer
}

View File

@ -0,0 +1,48 @@
package napi
/*
#include <node/node_api.h>
*/
import "C"
type ValueType C.napi_valuetype
const (
ValueTypeUndefined ValueType = C.napi_undefined
ValueTypeNull ValueType = C.napi_null
ValueTypeBoolean ValueType = C.napi_boolean
ValueTypeNumber ValueType = C.napi_number
ValueTypeString ValueType = C.napi_string
ValueTypeSymbol ValueType = C.napi_symbol
ValueTypeObject ValueType = C.napi_object
ValueTypeFunction ValueType = C.napi_function
ValueTypeExternal ValueType = C.napi_external
ValueTypeBigint ValueType = C.napi_bigint
)
func (v ValueType) String() string {
switch v {
case ValueTypeUndefined:
return "undefined"
case ValueTypeNull:
return "null"
case ValueTypeBoolean:
return "boolean"
case ValueTypeNumber:
return "number"
case ValueTypeString:
return "string"
case ValueTypeSymbol:
return "symbol"
case ValueTypeObject:
return "object"
case ValueTypeFunction:
return "function"
case ValueTypeExternal:
return "external"
case ValueTypeBigint:
return "bigint"
default:
return "undefined"
}
}

View File

@ -1,32 +0,0 @@
package js
import (
"github.com/akshayganeshen/napi-go"
)
type Callback = func(env Env, this Value, args []Value) any
func AsCallback(fn Callback) napi.Callback {
return func(env napi.Env, info napi.CallbackInfo) napi.Value {
cbInfo, st := napi.GetCbInfo(env, info)
if st != napi.StatusOK {
panic(napi.StatusError(st))
}
jsEnv := AsEnv(env)
this := Value{
Env: jsEnv,
Value: cbInfo.This,
}
args := make([]Value, len(cbInfo.Args))
for i, cbArg := range cbInfo.Args {
args[i] = Value{
Env: jsEnv,
Value: cbArg,
}
}
result := fn(jsEnv, this, args)
return jsEnv.ValueOf(result).Value
}
}

196
js/env.go
View File

@ -1,196 +0,0 @@
package js
import (
"fmt"
"unsafe"
"github.com/akshayganeshen/napi-go"
)
type Env struct {
Env napi.Env
}
type InvalidValueTypeError struct {
Value any
}
var _ error = InvalidValueTypeError{}
func AsEnv(env napi.Env) Env {
return Env{
Env: env,
}
}
func (e Env) Global() Value {
v, st := napi.GetGlobal(e.Env)
if st != napi.StatusOK {
panic(napi.StatusError(st))
}
return Value{
Env: e,
Value: v,
}
}
func (e Env) Null() Value {
v, st := napi.GetNull(e.Env)
if st != napi.StatusOK {
panic(napi.StatusError(st))
}
return Value{
Env: e,
Value: v,
}
}
func (e Env) Undefined() Value {
v, st := napi.GetUndefined(e.Env)
if st != napi.StatusOK {
panic(napi.StatusError(st))
}
return Value{
Env: e,
Value: v,
}
}
func (e Env) ValueOf(x any) Value {
var (
v napi.Value
st napi.Status
)
switch xt := x.(type) {
case Value:
return xt
case []Value:
l := len(xt)
v, st = napi.CreateArrayWithLength(e.Env, l)
if st != napi.StatusOK {
break
}
for i, xti := range xt {
// TODO: Use Value.SetIndex helper
st = napi.SetElement(e.Env, v, i, xti.Value)
if st != napi.StatusOK {
break
}
}
case Func:
return xt.Value
case Callback:
return e.FuncOf(xt).Value
case *Promise:
v, st = xt.Promise.Value, napi.StatusOK
case napi.Value:
v, st = xt, napi.StatusOK
case nil:
v, st = napi.GetNull(e.Env)
case bool:
v, st = napi.GetBoolean(e.Env, xt)
case int:
v, st = napi.CreateDouble(e.Env, float64(xt))
case int8:
v, st = napi.CreateDouble(e.Env, float64(xt))
case int16:
v, st = napi.CreateDouble(e.Env, float64(xt))
case int64:
v, st = napi.CreateDouble(e.Env, float64(xt))
case uint:
v, st = napi.CreateDouble(e.Env, float64(xt))
case uint8:
v, st = napi.CreateDouble(e.Env, float64(xt))
case uint16:
v, st = napi.CreateDouble(e.Env, float64(xt))
case uint64:
v, st = napi.CreateDouble(e.Env, float64(xt))
case uintptr:
v, st = napi.CreateDouble(e.Env, float64(xt))
case unsafe.Pointer:
v, st = napi.CreateDouble(e.Env, float64(uintptr(xt)))
case float32:
v, st = napi.CreateDouble(e.Env, float64(xt))
case float64:
v, st = napi.CreateDouble(e.Env, xt)
case string:
v, st = napi.CreateStringUtf8(e.Env, xt)
case error:
msg := e.ValueOf(xt.Error())
v, st = napi.CreateError(e.Env, nil, msg.Value)
case []any:
l := len(xt)
v, st = napi.CreateArrayWithLength(e.Env, l)
if st != napi.StatusOK {
break
}
for i, xti := range xt {
// TODO: Use Value.SetIndex helper
vti := e.ValueOf(xti)
st = napi.SetElement(e.Env, v, i, vti.Value)
if st != napi.StatusOK {
break
}
}
case map[string]any:
v, st = napi.CreateObject(e.Env)
if st != napi.StatusOK {
break
}
for xtk, xtv := range xt {
// TODO: Use Value.Set helper
vtk, vtv := e.ValueOf(xtk), e.ValueOf(xtv)
st = napi.SetProperty(e.Env, v, vtk.Value, vtv.Value)
if st != napi.StatusOK {
break
}
}
default:
panic(InvalidValueTypeError{x})
}
if st != napi.StatusOK {
panic(napi.StatusError(st))
}
return Value{
Env: e,
Value: v,
}
}
func (e Env) FuncOf(fn Callback) Func {
// TODO: Add CreateReference to FuncOf to keep value alive
v, st := napi.CreateFunction(
e.Env,
"",
AsCallback(fn),
)
if st != napi.StatusOK {
panic(napi.StatusError(st))
}
return Func{
Value: Value{
Env: e,
Value: v,
},
}
}
func (e Env) NewPromise() *Promise {
var result Promise
result.reset(e)
return &result
}
func (err InvalidValueTypeError) Error() string {
return fmt.Sprintf("Value cannot be represented in JS: %T", err.Value)
}

View File

@ -1,5 +0,0 @@
package js
type Func struct {
Value
}

21
js/funcof.md Normal file
View File

@ -0,0 +1,21 @@
# Go functions conversion to Javascript functions
## Simples conversions
| Go | Node |
| --------------------------------- | ------------------------------------------- |
| `func()` | `function(): void` |
| `func() value` | `function(): value` |
| `func(value1, value2)` | `function(value1, value2): void` |
| `func() (value1, value2)` | `function(): [value1, value2]` |
| `func(value1, value2, values...)` | `function(value1, value2, ...values): void` |
| `func(values...)` | `function(...values): void` |
## Error throw
| Go | Node |
| ------------------------------------------ | --------------------------------------------------- |
| `func() error` | `function(): throw Error` |
| `func(value1, value2) error` | `function(value1, value2): throw Error` |
| `func() (value, error)` | `function(): value \|\| throw Error` |
| `func(value1, value2) (...nValues, error)` | `function(value1, value2): (Array \|\|throw Error)` |

575
js/js.go Normal file
View File

@ -0,0 +1,575 @@
package js
import (
"encoding"
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
"time"
"sirherobrine23.com.br/Sirherobrine23/napi-go"
)
const propertiesTagName = "napi"
// Convert go types to valid NAPI, if not conpatible return Undefined.
func ValueOf(env napi.EnvType, value any) (napiValue napi.ValueType, err error) {
return valueOf(env, reflect.ValueOf(value))
}
// Convert NAPI value to Go values
func ValueFrom(napiValue napi.ValueType, v any) error {
ptr := reflect.ValueOf(v)
if ptr.Kind() != reflect.Pointer {
return fmt.Errorf("require point to convert napi value to go value")
}
return valueFrom(napiValue, ptr.Elem())
}
func valueOf(env napi.EnvType, ptr reflect.Value) (napiValue napi.ValueType, err error) {
defer func(err *error) {
if err2 := recover(); err2 != nil {
switch v := err2.(type) {
case error:
*err = v
default:
*err = fmt.Errorf("panic recover: %s", err2)
}
}
}(&err)
if !ptr.IsValid() || ptr.IsZero() {
return env.Undefined()
}
// Marshalers
if ptr.CanInterface() {
switch v := ptr.Interface().(type) {
case napi.ValueType:
return v, nil
case time.Time:
return napi.CreateDate(env, v)
case encoding.TextMarshaler:
data, err := v.MarshalText()
if err != nil {
return nil, err
}
return napi.CreateString(env, string(data))
case json.Marshaler:
var pointData any
data, err := v.MarshalJSON()
if err != nil {
return nil, err
} else if err = json.Unmarshal(data, &pointData); err != nil {
return nil, err
}
return ValueOf(env, pointData)
}
}
ptrType := ptr.Type()
switch ptrType.Kind() {
case reflect.Pointer:
return valueOf(env, ptr.Elem())
case reflect.String:
return napi.CreateString(env, ptr.String())
case reflect.Bool:
return napi.CreateBoolean(env, ptr.Bool())
case reflect.Int, reflect.Uint, reflect.Int32, reflect.Uint32, reflect.Float32, reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16:
return napi.CreateNumber(env, ptr.Int())
case reflect.Float64:
return napi.CreateNumber(env, ptr.Float())
case reflect.Int64, reflect.Uint64:
return napi.CreateBigint(env, ptr.Int())
case reflect.Func:
return funcOf(env, ptr)
case reflect.Slice, reflect.Array:
arr, err := napi.CreateArray(env, ptr.Len())
if err != nil {
return nil, err
}
for index := range ptr.Len() {
value, err := valueOf(env, ptr.Index(index))
if err != nil {
return arr, err
} else if err = arr.Set(index, value); err != nil {
return arr, err
}
}
return arr, nil
case reflect.Struct:
obj, err := napi.CreateObject(env)
if err != nil {
return nil, err
}
for keyIndex := range ptrType.NumField() {
field, fieldType := ptr.Field(keyIndex), ptrType.Field(keyIndex)
if !fieldType.IsExported() || fieldType.Tag.Get(propertiesTagName) == "-" {
continue
}
value, err := valueOf(env, field)
if err != nil {
return obj, err
}
typeof, err := value.Type()
if err != nil {
return nil, err
}
keyNamed := fieldType.Name
if strings.Count(fieldType.Tag.Get(propertiesTagName), ",") > 0 {
fields := strings.SplitN(fieldType.Tag.Get(propertiesTagName), ",", 2)
keyNamed = fields[0]
switch fields[1] {
case "omitempty":
switch typeof {
case napi.TypeUndefined, napi.TypeNull, napi.TypeUnkown:
continue
case napi.TypeString:
str, err := napi.ToString(value).Utf8Value()
if err != nil {
return nil, err
} else if str == "" {
continue
}
}
case "omitzero":
switch typeof {
case napi.TypeUndefined, napi.TypeNull, napi.TypeUnkown:
continue
case napi.TypeDate:
value, err := napi.ToDate(value).Time()
if err != nil {
return nil, err
} else if value.Unix() == 0 {
continue
}
case napi.TypeBigInt:
value, err := napi.ToBigint(value).Int64()
if err != nil {
return nil, err
} else if value == 0 {
continue
}
case napi.TypeNumber:
value, err := napi.ToNumber(value).Int()
if err != nil {
return nil, err
} else if value == 0 {
continue
}
case napi.TypeArray:
value, err := napi.ToArray(value).Length()
if err != nil {
return nil, err
} else if value == 0 {
continue
}
}
}
}
if err = obj.Set(keyNamed, value); err != nil {
return obj, err
}
}
return obj, nil
case reflect.Map:
obj, err := napi.CreateObject(env)
if err != nil {
return nil, err
}
for ptrKey, ptrValue := range ptr.Seq2() {
key, err := valueOf(env, ptrKey)
if err != nil {
return nil, err
}
value, err := valueOf(env, ptrValue)
if err != nil {
return nil, err
} else if err = obj.SetWithValue(key, value); err != nil {
return nil, err
}
}
return obj, nil
case reflect.Interface:
if ptr.IsValid() {
if ptr.IsNil() {
return env.Null()
} else if ptr.CanInterface() {
return valueOf(env, reflect.ValueOf(ptr.Interface()))
}
}
}
return env.Undefined()
}
func valueFrom(napiValue napi.ValueType, ptr reflect.Value) error {
typeOf, err := napiValue.Type()
if err != nil {
return err
}
ptrType := ptr.Type()
switch ptrType.Kind() {
case reflect.Pointer:
return valueFrom(napiValue, ptr.Elem())
case reflect.Interface:
// Check if is any and can set
if ptr.CanSet() && ptrType == reflect.TypeFor[any]() {
switch typeOf {
case napi.TypeNull, napi.TypeUndefined, napi.TypeUnkown:
ptr.Set(reflect.Zero(ptrType))
return nil
case napi.TypeBoolean:
valueOf, err := napi.ToBoolean(napiValue).Value()
if err != nil {
return err
}
ptr.Set(reflect.ValueOf(valueOf))
case napi.TypeNumber:
numberValue, err := napi.ToNumber(napiValue).Float()
if err != nil {
return err
}
ptr.Set(reflect.ValueOf(numberValue))
case napi.TypeBigInt:
numberValue, err := napi.ToBigint(napiValue).Int64()
if err != nil {
return err
}
ptr.Set(reflect.ValueOf(numberValue))
case napi.TypeString:
str, err := napi.ToString(napiValue).Utf8Value()
if err != nil {
return err
}
ptr.Set(reflect.ValueOf(str))
case napi.TypeDate:
timeDate, err := napi.ToDate(napiValue).Time()
if err != nil {
return err
}
ptr.Set(reflect.ValueOf(timeDate))
case napi.TypeArray:
napiArray := napi.ToArray(napiValue)
size, err := napiArray.Length()
if err != nil {
return err
}
value := reflect.MakeSlice(reflect.SliceOf(ptrType), size, size)
for index := range size {
napiValue, err := napiArray.Get(index)
if err != nil {
return err
} else if err = valueFrom(napiValue, value.Index(index)); err != nil {
return err
}
}
ptr.Set(value)
case napi.TypeBuffer:
buff, err := napi.ToBuffer(napiValue).Data()
if err != nil {
return err
}
ptr.Set(reflect.ValueOf(buff))
case napi.TypeObject:
obj := napi.ToObject(napiValue)
goMap := reflect.MakeMap(reflect.MapOf(reflect.TypeFor[string](), reflect.TypeFor[any]()))
for keyName, value := range obj.Seq() {
valueOf := reflect.New(reflect.TypeFor[any]())
if err := valueFrom(value, valueOf); err != nil {
return err
}
goMap.SetMapIndex(reflect.ValueOf(keyName), valueOf)
}
ptr.Set(goMap)
case napi.TypeFunction:
ptr.Set(reflect.ValueOf(napi.ToFunction(napiValue)))
}
return nil
}
return fmt.Errorf("cannot set value, returned %s", typeOf)
}
switch typeOf {
case napi.TypeNull, napi.TypeUndefined, napi.TypeUnkown:
switch ptrType.Kind() {
case reflect.Interface, reflect.Pointer:
ptr.Set(reflect.Zero(ptrType))
return nil
default:
return fmt.Errorf("cannot set value, returned %s", typeOf)
}
case napi.TypeBoolean:
switch ptr.Kind() {
case reflect.Bool:
valueOf, err := napi.ToBoolean(napiValue).Value()
if err != nil {
return err
}
ptr.SetBool(valueOf)
default:
return fmt.Errorf("cannot set boolean value to %s", ptr.Kind())
}
case napi.TypeNumber:
switch ptrType.Kind() {
case reflect.Float32, reflect.Float64:
floatValue, err := napi.ToNumber(napiValue).Float()
if err != nil {
return err
}
ptr.SetFloat(floatValue)
return nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
numberValue, err := napi.ToNumber(napiValue).Int()
if err != nil {
return err
}
ptr.SetInt(numberValue)
return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
numberValue, err := napi.ToNumber(napiValue).Int()
if err != nil {
return err
}
ptr.SetUint(uint64(numberValue))
return nil
default:
return fmt.Errorf("cannot set number value to %s", ptr.Kind())
}
case napi.TypeBigInt:
switch ptrType.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
numberValue, err := napi.ToNumber(napiValue).Int()
if err != nil {
return err
}
ptr.SetInt(numberValue)
return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
numberValue, err := napi.ToNumber(napiValue).Int()
if err != nil {
return err
}
ptr.SetUint(uint64(numberValue))
return nil
default:
return fmt.Errorf("cannot set number value to %s", ptr.Kind())
}
case napi.TypeString:
switch ptr.Kind() {
case reflect.String:
default:
return fmt.Errorf("cannot set string to %s", ptr.Kind())
}
str, err := napi.ToString(napiValue).Utf8Value()
if err != nil {
return err
}
ptr.Set(reflect.ValueOf(str))
return nil
case napi.TypeDate:
switch ptrType.Kind() {
case reflect.Struct:
if ptrType == reflect.TypeFor[time.Time]() {
break
}
fallthrough
default:
return fmt.Errorf("cannot set Date to %s", ptr.Kind())
}
timeDate, err := napi.ToDate(napiValue).Time()
if err != nil {
return err
}
ptr.Set(reflect.ValueOf(timeDate))
return nil
case napi.TypeArray:
napiArray := napi.ToArray(napiValue)
size, err := napiArray.Length()
if err != nil {
return err
}
switch ptr.Kind() {
case reflect.Slice:
value := reflect.MakeSlice(ptrType, size, size)
for index := range size {
napiValue, err := napiArray.Get(index)
if err != nil {
return err
} else if err = valueFrom(napiValue, value.Index(index)); err != nil {
return err
}
}
ptr.Set(value)
return nil
case reflect.Array:
value := reflect.New(ptrType)
for index := range min(size, value.Len()) {
napiValue, err := napiArray.Get(index)
if err != nil {
return err
} else if err = valueFrom(napiValue, value.Index(index)); err != nil {
return err
}
}
ptr.Set(value)
return nil
default:
return fmt.Errorf("cannot set Array to %s", ptr.Kind())
}
case napi.TypeBuffer:
switch ptr.Kind() {
case reflect.Slice:
if ptrType == reflect.TypeFor[[]byte]() {
break
}
fallthrough
default:
return fmt.Errorf("cannot set Buffer to %s", ptr.Kind())
}
buff, err := napi.ToBuffer(napiValue).Data()
if err != nil {
return err
}
ptr.SetBytes(buff)
return nil
case napi.TypeObject:
obj := napi.ToObject(napiValue)
switch ptr.Kind() {
case reflect.Struct:
ptr.Set(reflect.New(ptrType).Elem())
for keyIndex := range ptrType.NumField() {
field, fieldType := ptr.Field(keyIndex), ptrType.Field(keyIndex)
if !fieldType.IsExported() || fieldType.Tag.Get(propertiesTagName) == "-" {
continue
}
keyName, omitEmpty, omitZero := fieldType.Name, false, false
if strings.Count(fieldType.Tag.Get(propertiesTagName), ",") > 0 {
fields := strings.SplitN(fieldType.Tag.Get(propertiesTagName), ",", 2)
keyName = fields[0]
switch fields[1] {
case "omitempty":
omitEmpty = true
case "omitzero":
omitZero = true
}
} else {
omitEmpty, omitZero = true, true
}
if ok, err := obj.Has(keyName); err != nil {
return err
} else if !ok && !(omitEmpty || omitZero) {
return fmt.Errorf("cannot set %s to %s", keyName, ptr.Kind())
}
value, err := obj.Get(keyName)
if err != nil {
return err
}
valueTypeof, _ := value.Type()
if omitEmpty || omitZero {
switch valueTypeof {
case napi.TypeUndefined, napi.TypeNull, napi.TypeUnkown:
continue
case napi.TypeString:
if str, _ := napi.ToString(value).Utf8Value(); str == "" {
continue
}
case napi.TypeDate:
if timeDate, _ := napi.ToDate(value).Time(); timeDate.Unix() == 0 {
continue
}
case napi.TypeBigInt:
if numberValue, _ := napi.ToBigint(value).Int64(); numberValue == 0 {
continue
}
case napi.TypeNumber:
if numberValue, _ := napi.ToNumber(value).Int(); numberValue == 0 {
continue
}
case napi.TypeArray:
if size, _ := napi.ToArray(value).Length(); size == 0 {
continue
}
}
}
valueOf := reflect.New(fieldType.Type).Elem()
if err := valueFrom(value, valueOf); err != nil {
return err
}
field.Set(valueOf)
}
return nil
case reflect.Map:
// Check if key is string, bool, int*, uint*, float*, else return error
switch ptrType.Key().Kind() {
case reflect.String:
case reflect.Bool:
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
case reflect.Float32, reflect.Float64:
default:
return fmt.Errorf("cannot set Object to %s", ptr.Kind())
}
goMap := reflect.MakeMap(ptrType)
for keyName, value := range obj.Seq() {
keySetValue := reflect.New(ptrType.Key()).Elem()
switch ptrType.Key().Kind() {
case reflect.String:
keySetValue.SetString(keyName)
case reflect.Bool:
boolV, err := strconv.ParseBool(keyName)
if err != nil {
return err
}
keySetValue.SetBool(boolV)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
intV, err := strconv.ParseInt(keyName, 10, 64)
if err != nil {
return err
}
keySetValue.SetInt(intV)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
intV, err := strconv.ParseUint(keyName, 10, 64)
if err != nil {
return err
}
keySetValue.SetUint(intV)
case reflect.Float32, reflect.Float64:
floatV, err := strconv.ParseFloat(keyName, 64)
if err != nil {
return err
}
keySetValue.SetFloat(floatV)
}
valueOf := reflect.New(ptrType.Elem()).Elem()
if err := valueFrom(value, valueOf); err != nil {
return err
}
goMap.SetMapIndex(keySetValue, valueOf)
}
ptr.Set(goMap)
return nil
default:
return fmt.Errorf("cannot set Object to %s", ptr.Kind())
}
default:
println(typeOf.String())
}
return nil
}

123
js/js_func.go Normal file
View File

@ -0,0 +1,123 @@
package js
import (
"fmt"
"reflect"
"runtime"
"sirherobrine23.com.br/Sirherobrine23/napi-go"
internalNapi "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi"
)
// Create napi bind to golang functions
func GoFuncOf(env napi.EnvType, function any) (napi.ValueType, error) {
return funcOf(env, reflect.ValueOf(function))
}
func funcOf(env napi.EnvType, ptr reflect.Value) (napi.ValueType, error) {
if ptr.Kind() != reflect.Func {
return nil, fmt.Errorf("return function to return napi value")
}
funcName := runtime.FuncForPC(ptr.Pointer()).Name()
switch v := ptr.Interface().(type) {
case nil:
return nil, nil
case napi.Callback:
return napi.CreateFunction(env, funcName, v)
case internalNapi.Callback:
return napi.CreateFunctionNapi(env, funcName, v)
default:
return napi.CreateFunction(env, funcName, func(env napi.EnvType, this napi.ValueType, args []napi.ValueType) (napi.ValueType, error) {
fnType := ptr.Type()
returnValues := []reflect.Value{}
switch {
case fnType.NumIn() == 0 && fnType.NumOut() == 0:
returnValues = ptr.Call([]reflect.Value{})
case !fnType.IsVariadic():
returnValues = ptr.Call(goValuesInFunc(ptr, args, false))
default:
returnValues = ptr.CallSlice(goValuesInFunc(ptr, args, true))
}
switch len(returnValues) {
case 0:
return nil, nil
case 1:
return valueOf(env, returnValues[0])
default:
lastValue := returnValues[len(returnValues)-1]
if lastValue.CanConvert(reflect.TypeFor[error]()) {
returnValues = returnValues[:len(returnValues)-1]
if !lastValue.IsNil() {
if err := lastValue.Interface().(error); err != nil {
return nil, err
}
}
}
switch len(returnValues) {
case 1:
// Return value from return
return valueOf(env, returnValues[0])
default:
// Create array
arr, err := napi.CreateArray(env, len(returnValues))
if err != nil {
return nil, err
}
// Append values to js array
for index, value := range returnValues {
napiValue, err := valueOf(env, value)
if err != nil {
return nil, err
} else if err = arr.Set(index, napiValue); err != nil {
return nil, err
}
}
return arr, nil
}
}
})
}
}
func goValuesInFunc(ptr reflect.Value, jsArgs []napi.ValueType, variadic bool) (values []reflect.Value) {
if variadic && (ptr.Type().NumIn()-1 > 0) && ptr.Type().NumIn()-1 < len(jsArgs) {
panic(fmt.Errorf("require minimun %d arguments, called with %d", ptr.Type().NumIn()-1, len(jsArgs)))
} else if !variadic &&ptr.Type().NumIn() != len(jsArgs) {
panic(fmt.Errorf("require %d arguments, called with %d", ptr.Type().NumIn(), len(jsArgs)))
}
size := ptr.Type().NumIn()
if variadic {
size--
}
values = make([]reflect.Value, size)
for index := range values {
valueOf := reflect.New(ptr.Type().In(index))
if err := valueFrom(jsArgs[index], valueOf); err != nil {
panic(err)
}
values[index] = valueOf
}
if variadic {
variadicType := ptr.Type().In(size).Elem()
valueAppend := jsArgs[size:]
valueOf := reflect.MakeSlice(reflect.SliceOf(variadicType), len(valueAppend), len(valueAppend))
for index := range valueAppend {
if err := valueFrom(valueAppend[index], valueOf.Index(index)); err != nil {
panic(err)
}
}
values = append(values, valueOf)
}
return
}

View File

@ -1,118 +0,0 @@
package js
import (
"errors"
"github.com/akshayganeshen/napi-go"
)
type Promise struct {
Promise napi.Promise
ThreadsafeFunction napi.ThreadsafeFunction
Result any
ResultType PromiseResultType
}
type PromiseResultType string
type PromiseProvider interface {
Resolve(resolution any)
Reject(rejection any)
}
var _ PromiseProvider = &Promise{}
const (
PromiseResultTypeResolved PromiseResultType = "resolved"
PromiseResultTypeRejected PromiseResultType = "rejected"
)
var ErrPromiseSettled = errors.New(
"Promise: Cannot resolve/reject a settled promise",
)
func (p *Promise) Resolve(resolution any) {
p.ensurePending()
p.Result = resolution
p.ResultType = PromiseResultTypeResolved
// function has already been acquired during reset
defer p.release()
p.settle()
}
func (p *Promise) Reject(rejection any) {
p.ensurePending()
p.Result = rejection
p.ResultType = PromiseResultTypeRejected
// function has already been acquired during reset
defer p.release()
p.settle()
}
func (p *Promise) reset(e Env) {
np, st := napi.CreatePromise(e.Env)
if st != napi.StatusOK {
panic(napi.StatusError(st))
}
asyncResourceName := e.ValueOf("napi-go/js-promise")
fn := e.FuncOf(func(env Env, this Value, args []Value) any {
value := env.ValueOf(p.Result)
st := napi.StatusOK
switch p.ResultType {
case PromiseResultTypeResolved:
st = napi.ResolveDeferred(env.Env, p.Promise.Deferred, value.Value)
case PromiseResultTypeRejected:
st = napi.RejectDeferred(env.Env, p.Promise.Deferred, value.Value)
}
if st != napi.StatusOK {
panic(napi.StatusError(st))
}
return nil
})
tsFn, st := napi.CreateThreadsafeFunction(
e.Env,
fn.Value.Value,
nil, asyncResourceName.Value,
0,
1, // initialize with 1 acquisition
)
if st != napi.StatusOK {
panic(napi.StatusError(st))
}
*p = Promise{
Promise: np,
ThreadsafeFunction: tsFn,
}
}
func (p *Promise) ensurePending() {
if p.ResultType != "" {
panic(ErrPromiseSettled)
}
}
func (p *Promise) settle() {
st := napi.CallThreadsafeFunction(p.ThreadsafeFunction)
if st != napi.StatusOK {
panic(napi.StatusError(st))
}
}
func (p *Promise) release() {
st := napi.ReleaseThreadsafeFunction(p.ThreadsafeFunction)
if st == napi.StatusClosing {
p.ThreadsafeFunction = nil
} else if st != napi.StatusOK {
panic(napi.StatusError(st))
}
}

View File

@ -1,14 +0,0 @@
package js
import (
"github.com/akshayganeshen/napi-go"
)
type Value struct {
Env Env
Value napi.Value
}
func (v Value) GetEnv() Env {
return v.Env
}

View File

@ -1,341 +0,0 @@
package napi
/*
#include <stdlib.h>
#include <node/node_api.h>
*/
import "C"
import (
"unsafe"
)
func GetUndefined(env Env) (Value, Status) {
var result Value
status := Status(C.napi_get_undefined(
C.napi_env(env),
(*C.napi_value)(unsafe.Pointer(&result)),
))
return result, status
}
func GetNull(env Env) (Value, Status) {
var result Value
status := Status(C.napi_get_null(
C.napi_env(env),
(*C.napi_value)(unsafe.Pointer(&result)),
))
return result, status
}
func GetGlobal(env Env) (Value, Status) {
var result Value
status := Status(C.napi_get_global(
C.napi_env(env),
(*C.napi_value)(unsafe.Pointer(&result)),
))
return result, status
}
func GetBoolean(env Env, value bool) (Value, Status) {
var result Value
status := Status(C.napi_get_boolean(
C.napi_env(env),
C.bool(value),
(*C.napi_value)(unsafe.Pointer(&result)),
))
return result, status
}
func CreateObject(env Env) (Value, Status) {
var result Value
status := Status(C.napi_create_object(
C.napi_env(env),
(*C.napi_value)(unsafe.Pointer(&result)),
))
return result, status
}
func CreateArray(env Env) (Value, Status) {
var result Value
status := Status(C.napi_create_array(
C.napi_env(env),
(*C.napi_value)(unsafe.Pointer(&result)),
))
return result, status
}
func CreateArrayWithLength(env Env, length int) (Value, Status) {
var result Value
status := Status(C.napi_create_array_with_length(
C.napi_env(env),
C.size_t(length),
(*C.napi_value)(unsafe.Pointer(&result)),
))
return result, status
}
func CreateDouble(env Env, value float64) (Value, Status) {
var result Value
status := Status(C.napi_create_double(
C.napi_env(env),
C.double(value),
(*C.napi_value)(unsafe.Pointer(&result)),
))
return result, status
}
func CreateStringUtf8(env Env, str string) (Value, Status) {
cstr := C.CString(str)
defer C.free(unsafe.Pointer(cstr))
var result Value
status := Status(C.napi_create_string_utf8(
C.napi_env(env),
cstr,
C.size_t(len([]byte(str))), // must pass number of bytes
(*C.napi_value)(unsafe.Pointer(&result)),
))
return result, status
}
func CreateSymbol(env Env, description Value) (Value, Status) {
var result Value
status := Status(C.napi_create_symbol(
C.napi_env(env),
C.napi_value(description),
(*C.napi_value)(unsafe.Pointer(&result)),
))
return result, status
}
func CreateFunction(env Env, name string, cb Callback) (Value, Status) {
provider, status := getInstanceData(env)
if status != StatusOK || provider == nil {
return nil, status
}
return provider.GetCallbackData().CreateCallback(env, name, cb)
}
func CreateError(env Env, code, msg Value) (Value, Status) {
var result Value
status := Status(C.napi_create_error(
C.napi_env(env),
C.napi_value(code),
C.napi_value(msg),
(*C.napi_value)(unsafe.Pointer(&result)),
))
return result, status
}
func Typeof(env Env, value Value) (ValueType, Status) {
var result ValueType
status := Status(C.napi_typeof(
C.napi_env(env),
C.napi_value(value),
(*C.napi_valuetype)(unsafe.Pointer(&result)),
))
return result, status
}
func GetValueDouble(env Env, value Value) (float64, Status) {
var result float64
status := Status(C.napi_get_value_double(
C.napi_env(env),
C.napi_value(value),
(*C.double)(unsafe.Pointer(&result)),
))
return result, status
}
func GetValueBool(env Env, value Value) (bool, Status) {
var result bool
status := Status(C.napi_get_value_bool(
C.napi_env(env),
C.napi_value(value),
(*C.bool)(unsafe.Pointer(&result)),
))
return result, status
}
func GetValueStringUtf8(env Env, value Value) (string, Status) {
// call napi_get_value_string_utf8 twice
// first is to get number of bytes
// second is to populate the actual string buffer
bufsize := C.size_t(0)
var strsize C.size_t
status := Status(C.napi_get_value_string_utf8(
C.napi_env(env),
C.napi_value(value),
nil,
bufsize,
&strsize,
))
if status != StatusOK {
return "", status
}
// ensure there is room for the null terminator as well
strsize++
cstr := (*C.char)(C.malloc(C.sizeof_char * strsize))
defer C.free(unsafe.Pointer(cstr))
status = Status(C.napi_get_value_string_utf8(
C.napi_env(env),
C.napi_value(value),
cstr,
strsize,
&strsize,
))
if status != StatusOK {
return "", status
}
return C.GoStringN(
(*C.char)(cstr),
(C.int)(strsize),
), status
}
func SetProperty(env Env, object, key, value Value) Status {
return Status(C.napi_set_property(
C.napi_env(env),
C.napi_value(object),
C.napi_value(key),
C.napi_value(value),
))
}
func SetElement(env Env, object Value, index int, value Value) Status {
return Status(C.napi_set_element(
C.napi_env(env),
C.napi_value(object),
C.uint32_t(index),
C.napi_value(value),
))
}
func StrictEquals(env Env, lhs, rhs Value) (bool, Status) {
var result bool
status := Status(C.napi_strict_equals(
C.napi_env(env),
C.napi_value(lhs),
C.napi_value(rhs),
(*C.bool)(&result),
))
return result, status
}
type GetCbInfoResult struct {
Args []Value
This Value
}
func GetCbInfo(env Env, info CallbackInfo) (GetCbInfoResult, Status) {
// call napi_get_cb_info twice
// first is to get total number of arguments
// second is to populate the actual arguments
argc := C.size_t(0)
status := Status(C.napi_get_cb_info(
C.napi_env(env),
C.napi_callback_info(info),
&argc,
nil,
nil,
nil,
))
if status != StatusOK {
return GetCbInfoResult{}, status
}
argv := make([]Value, int(argc))
var cArgv unsafe.Pointer
if argc > 0 {
cArgv = unsafe.Pointer(&argv[0]) // must pass element pointer
}
var thisArg Value
status = Status(C.napi_get_cb_info(
C.napi_env(env),
C.napi_callback_info(info),
&argc,
(*C.napi_value)(cArgv),
(*C.napi_value)(unsafe.Pointer(&thisArg)),
nil,
))
return GetCbInfoResult{
Args: argv,
This: thisArg,
}, status
}
func Throw(env Env, err Value) Status {
return Status(C.napi_throw(
C.napi_env(env),
C.napi_value(err),
))
}
func ThrowError(env Env, code, msg string) Status {
codeCStr, msgCCstr := C.CString(code), C.CString(msg)
defer C.free(unsafe.Pointer(codeCStr))
defer C.free(unsafe.Pointer(msgCCstr))
return Status(C.napi_throw_error(
C.napi_env(env),
codeCStr,
msgCCstr,
))
}
func CreatePromise(env Env) (Promise, Status) {
var result Promise
status := Status(C.napi_create_promise(
C.napi_env(env),
(*C.napi_deferred)(unsafe.Pointer(&result.Deferred)),
(*C.napi_value)(unsafe.Pointer(&result.Value)),
))
return result, status
}
func ResolveDeferred(env Env, deferred Deferred, resolution Value) Status {
return Status(C.napi_resolve_deferred(
C.napi_env(env),
C.napi_deferred(deferred),
C.napi_value(resolution),
))
}
func RejectDeferred(env Env, deferred Deferred, rejection Value) Status {
return Status(C.napi_reject_deferred(
C.napi_env(env),
C.napi_deferred(deferred),
C.napi_value(rejection),
))
}
func SetInstanceData(env Env, data any) Status {
provider, status := getInstanceData(env)
if status != StatusOK || provider == nil {
return status
}
provider.SetUserData(data)
return status
}
func GetInstanceData(env Env) (any, Status) {
provider, status := getInstanceData(env)
if status != StatusOK || provider == nil {
return nil, status
}
return provider.GetUserData(), status
}

View File

@ -1,40 +0,0 @@
all: doc
clean: clean-doc
EXAMPLE_DIR = docs/examples
EXAMPLE_PACKAGES = \
async-promise \
callback \
describe-args \
hello-world \
js
NAPI_LIB_SUFFIX = .node
TARGET_BUILDDIR = build
EXAMPLE_BINDINGS = $(addsuffix $(NAPI_LIB_SUFFIX),$(EXAMPLE_PACKAGES))
TARGET_EXAMPLES = \
$(addprefix $(TARGET_BUILDDIR)/, $(EXAMPLE_BINDINGS))
# TODO: Configure CGO_LDFLAGS_ALLOW for non-darwin systems.
CGO_LDFLAGS_ALLOW = (-Wl,(-undefined,dynamic_lookup|-no_pie|-search_paths_first))
doc: $(TARGET_EXAMPLES)
$(TARGET_EXAMPLES): | $(TARGET_BUILDDIR)
$(TARGET_EXAMPLES): $(TARGET_BUILDDIR)/%$(NAPI_LIB_SUFFIX): $(EXAMPLE_DIR)/%
CGO_LDFLAGS_ALLOW='$(CGO_LDFLAGS_ALLOW)' \
go build -buildmode=c-shared -o "$(@)" "./$(<)/"
$(TARGET_BUILDDIR):
mkdir -p "$(TARGET_BUILDDIR)"
clean:
rmdir "$(TARGET_BUILDDIR)"
clean-doc:
rm -f $(patsubst %,"%",$(TARGET_EXAMPLES))
.PHONY: all doc
.PHONY: clean clean-doc

50
napi_env.go Normal file
View File

@ -0,0 +1,50 @@
package napi
import "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi"
type EnvType interface {
NapiValue() napi.Env // Primitive value to NAPI call
Global() (*Object, error)
Undefined() (ValueType, error)
Null() (ValueType, error)
}
// Return N-API env reference
func N_APIEnv(env napi.Env) EnvType { return &Env{env} }
// N-API Env
type Env struct{
NapiEnv napi.Env
}
// Return [napi.Env] to point from internal napi cgo
func (e *Env) NapiValue() napi.Env {
return e.NapiEnv
}
// Return representantion to 'This' [*Object]
func (e *Env) Global() (*Object, error) {
napiValue, err := mustValueErr(napi.GetGlobal(e.NapiEnv))
if err != nil {
return nil, err
}
return ToObject(N_APIValue(e, napiValue)), nil
}
// Return Undefined value
func (e *Env) Undefined() (ValueType, error) {
napiValue, err := mustValueErr(napi.GetUndefined(e.NapiEnv))
if err != nil {
return nil, err
}
return N_APIValue(e, napiValue), nil
}
// Return Null value
func (e *Env) Null() (ValueType, error) {
napiValue, err := mustValueErr(napi.GetNull(e.NapiEnv))
if err != nil {
return nil, err
}
return N_APIValue(e, napiValue), nil
}

35
napi_status.go Normal file
View File

@ -0,0 +1,35 @@
package napi
import "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi"
// Process status to return error if StatusOK return nil on error
func mustValueErr[T any](input T, status napi.Status) (T, error) {
if status != napi.StatusOK {
return input, napi.StatusError(status)
}
return input, nil
}
// return error from status
func singleMustValueErr(status napi.Status) error {
if status != napi.StatusOK {
return napi.StatusError(status)
}
return nil
}
// Process status to return error if StatusOK return nil on error
func mustValueErr2[T any](input T, _ bool, status napi.Status) (T, error) {
if status != napi.StatusOK {
return input, napi.StatusError(status)
}
return input, nil
}
// Process status to return error if StatusOK return nil on error
func mustValueErr3[T, C any](input T, i2 C, status napi.Status) (T, C, error) {
if status != napi.StatusOK {
return input, i2, napi.StatusError(status)
}
return input, i2, nil
}

167
napi_value.go Normal file
View File

@ -0,0 +1,167 @@
package napi
import (
"fmt"
"sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi"
)
type ValueType interface {
NapiValue() napi.Value // Primitive value to NAPI call
NapiEnv() napi.Env // NAPI Env to NAPI call
Env() EnvType // NAPI Env to NAPI call
Type() (NapiType, error) // NAPI Type of value
}
type (
value = ValueType // to dont expose to external structs
// Generic type to NAPI value
Value struct {
env EnvType
valueOf napi.Value
}
NapiType int // Return typeof of Value
)
const (
TypeUnkown NapiType = iota
TypeUndefined
TypeNull
TypeBoolean
TypeNumber
TypeBigInt
TypeString
TypeSymbol
TypeObject
TypeFunction
TypeExternal
TypeTypedArray
TypePromise
TypeDataView
TypeBuffer
TypeDate
TypeArray
TypeArrayBuffer
TypeError
)
var napiTypeNames = map[NapiType]string{
TypeUnkown: "Unknown",
TypeUndefined: "Undefined",
TypeNull: "Null",
TypeBoolean: "Boolean",
TypeNumber: "Number",
TypeBigInt: "BigInt",
TypeString: "String",
TypeSymbol: "Symbol",
TypeObject: "Object",
TypeFunction: "Function",
TypeExternal: "External",
TypeTypedArray: "TypedArray",
TypePromise: "Promise",
TypeDataView: "DaraView",
TypeBuffer: "Buffer",
TypeDate: "Date",
TypeArray: "Array",
TypeArrayBuffer: "ArrayBuffer",
TypeError: "Error",
}
// Return [ValueType] from [napi.Value]
func N_APIValue(env EnvType, value napi.Value) ValueType {
return &Value{env: env, valueOf: value}
}
func (v *Value) NapiValue() napi.Value { return v.valueOf }
func (v *Value) NapiEnv() napi.Env { return v.env.NapiValue() }
func (v *Value) Env() EnvType { return v.env }
func (v *Value) Type() (NapiType, error) {
isTypedArray, err := mustValueErr(napi.IsTypedArray(v.NapiEnv(), v.NapiValue()))
if err != nil {
return TypeUnkown, err
}
isPromise, err := mustValueErr(napi.IsPromise(v.NapiEnv(), v.NapiValue()))
if err != nil {
return TypeUnkown, err
}
isDataView, err := mustValueErr(napi.IsDataView(v.NapiEnv(), v.NapiValue()))
if err != nil {
return TypeUnkown, err
}
isBuffer, err := mustValueErr(napi.IsBuffer(v.NapiEnv(), v.NapiValue()))
if err != nil {
return TypeUnkown, err
}
isDate, err := mustValueErr(napi.IsDate(v.NapiEnv(), v.NapiValue()))
if err != nil {
return TypeUnkown, err
}
isArray, err := mustValueErr(napi.IsArray(v.NapiEnv(), v.NapiValue()))
if err != nil {
return TypeUnkown, err
}
isArrayBuffer, err := mustValueErr(napi.IsArrayBuffer(v.NapiEnv(), v.NapiValue()))
if err != nil {
return TypeUnkown, err
}
isError, err := mustValueErr(napi.IsError(v.NapiEnv(), v.NapiValue()))
if err != nil {
return TypeUnkown, err
}
isTypeof, err := mustValueErr(napi.Typeof(v.NapiEnv(), v.NapiValue()))
if err != nil {
return TypeUnkown, err
}
switch {
case isTypedArray:
return TypeTypedArray, nil
case isPromise:
return TypePromise, nil
case isDataView:
return TypeDataView, nil
case isBuffer:
return TypeBuffer, nil
case isDate:
return TypeDate, nil
case isArray:
return TypeArray, nil
case isArrayBuffer:
return TypeArrayBuffer, nil
case isError:
return TypeError, nil
case isTypeof == napi.ValueTypeUndefined:
return TypeUndefined, nil
case isTypeof == napi.ValueTypeNull:
return TypeNull, nil
case isTypeof == napi.ValueTypeBoolean:
return TypeBoolean, nil
case isTypeof == napi.ValueTypeNumber:
return TypeNumber, nil
case isTypeof == napi.ValueTypeString:
return TypeString, nil
case isTypeof == napi.ValueTypeSymbol:
return TypeSymbol, nil
case isTypeof == napi.ValueTypeObject:
return TypeObject, nil
case isTypeof == napi.ValueTypeFunction:
return TypeFunction, nil
case isTypeof == napi.ValueTypeExternal:
return TypeExternal, nil
case isTypeof == napi.ValueTypeBigint:
return TypeBigInt, nil
}
return TypeUnkown, nil
}
func (t NapiType) String() string {
if name, ok := napiTypeNames[t]; ok {
return name
}
return fmt.Sprintf("Unknown NapiType %d", t)
}

114
number.go Normal file
View File

@ -0,0 +1,114 @@
package napi
import (
"fmt"
"sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi"
)
type Number struct{ value }
type Bigint struct{ value }
// Convert [ValueType] to [*Number]
func ToNumber(o ValueType) *Number { return &Number{o} }
// Convert [ValueType] to [*Bigint]
func ToBigint(o ValueType) *Bigint { return &Bigint{o} }
func (num *Number) Float() (float64, error) {
return mustValueErr(napi.GetValueDouble(num.NapiEnv(), num.NapiValue()))
}
func (num *Number) Int() (int64, error) {
return mustValueErr(napi.GetValueInt64(num.NapiEnv(), num.NapiValue()))
}
func (num *Number) Uint32() (uint32, error) {
return mustValueErr(napi.GetValueUint32(num.NapiEnv(), num.NapiValue()))
}
func (num *Number) Int32() (int32, error) {
return mustValueErr(napi.GetValueInt32(num.NapiEnv(), num.NapiValue()))
}
func (big *Bigint) Int64() (int64, error) {
return mustValueErr2(napi.GetValueBigIntInt64(big.NapiEnv(), big.NapiValue()))
}
func (big *Bigint) Uint64() (uint64, error) {
return mustValueErr2(napi.GetValueBigIntUint64(big.NapiEnv(), big.NapiValue()))
}
func CreateBigint[T int64 | uint64](env EnvType, valueOf T) (*Bigint, error) {
var value napi.Value
var err error
switch v := any(valueOf).(type) {
case int64:
if value, err = mustValueErr(napi.CreateBigIntInt64(env.NapiValue(), v)); err != nil {
return nil, err
}
case uint64:
if value, err = mustValueErr(napi.CreateBigIntUint64(env.NapiValue(), v)); err != nil {
return nil, err
}
}
return &Bigint{value: &Value{env: env, valueOf: value}}, nil
}
func CreateNumber[T ~int | ~uint | ~int8 | ~uint8 | ~int16 | ~uint16 | ~int32 | ~uint32 | ~int64 | ~uint64 | ~float32 | ~float64](env EnvType, n T) (*Number, error) {
var value napi.Value
var err error
switch v := any(n).(type) {
case int:
if value, err = mustValueErr(napi.CreateInt64(env.NapiValue(), int64(v))); err != nil {
return nil, err
}
case uint:
if value, err = mustValueErr(napi.CreateInt64(env.NapiValue(), int64(v))); err != nil {
return nil, err
}
case int8:
if value, err = mustValueErr(napi.CreateInt64(env.NapiValue(), int64(v))); err != nil {
return nil, err
}
case uint8:
if value, err = mustValueErr(napi.CreateInt64(env.NapiValue(), int64(v))); err != nil {
return nil, err
}
case int16:
if value, err = mustValueErr(napi.CreateInt64(env.NapiValue(), int64(v))); err != nil {
return nil, err
}
case uint16:
if value, err = mustValueErr(napi.CreateInt64(env.NapiValue(), int64(v))); err != nil {
return nil, err
}
case int32:
if value, err = mustValueErr(napi.CreateInt32(env.NapiValue(), v)); err != nil {
return nil, err
}
case uint32:
if value, err = mustValueErr(napi.CreateUint32(env.NapiValue(), v)); err != nil {
return nil, err
}
case int64:
if value, err = mustValueErr(napi.CreateInt64(env.NapiValue(), v)); err != nil {
return nil, err
}
case uint64:
if value, err = mustValueErr(napi.CreateInt64(env.NapiValue(), int64(v))); err != nil {
return nil, err
}
case float32:
if value, err = mustValueErr(napi.CreateDouble(env.NapiValue(), float64(v))); err != nil {
return nil, err
}
case float64:
if value, err = mustValueErr(napi.CreateDouble(env.NapiValue(), v)); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("invalid number type")
}
return ToNumber(N_APIValue(env, value)), err
}

134
object.go Normal file
View File

@ -0,0 +1,134 @@
package napi
import (
"iter"
"sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi"
)
type Object struct{ value }
// Convert ValueType to [*Object]
func ToObject(o ValueType) *Object { return &Object{o} }
// Create [*Object]
func CreateObject(env EnvType) (*Object, error) {
napiValue, err := mustValueErr(napi.CreateObject(env.NapiValue()))
if err != nil {
return nil, err
}
return ToObject(N_APIValue(env, napiValue)), nil
}
// Check if exists named property.
func (obj *Object) Has(name string) (bool, error) {
return mustValueErr(napi.HasNamedProperty(obj.NapiEnv(), obj.NapiValue(), name))
}
// Checks whether a own property is present.
func (obj *Object) HasOwnProperty(key ValueType) (bool, error) {
return mustValueErr(napi.HasOwnProperty(obj.NapiEnv(), obj.NapiValue(), key.NapiValue()))
}
// Checks whether a own property is present.
func (obj *Object) HasOwnPropertyString(keyString string) (bool, error) {
napiString, err := CreateString(obj.Env(), keyString)
if err != nil {
return false, err
}
return obj.HasOwnProperty(napiString)
}
// Gets a property.
func (obj *Object) Get(key string) (ValueType, error) {
keyValue, err := CreateString(obj.Env(), key)
if err != nil {
return nil, err
}
return obj.GetWithValue(keyValue)
}
// Gets a property.
func (obj *Object) GetWithValue(key ValueType) (ValueType, error) {
napiValue, err := mustValueErr(napi.GetProperty(obj.Env().NapiValue(), obj.NapiValue(), key.NapiValue()))
if err != nil {
return nil, err
}
return N_APIValue(obj.Env(), napiValue), nil
}
// Sets a property.
func (obj *Object) Set(key string, value ValueType) error {
keyValue, err := CreateString(obj.Env(), key)
if err != nil {
return err
}
return obj.SetWithValue(keyValue, value)
}
// Sets a property.
func (obj *Object) SetWithValue(key, value ValueType) error {
return singleMustValueErr(napi.SetProperty(obj.NapiEnv(), obj.NapiValue(), key.NapiValue(), value.NapiValue()))
}
// Delete property.
func (obj *Object) Delete(key string) (bool, error) {
keyValue, err := CreateString(obj.Env(), key)
if err != nil {
return false, err
}
return obj.DeleteWithValue(keyValue)
}
// Delete property.
func (obj *Object) DeleteWithValue(key ValueType) (bool, error) {
return mustValueErr(napi.DeleteProperty(obj.NapiEnv(), obj.NapiValue(), key.NapiValue()))
}
// Get all property names.
func (obj *Object) GetPropertyNames() (*Array, error) {
arrValue, err := mustValueErr(napi.GetPropertyNames(obj.NapiEnv(), obj.NapiValue()))
if err != nil {
return nil, err
}
return ToArray(N_APIValue(obj.Env(), arrValue)), nil
}
// Checks if an object is an instance created by a constructor function,
// this is equivalent to the JavaScript `instanceof` operator.
func (obj *Object) InstanceOf(value ValueType) (bool, error) {
return mustValueErr(napi.InstanceOf(obj.NapiEnv(), obj.NapiValue(), value.NapiValue()))
}
// Freeze object.
func (obj *Object) Freeze() error {
return singleMustValueErr(napi.ObjectFreeze(obj.NapiEnv(), obj.NapiValue()))
}
func (obj *Object) Seal() error {
return singleMustValueErr(napi.ObjectSeal(obj.NapiEnv(), obj.NapiValue()))
}
func (obj *Object) Seq() iter.Seq2[string, ValueType] {
keys, err := obj.GetPropertyNames()
if err != nil {
panic(err)
}
return func(yield func(string, ValueType) bool) {
for key := range keys.Seq() {
value, err := obj.GetWithValue(key)
if err != nil {
panic(err)
}
keyName, err := ToString(key).Utf8Value()
if err != nil {
panic(err)
}
if !yield(keyName, value) {
return
}
}
}
}

44
string.go Normal file
View File

@ -0,0 +1,44 @@
package napi
import (
"unicode/utf16"
"sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi"
)
type String struct{ value }
// Convert [ValueType] to [*String]
func ToString(o ValueType) *String { return &String{o} }
// Create [*String] from go string
func CreateString(env EnvType, str string) (*String, error) {
napiString, err := mustValueErr(napi.CreateStringUtf8(env.NapiValue(), str))
if err != nil {
return nil, err
}
return ToString(N_APIValue(env, napiString)), nil
}
// Create string to utf16
func CreateStringUtf16(env EnvType, str []rune) (*String, error) {
napiString, err := mustValueErr(napi.CreateStringUtf16(env.NapiValue(), utf16.Encode(str)))
if err != nil {
return nil, err
}
return ToString(N_APIValue(env, napiString)), nil
}
// Get String value.
func (str *String) Utf8Value() (string, error) {
return mustValueErr(napi.GetValueStringUtf8(str.NapiEnv(), str.NapiValue()))
}
// Converts a String value to a UTF-16 encoded in rune.
func (str *String) Utf16Value() ([]rune, error) {
valueOf, err := mustValueErr(napi.GetValueStringUtf16(str.NapiEnv(), str.NapiValue()))
if err != nil {
return nil, err
}
return utf16.Decode(valueOf), nil
}

View File

@ -1,21 +0,0 @@
package napi
/*
#include <node/node_api.h>
*/
import "C"
type ValueType int
const (
ValueTypeUndefined ValueType = C.napi_undefined
ValueTypeNull ValueType = C.napi_null
ValueTypeBoolean ValueType = C.napi_boolean
ValueTypeNumber ValueType = C.napi_number
ValueTypeString ValueType = C.napi_string
ValueTypeSymbol ValueType = C.napi_symbol
ValueTypeObject ValueType = C.napi_object
ValueTypeFunction ValueType = C.napi_function
ValueTypeExternal ValueType = C.napi_external
ValueTypeBigint ValueType = C.napi_bigint
)