├── LICENSE ├── README.md ├── async_work.go ├── callback.go ├── callback_info.go ├── docs └── examples │ ├── async-promise │ └── main.go │ ├── callback │ └── main.go │ ├── describe-args │ └── main.go │ ├── hello-world │ └── main.go │ └── js │ └── main.go ├── entry ├── entry.h ├── exports.go ├── napi_module.c ├── napi_module.go └── node_api_cgo_flags.go ├── env.go ├── go.mod ├── instance_data.go ├── js ├── callback.go ├── env.go ├── func.go ├── promise.go └── value.go ├── js_native_api.go ├── makefile ├── node_api.go ├── node_api_cgo_flags.go ├── node_version.go ├── promise.go ├── status.go ├── threadsafe_function.go ├── value.go └── value_type.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Akshay Ganeshen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # napi-go 2 | 3 | A Go library for building Node.js Native Addons using Node-API. 4 | 5 | ## Usage 6 | 7 | Use `go get` to install the library: 8 | 9 | ```sh 10 | go get -u github.com/akshayganeshen/napi-go 11 | ``` 12 | 13 | Then use the library to define handlers: 14 | 15 | ```go 16 | package handlers 17 | 18 | import "github.com/akshayganeshen/napi-go" 19 | 20 | func MyHandler(env napi.Env, info napi.CallbackInfo) napi.Value { 21 | return nil 22 | } 23 | ``` 24 | 25 | Next, create a `main.go` that registers all module exports: 26 | 27 | ```go 28 | package main 29 | 30 | import "github.com/akshayganeshen/napi-go/entry" 31 | 32 | func init() { 33 | entry.Export("myHandler", MyHandler) 34 | } 35 | 36 | func main() {} 37 | ``` 38 | 39 | Finally, build the Node.js addon using `go build`: 40 | 41 | ```sh 42 | go build -buildmode=c-shared -o "example.node" . 43 | ``` 44 | 45 | The output `.node` file can now be imported via `require`: 46 | 47 | ```js 48 | const example = require("./example.node"); 49 | 50 | example.myHandler(); 51 | ``` 52 | 53 | ### JS Helpers 54 | 55 | In addition to the Node-API exposed via package `napi`, the `napi-go/js` 56 | package provides functions similar to the `syscall/js` standard library. 57 | 58 | ```go 59 | package main 60 | 61 | import ( 62 | "github.com/akshayganeshen/napi-go/entry" 63 | "github.com/akshayganeshen/napi-go/js" 64 | ) 65 | 66 | func init() { 67 | entry.Export("myCallback", js.AsCallback(MyCallback)) 68 | } 69 | 70 | func MyCallback(env js.Env, this js.Value, args []js.Value) any { 71 | return map[string]any{ 72 | "message": "hello world", 73 | "args": args, 74 | } 75 | } 76 | 77 | func main() {} 78 | ``` 79 | 80 | ## Examples 81 | 82 | Check out the example addons in [`docs/examples`](docs/examples). 83 | -------------------------------------------------------------------------------- /async_work.go: -------------------------------------------------------------------------------- 1 | package napi 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | type AsyncWork struct { 8 | Handle unsafe.Pointer 9 | ID NapiGoAsyncWorkID 10 | } 11 | 12 | type AsyncExecuteCallback func(env Env) 13 | 14 | type AsyncCompleteCallback func(env Env, status Status) 15 | -------------------------------------------------------------------------------- /callback.go: -------------------------------------------------------------------------------- 1 | package napi 2 | 3 | type Callback func(env Env, info CallbackInfo) Value 4 | -------------------------------------------------------------------------------- /callback_info.go: -------------------------------------------------------------------------------- 1 | package napi 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | type CallbackInfo unsafe.Pointer 8 | -------------------------------------------------------------------------------- /docs/examples/async-promise/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/akshayganeshen/napi-go" 8 | "github.com/akshayganeshen/napi-go/entry" 9 | ) 10 | 11 | func init() { 12 | entry.Export("getPromise", GetPromiseHandler) 13 | } 14 | 15 | func GetPromiseHandler(env napi.Env, info napi.CallbackInfo) napi.Value { 16 | result, _ := napi.CreatePromise(env) 17 | asyncResourceName, _ := napi.CreateStringUtf8( 18 | env, 19 | "napi-go/async-promise-example", 20 | ) 21 | 22 | var asyncWork napi.AsyncWork 23 | asyncWork, _ = napi.CreateAsyncWork( 24 | env, 25 | nil, asyncResourceName, 26 | func(env napi.Env) { 27 | fmt.Printf("AsyncExecuteCallback(start)\n") 28 | defer fmt.Printf("AsyncExecuteCallback(stop)\n") 29 | time.Sleep(time.Second) 30 | }, 31 | func(env napi.Env, status napi.Status) { 32 | defer napi.DeleteAsyncWork(env, asyncWork) 33 | 34 | if status == napi.StatusCancelled { 35 | fmt.Printf("AsyncCompleteCallback(cancelled)\n") 36 | return 37 | } 38 | 39 | fmt.Printf("AsyncCompleteCallback\n") 40 | resolution, _ := napi.CreateStringUtf8(env, "resolved") 41 | napi.ResolveDeferred(env, result.Deferred, resolution) 42 | }, 43 | ) 44 | napi.QueueAsyncWork(env, asyncWork) 45 | 46 | return result.Value 47 | } 48 | 49 | func main() {} 50 | -------------------------------------------------------------------------------- /docs/examples/callback/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/akshayganeshen/napi-go" 5 | "github.com/akshayganeshen/napi-go/entry" 6 | ) 7 | 8 | func init() { 9 | entry.Export("getCallback", GetCallbackHandler) 10 | } 11 | 12 | func GetCallbackHandler(env napi.Env, info napi.CallbackInfo) napi.Value { 13 | result, _ := napi.CreateFunction( 14 | env, 15 | "callback", 16 | func(env napi.Env, info napi.CallbackInfo) napi.Value { 17 | result, _ := napi.CreateStringUtf8(env, "hello world") 18 | return result 19 | }, 20 | ) 21 | 22 | return result 23 | } 24 | 25 | func main() {} 26 | -------------------------------------------------------------------------------- /docs/examples/describe-args/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/akshayganeshen/napi-go" 5 | "github.com/akshayganeshen/napi-go/entry" 6 | ) 7 | 8 | func init() { 9 | entry.Export("describeArgs", DescribeArgsHandler) 10 | } 11 | 12 | func DescribeArgsHandler(env napi.Env, info napi.CallbackInfo) napi.Value { 13 | extractedInfo, _ := napi.GetCbInfo(env, info) 14 | result, _ := napi.CreateArrayWithLength(env, len(extractedInfo.Args)) 15 | for i, arg := range extractedInfo.Args { 16 | vt, _ := napi.Typeof(env, arg) 17 | dv, _ := napi.CreateStringUtf8(env, DescribeValueType(vt)) 18 | napi.SetElement(env, result, i, dv) 19 | } 20 | 21 | return result 22 | } 23 | 24 | func DescribeValueType(vt napi.ValueType) string { 25 | switch vt { 26 | case napi.ValueTypeUndefined: 27 | return "undefined" 28 | case napi.ValueTypeNull: 29 | return "null" 30 | case napi.ValueTypeBoolean: 31 | return "boolean" 32 | case napi.ValueTypeNumber: 33 | return "number" 34 | case napi.ValueTypeString: 35 | return "string" 36 | case napi.ValueTypeSymbol: 37 | return "symbol" 38 | case napi.ValueTypeObject: 39 | return "object" 40 | case napi.ValueTypeFunction: 41 | return "function" 42 | case napi.ValueTypeExternal: 43 | return "external" 44 | case napi.ValueTypeBigint: 45 | return "bigint" 46 | 47 | default: 48 | return "other" 49 | } 50 | } 51 | 52 | func main() {} 53 | -------------------------------------------------------------------------------- /docs/examples/hello-world/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/akshayganeshen/napi-go" 7 | "github.com/akshayganeshen/napi-go/entry" 8 | ) 9 | 10 | func init() { 11 | entry.Export("hello", HelloHandler) 12 | } 13 | 14 | func HelloHandler(env napi.Env, info napi.CallbackInfo) napi.Value { 15 | fmt.Println("hello world!") 16 | return nil 17 | } 18 | 19 | func main() {} 20 | -------------------------------------------------------------------------------- /docs/examples/js/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/akshayganeshen/napi-go" 8 | "github.com/akshayganeshen/napi-go/entry" 9 | "github.com/akshayganeshen/napi-go/js" 10 | ) 11 | 12 | func init() { 13 | entry.Export("getMap", GetMapHandler) 14 | entry.Export("getCallback", js.AsCallback(GetCallback)) 15 | entry.Export("getArray", js.AsCallback(GetArray)) 16 | entry.Export("getPromiseResolve", js.AsCallback(GetPromiseResolve)) 17 | entry.Export("getPromiseReject", js.AsCallback(GetPromiseReject)) 18 | } 19 | 20 | func GetMapHandler(env napi.Env, info napi.CallbackInfo) napi.Value { 21 | jsEnv := js.AsEnv(env) 22 | 23 | return jsEnv.ValueOf( 24 | map[string]any{ 25 | "string": "hello world", 26 | "number": 123, 27 | "bool": false, 28 | "undefined": jsEnv.Undefined(), 29 | "null": nil, 30 | "function": jsEnv.FuncOf( 31 | func(env js.Env, this js.Value, args []js.Value) any { 32 | return "hello world" 33 | }, 34 | ), 35 | }, 36 | ).Value 37 | } 38 | 39 | func GetCallback(env js.Env, this js.Value, args []js.Value) any { 40 | return func(env js.Env, this js.Value, args []js.Value) any { 41 | return map[string]any{ 42 | "this": this, 43 | "args": args, 44 | } 45 | } 46 | } 47 | 48 | func GetArray(env js.Env, this js.Value, args []js.Value) any { 49 | return []any{ 50 | "hello world", 51 | 123, 52 | true, 53 | map[string]any{ 54 | "key": "value", 55 | }, 56 | } 57 | } 58 | 59 | func GetPromiseResolve(env js.Env, this js.Value, args []js.Value) any { 60 | promise := env.NewPromise() 61 | 62 | go func() { 63 | time.Sleep(time.Second) 64 | promise.Resolve("resolved") 65 | }() 66 | 67 | return promise 68 | } 69 | 70 | func GetPromiseReject(env js.Env, this js.Value, args []js.Value) any { 71 | promise := env.NewPromise() 72 | 73 | go func() { 74 | time.Sleep(time.Second) 75 | promise.Reject(fmt.Errorf("rejected")) 76 | }() 77 | 78 | return promise 79 | } 80 | 81 | func main() {} 82 | -------------------------------------------------------------------------------- /entry/entry.h: -------------------------------------------------------------------------------- 1 | #ifndef __ENTRY_ENTRY_H__ 2 | #define __ENTRY_ENTRY_H__ 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif /* __cplusplus */ 9 | 10 | // InitializeModule is a N-API module initialization function. 11 | // InitializeModule is suitable for use as a napi_addon_register_func. 12 | extern napi_value InitializeModule( 13 | napi_env env, 14 | napi_value exports 15 | ); 16 | 17 | #ifdef __cplusplus 18 | } 19 | #endif /* __cplusplus */ 20 | 21 | #endif /* __ENTRY_ENTRY_H__ */ 22 | -------------------------------------------------------------------------------- /entry/exports.go: -------------------------------------------------------------------------------- 1 | package entry 2 | 3 | import ( 4 | "github.com/akshayganeshen/napi-go" 5 | ) 6 | 7 | type napiGoExport struct { 8 | Name string 9 | Callback napi.Callback 10 | } 11 | 12 | var napiGoGlobalExports []napiGoExport 13 | 14 | func Export(name string, callback napi.Callback) { 15 | napiGoGlobalExports = append(napiGoGlobalExports, napiGoExport{ 16 | Name: name, 17 | Callback: callback, 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /entry/napi_module.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "./entry.h" 4 | 5 | NAPI_MODULE(napiGo, InitializeModule) 6 | -------------------------------------------------------------------------------- /entry/napi_module.go: -------------------------------------------------------------------------------- 1 | package entry 2 | 3 | /* 4 | #include 5 | 6 | #include "./entry.h" 7 | */ 8 | import "C" 9 | 10 | import ( 11 | "github.com/akshayganeshen/napi-go" 12 | ) 13 | 14 | //export InitializeModule 15 | func InitializeModule(cEnv C.napi_env, cExports C.napi_value) C.napi_value { 16 | env, exports := napi.Env(cEnv), napi.Value(cExports) 17 | napi.InitializeInstanceData(env) 18 | 19 | for _, export := range napiGoGlobalExports { 20 | cb, _ := napi.CreateFunction(env, export.Name, export.Callback) 21 | name, _ := napi.CreateStringUtf8(env, export.Name) 22 | napi.SetProperty(env, exports, name, cb) 23 | } 24 | 25 | return cExports 26 | } 27 | -------------------------------------------------------------------------------- /entry/node_api_cgo_flags.go: -------------------------------------------------------------------------------- 1 | package entry 2 | 3 | /* 4 | #cgo CFLAGS: -DDEBUG 5 | #cgo CFLAGS: -D_DEBUG 6 | #cgo CFLAGS: -DV8_ENABLE_CHECKS 7 | #cgo CFLAGS: -DNAPI_EXPERIMENTAL 8 | #cgo CFLAGS: -I/usr/local/include/node 9 | #cgo CXXFLAGS: -std=c++11 10 | 11 | #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup 12 | #cgo darwin LDFLAGS: -Wl,-no_pie 13 | #cgo darwin LDFLAGS: -Wl,-search_paths_first 14 | #cgo darwin LDFLAGS: -arch x86_64 15 | 16 | #cgo linux LDFLAGS: -Wl,-unresolved-symbols=ignore-all 17 | 18 | #cgo LDFLAGS: -L${SRCDIR} 19 | #cgo LDFLAGS: -stdlib=libc++ 20 | */ 21 | import "C" 22 | -------------------------------------------------------------------------------- /env.go: -------------------------------------------------------------------------------- 1 | package napi 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | type Env unsafe.Pointer 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/akshayganeshen/napi-go 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /instance_data.go: -------------------------------------------------------------------------------- 1 | package napi 2 | 3 | /* 4 | #include 5 | #include 6 | 7 | extern void DeleteInstanceData( 8 | napi_env env, 9 | void *finalize_data, 10 | void *finalize_hint 11 | ); 12 | 13 | extern void DeleteCallbackData( 14 | napi_env env, 15 | void *finalize_data, 16 | void *finalize_hint 17 | ); 18 | 19 | extern napi_value ExecuteCallback( 20 | napi_env env, 21 | napi_callback_info info 22 | ); 23 | 24 | extern void ExecuteAsyncExecuteCallback( 25 | napi_env env, 26 | void *data 27 | ); 28 | 29 | extern void ExecuteAsyncCompleteCallback( 30 | napi_env env, 31 | napi_status status, 32 | void *data 33 | ); 34 | */ 35 | import "C" 36 | 37 | import ( 38 | "fmt" 39 | "runtime" 40 | "runtime/cgo" 41 | "sync" 42 | "unsafe" 43 | ) 44 | 45 | type NapiGoInstanceData struct { 46 | UserData any 47 | CallbackData NapiGoInstanceCallbackData 48 | AsyncWorkData NapiGoInstanceAsyncWorkData 49 | } 50 | 51 | type NapiGoInstanceCallbackData struct { 52 | CallbackMap NapiGoInstanceCallbackMap 53 | NextID NapiGoCallbackID 54 | Lock sync.RWMutex 55 | } 56 | 57 | type NapiGoCallbackID int 58 | 59 | type NapiGoInstanceCallbackMap map[NapiGoCallbackID]*NapiGoCallbackMapEntry 60 | 61 | type NapiGoCallbackMapEntry struct { 62 | Callback Callback 63 | ID NapiGoCallbackID 64 | } 65 | 66 | type NapiGoAsyncWorkID int 67 | 68 | type NapiGoInstanceAsyncWorkData struct { 69 | AsyncWorkMap NapiGoInstanceAsyncWorkMap 70 | NextID NapiGoAsyncWorkID 71 | Lock sync.RWMutex 72 | } 73 | 74 | type NapiGoInstanceAsyncWorkMap map[NapiGoAsyncWorkID]*NapiGoAsyncWorkMapEntry 75 | 76 | type NapiGoAsyncWorkMapEntry struct { 77 | Execute AsyncExecuteCallback 78 | Complete AsyncCompleteCallback 79 | ID NapiGoAsyncWorkID 80 | } 81 | 82 | type InstanceDataProvider interface { 83 | GetUserData() any 84 | SetUserData(userData any) 85 | 86 | GetCallbackData() CallbackDataProvider 87 | GetAsyncWorkData() AsyncWorkDataProvider 88 | } 89 | 90 | type CallbackDataProvider interface { 91 | CreateCallback(env Env, name string, cb Callback) (Value, Status) 92 | GetCallback(id NapiGoCallbackID) *NapiGoCallbackMapEntry 93 | DeleteCallback(id NapiGoCallbackID) 94 | } 95 | 96 | type AsyncWorkDataProvider interface { 97 | CreateAsyncWork( 98 | env Env, 99 | asyncResource, asyncResourceName Value, 100 | execute AsyncExecuteCallback, 101 | complete AsyncCompleteCallback, 102 | ) (AsyncWork, Status) 103 | GetAsyncWork(id NapiGoAsyncWorkID) *NapiGoAsyncWorkMapEntry 104 | DeleteAsyncWork(id NapiGoAsyncWorkID) 105 | } 106 | 107 | var _ InstanceDataProvider = &NapiGoInstanceData{} 108 | var _ CallbackDataProvider = &NapiGoInstanceCallbackData{} 109 | var _ AsyncWorkDataProvider = &NapiGoInstanceAsyncWorkData{} 110 | 111 | const ( 112 | maxStackTraceSize = 8192 113 | ) 114 | 115 | func InitializeInstanceData(env Env) Status { 116 | return setInstanceData(env, &NapiGoInstanceData{}) 117 | } 118 | 119 | //export DeleteInstanceData 120 | func DeleteInstanceData( 121 | env C.napi_env, 122 | finalizeData, finalizeHint unsafe.Pointer, 123 | ) { 124 | instanceDataHandle := cgo.Handle(finalizeData) 125 | instanceDataHandle.Delete() 126 | } 127 | 128 | //export DeleteCallbackData 129 | func DeleteCallbackData( 130 | cEnv C.napi_env, 131 | finalizeData, finalizeHint unsafe.Pointer, 132 | ) { 133 | env := Env(cEnv) 134 | defer func() { 135 | err := recover() 136 | if err != nil { 137 | fmt.Printf("napi.DeleteCallbackData: Recovered from panic: %s\n", err) 138 | reportStackTrace() 139 | 140 | msg := "unknown error" 141 | if err, ok := err.(error); ok { 142 | msg = err.Error() 143 | } 144 | ThrowError(env, "", msg) 145 | } 146 | }() 147 | 148 | instanceData, status := getInstanceData(env) 149 | if status != StatusOK { 150 | panic(StatusError(status)) 151 | } 152 | 153 | id := *(*NapiGoCallbackID)(finalizeData) 154 | instanceData.GetCallbackData().DeleteCallback(id) 155 | } 156 | 157 | //export ExecuteCallback 158 | func ExecuteCallback( 159 | cEnv C.napi_env, 160 | cInfo C.napi_callback_info, 161 | ) C.napi_value { 162 | env := Env(cEnv) 163 | defer func() { 164 | err := recover() 165 | if err != nil { 166 | fmt.Printf("napi.ExecuteCallback: Recovered from panic: %s\n", err) 167 | reportStackTrace() 168 | 169 | msg := "unknown error" 170 | if err, ok := err.(error); ok { 171 | msg = err.Error() 172 | } 173 | ThrowError(env, "", msg) 174 | } 175 | }() 176 | 177 | instanceData, status := getInstanceData(env) 178 | if status != StatusOK { 179 | panic(StatusError(status)) 180 | } 181 | 182 | argc := C.size_t(0) 183 | var cData unsafe.Pointer 184 | status = Status(C.napi_get_cb_info( 185 | cEnv, 186 | cInfo, 187 | &argc, 188 | nil, 189 | nil, 190 | &cData, 191 | )) 192 | 193 | if status != StatusOK { 194 | panic(StatusError(status)) 195 | } 196 | 197 | id := *(*NapiGoCallbackID)(cData) 198 | callbackData := instanceData.GetCallbackData().GetCallback(id) 199 | 200 | info := CallbackInfo(cInfo) 201 | result := callbackData.Callback(env, info) 202 | return C.napi_value(result) 203 | } 204 | 205 | //export ExecuteAsyncExecuteCallback 206 | func ExecuteAsyncExecuteCallback(cEnv C.napi_env, cData unsafe.Pointer) { 207 | env := Env(cEnv) 208 | defer func() { 209 | err := recover() 210 | if err != nil { 211 | fmt.Printf( 212 | "napi.ExecuteAsyncExecuteCallback: Recovered from panic: %s\n", 213 | err, 214 | ) 215 | reportStackTrace() 216 | 217 | msg := "unknown error" 218 | if err, ok := err.(error); ok { 219 | msg = err.Error() 220 | } 221 | ThrowError(env, "", msg) 222 | } 223 | }() 224 | 225 | instanceData, status := getInstanceData(env) 226 | if status != StatusOK { 227 | panic(StatusError(status)) 228 | } 229 | 230 | id := *(*NapiGoAsyncWorkID)(cData) 231 | asyncWorkData := instanceData.GetAsyncWorkData().GetAsyncWork(id) 232 | asyncWorkData.Execute(env) 233 | } 234 | 235 | //export ExecuteAsyncCompleteCallback 236 | func ExecuteAsyncCompleteCallback( 237 | cEnv C.napi_env, 238 | cStatus C.napi_status, 239 | cData unsafe.Pointer, 240 | ) { 241 | env := Env(cEnv) 242 | defer func() { 243 | err := recover() 244 | if err != nil { 245 | fmt.Printf( 246 | "napi.ExecuteAsyncExecuteCallback: Recovered from panic: %s\n", 247 | err, 248 | ) 249 | reportStackTrace() 250 | 251 | msg := "unknown error" 252 | if err, ok := err.(error); ok { 253 | msg = err.Error() 254 | } 255 | ThrowError(env, "", msg) 256 | } 257 | }() 258 | 259 | instanceData, status := getInstanceData(env) 260 | if status != StatusOK { 261 | panic(StatusError(status)) 262 | } 263 | 264 | id := *(*NapiGoAsyncWorkID)(cData) 265 | asyncWorkData := instanceData.GetAsyncWorkData().GetAsyncWork(id) 266 | asyncWorkData.Complete(env, Status(cStatus)) 267 | } 268 | 269 | func getInstanceDataHandle(env Env) (cgo.Handle, Status) { 270 | var result unsafe.Pointer 271 | status := Status(C.napi_get_instance_data( 272 | C.napi_env(env), 273 | &result, 274 | )) 275 | 276 | if status != StatusOK || result == nil { 277 | return cgo.Handle(0), status 278 | } 279 | 280 | return cgo.Handle(result), status 281 | } 282 | 283 | func getInstanceData(env Env) (InstanceDataProvider, Status) { 284 | handle, status := getInstanceDataHandle(env) 285 | if status != StatusOK || handle == 0 { 286 | return nil, status 287 | } 288 | 289 | return handle.Value().(InstanceDataProvider), status 290 | } 291 | 292 | func setInstanceData(env Env, data *NapiGoInstanceData) Status { 293 | // check if an existing handle is already set, and clean it up if so 294 | // (napi won't invoke the finalizer if overwriting instance data) 295 | handle, status := getInstanceDataHandle(env) 296 | if status != StatusOK { 297 | return status 298 | } 299 | 300 | if handle != 0 { 301 | handle.Delete() 302 | } 303 | 304 | dataHandle := cgo.NewHandle(data) 305 | return Status(C.napi_set_instance_data( 306 | C.napi_env(env), 307 | unsafe.Pointer(dataHandle), 308 | C.napi_finalize(C.DeleteInstanceData), 309 | nil, 310 | )) 311 | } 312 | 313 | func reportStackTrace() { 314 | stackTraceBuf := make([]byte, maxStackTraceSize) 315 | stackTraceSz := runtime.Stack(stackTraceBuf, false) 316 | fmt.Printf("%s\n", string(stackTraceBuf[:stackTraceSz])) 317 | } 318 | 319 | func (d *NapiGoInstanceData) GetUserData() any { 320 | return d.UserData 321 | } 322 | 323 | func (d *NapiGoInstanceData) SetUserData(userData any) { 324 | d.UserData = userData 325 | } 326 | 327 | func (d *NapiGoInstanceData) GetCallbackData() CallbackDataProvider { 328 | return &d.CallbackData 329 | } 330 | 331 | func (d *NapiGoInstanceData) GetAsyncWorkData() AsyncWorkDataProvider { 332 | return &d.AsyncWorkData 333 | } 334 | 335 | func (d *NapiGoInstanceCallbackData) CreateCallback( 336 | env Env, 337 | name string, 338 | cb Callback, 339 | ) (Value, Status) { 340 | d.Lock.Lock() 341 | defer d.Lock.Unlock() 342 | 343 | cname := C.CString(name) 344 | defer C.free(unsafe.Pointer(cname)) 345 | 346 | callbackState := d.insert(cb) 347 | 348 | var result Value 349 | status := Status(C.napi_create_function( 350 | C.napi_env(env), 351 | cname, 352 | C.size_t(len([]byte(name))), 353 | C.napi_callback(C.ExecuteCallback), 354 | unsafe.Pointer(&callbackState.ID), 355 | (*C.napi_value)(unsafe.Pointer(&result)), 356 | )) 357 | 358 | if status == StatusOK { 359 | status = Status(C.napi_add_finalizer( 360 | C.napi_env(env), 361 | C.napi_value(result), 362 | unsafe.Pointer(&callbackState.ID), 363 | C.napi_finalize(C.DeleteCallbackData), 364 | nil, 365 | nil, 366 | )) 367 | } 368 | 369 | return result, status 370 | } 371 | 372 | func (d *NapiGoInstanceCallbackData) GetCallback( 373 | id NapiGoCallbackID, 374 | ) *NapiGoCallbackMapEntry { 375 | d.Lock.RLock() 376 | defer d.Lock.RUnlock() 377 | return d.CallbackMap[id] 378 | } 379 | 380 | func (d *NapiGoInstanceCallbackData) DeleteCallback(id NapiGoCallbackID) { 381 | d.Lock.Lock() 382 | defer d.Lock.Unlock() 383 | delete(d.CallbackMap, id) 384 | } 385 | 386 | func (d *NapiGoInstanceCallbackData) insert( 387 | cb Callback, 388 | ) *NapiGoCallbackMapEntry { 389 | // callers are expected to lock 390 | 391 | if d.CallbackMap == nil { 392 | d.CallbackMap = NapiGoInstanceCallbackMap{} 393 | } 394 | 395 | for { 396 | id := d.NextID 397 | d.NextID++ 398 | 399 | if d.CallbackMap[id] == nil { 400 | result := &NapiGoCallbackMapEntry{ 401 | Callback: cb, 402 | ID: id, 403 | } 404 | d.CallbackMap[id] = result 405 | return result 406 | } 407 | } 408 | } 409 | 410 | func (d *NapiGoInstanceAsyncWorkData) CreateAsyncWork( 411 | env Env, 412 | asyncResource, asyncResourceName Value, 413 | execute AsyncExecuteCallback, 414 | complete AsyncCompleteCallback, 415 | ) (AsyncWork, Status) { 416 | d.Lock.Lock() 417 | defer d.Lock.Unlock() 418 | 419 | asyncWorkState := d.insert(execute, complete) 420 | 421 | result := AsyncWork{ 422 | ID: asyncWorkState.ID, 423 | } 424 | status := Status(C.napi_create_async_work( 425 | C.napi_env(env), 426 | C.napi_value(asyncResource), 427 | C.napi_value(asyncResourceName), 428 | C.napi_async_execute_callback(C.ExecuteAsyncExecuteCallback), 429 | C.napi_async_complete_callback(C.ExecuteAsyncCompleteCallback), 430 | unsafe.Pointer(&asyncWorkState.ID), 431 | (*C.napi_async_work)(unsafe.Pointer(&result.Handle)), 432 | )) 433 | 434 | return result, status 435 | } 436 | 437 | func (d *NapiGoInstanceAsyncWorkData) GetAsyncWork( 438 | id NapiGoAsyncWorkID, 439 | ) *NapiGoAsyncWorkMapEntry { 440 | d.Lock.RLock() 441 | defer d.Lock.RUnlock() 442 | return d.AsyncWorkMap[id] 443 | } 444 | 445 | func (d *NapiGoInstanceAsyncWorkData) DeleteAsyncWork(id NapiGoAsyncWorkID) { 446 | d.Lock.Lock() 447 | defer d.Lock.Unlock() 448 | delete(d.AsyncWorkMap, id) 449 | } 450 | 451 | func (d *NapiGoInstanceAsyncWorkData) insert( 452 | execute AsyncExecuteCallback, 453 | complete AsyncCompleteCallback, 454 | ) *NapiGoAsyncWorkMapEntry { 455 | // callers are expected to lock 456 | 457 | if d.AsyncWorkMap == nil { 458 | d.AsyncWorkMap = NapiGoInstanceAsyncWorkMap{} 459 | } 460 | 461 | for { 462 | id := d.NextID 463 | d.NextID++ 464 | 465 | if d.AsyncWorkMap[id] == nil { 466 | result := &NapiGoAsyncWorkMapEntry{ 467 | Execute: execute, 468 | Complete: complete, 469 | ID: id, 470 | } 471 | d.AsyncWorkMap[id] = result 472 | return result 473 | } 474 | } 475 | } 476 | -------------------------------------------------------------------------------- /js/callback.go: -------------------------------------------------------------------------------- 1 | package js 2 | 3 | import ( 4 | "github.com/akshayganeshen/napi-go" 5 | ) 6 | 7 | type Callback = func(env Env, this Value, args []Value) any 8 | 9 | func AsCallback(fn Callback) napi.Callback { 10 | return func(env napi.Env, info napi.CallbackInfo) napi.Value { 11 | cbInfo, st := napi.GetCbInfo(env, info) 12 | if st != napi.StatusOK { 13 | panic(napi.StatusError(st)) 14 | } 15 | 16 | jsEnv := AsEnv(env) 17 | this := Value{ 18 | Env: jsEnv, 19 | Value: cbInfo.This, 20 | } 21 | args := make([]Value, len(cbInfo.Args)) 22 | for i, cbArg := range cbInfo.Args { 23 | args[i] = Value{ 24 | Env: jsEnv, 25 | Value: cbArg, 26 | } 27 | } 28 | 29 | result := fn(jsEnv, this, args) 30 | return jsEnv.ValueOf(result).Value 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /js/env.go: -------------------------------------------------------------------------------- 1 | package js 2 | 3 | import ( 4 | "fmt" 5 | "unsafe" 6 | 7 | "github.com/akshayganeshen/napi-go" 8 | ) 9 | 10 | type Env struct { 11 | Env napi.Env 12 | } 13 | 14 | type InvalidValueTypeError struct { 15 | Value any 16 | } 17 | 18 | var _ error = InvalidValueTypeError{} 19 | 20 | func AsEnv(env napi.Env) Env { 21 | return Env{ 22 | Env: env, 23 | } 24 | } 25 | 26 | func (e Env) Global() Value { 27 | v, st := napi.GetGlobal(e.Env) 28 | if st != napi.StatusOK { 29 | panic(napi.StatusError(st)) 30 | } 31 | return Value{ 32 | Env: e, 33 | Value: v, 34 | } 35 | } 36 | 37 | func (e Env) Null() Value { 38 | v, st := napi.GetNull(e.Env) 39 | if st != napi.StatusOK { 40 | panic(napi.StatusError(st)) 41 | } 42 | return Value{ 43 | Env: e, 44 | Value: v, 45 | } 46 | } 47 | 48 | func (e Env) Undefined() Value { 49 | v, st := napi.GetUndefined(e.Env) 50 | if st != napi.StatusOK { 51 | panic(napi.StatusError(st)) 52 | } 53 | return Value{ 54 | Env: e, 55 | Value: v, 56 | } 57 | } 58 | 59 | func (e Env) ValueOf(x any) Value { 60 | var ( 61 | v napi.Value 62 | st napi.Status 63 | ) 64 | 65 | switch xt := x.(type) { 66 | case Value: 67 | return xt 68 | case []Value: 69 | l := len(xt) 70 | v, st = napi.CreateArrayWithLength(e.Env, l) 71 | if st != napi.StatusOK { 72 | break 73 | } 74 | 75 | for i, xti := range xt { 76 | // TODO: Use Value.SetIndex helper 77 | st = napi.SetElement(e.Env, v, i, xti.Value) 78 | if st != napi.StatusOK { 79 | break 80 | } 81 | } 82 | case Func: 83 | return xt.Value 84 | case Callback: 85 | return e.FuncOf(xt).Value 86 | case *Promise: 87 | v, st = xt.Promise.Value, napi.StatusOK 88 | case napi.Value: 89 | v, st = xt, napi.StatusOK 90 | 91 | case nil: 92 | v, st = napi.GetNull(e.Env) 93 | case bool: 94 | v, st = napi.GetBoolean(e.Env, xt) 95 | case int: 96 | v, st = napi.CreateDouble(e.Env, float64(xt)) 97 | case int8: 98 | v, st = napi.CreateDouble(e.Env, float64(xt)) 99 | case int16: 100 | v, st = napi.CreateDouble(e.Env, float64(xt)) 101 | case int64: 102 | v, st = napi.CreateDouble(e.Env, float64(xt)) 103 | case uint: 104 | v, st = napi.CreateDouble(e.Env, float64(xt)) 105 | case uint8: 106 | v, st = napi.CreateDouble(e.Env, float64(xt)) 107 | case uint16: 108 | v, st = napi.CreateDouble(e.Env, float64(xt)) 109 | case uint64: 110 | v, st = napi.CreateDouble(e.Env, float64(xt)) 111 | case uintptr: 112 | v, st = napi.CreateDouble(e.Env, float64(xt)) 113 | case unsafe.Pointer: 114 | v, st = napi.CreateDouble(e.Env, float64(uintptr(xt))) 115 | case float32: 116 | v, st = napi.CreateDouble(e.Env, float64(xt)) 117 | case float64: 118 | v, st = napi.CreateDouble(e.Env, xt) 119 | case string: 120 | v, st = napi.CreateStringUtf8(e.Env, xt) 121 | case error: 122 | msg := e.ValueOf(xt.Error()) 123 | v, st = napi.CreateError(e.Env, nil, msg.Value) 124 | case []any: 125 | l := len(xt) 126 | v, st = napi.CreateArrayWithLength(e.Env, l) 127 | if st != napi.StatusOK { 128 | break 129 | } 130 | 131 | for i, xti := range xt { 132 | // TODO: Use Value.SetIndex helper 133 | vti := e.ValueOf(xti) 134 | st = napi.SetElement(e.Env, v, i, vti.Value) 135 | if st != napi.StatusOK { 136 | break 137 | } 138 | } 139 | case map[string]any: 140 | v, st = napi.CreateObject(e.Env) 141 | if st != napi.StatusOK { 142 | break 143 | } 144 | 145 | for xtk, xtv := range xt { 146 | // TODO: Use Value.Set helper 147 | vtk, vtv := e.ValueOf(xtk), e.ValueOf(xtv) 148 | st = napi.SetProperty(e.Env, v, vtk.Value, vtv.Value) 149 | if st != napi.StatusOK { 150 | break 151 | } 152 | } 153 | 154 | default: 155 | panic(InvalidValueTypeError{x}) 156 | } 157 | 158 | if st != napi.StatusOK { 159 | panic(napi.StatusError(st)) 160 | } 161 | 162 | return Value{ 163 | Env: e, 164 | Value: v, 165 | } 166 | } 167 | 168 | func (e Env) FuncOf(fn Callback) Func { 169 | // TODO: Add CreateReference to FuncOf to keep value alive 170 | v, st := napi.CreateFunction( 171 | e.Env, 172 | "", 173 | AsCallback(fn), 174 | ) 175 | 176 | if st != napi.StatusOK { 177 | panic(napi.StatusError(st)) 178 | } 179 | 180 | return Func{ 181 | Value: Value{ 182 | Env: e, 183 | Value: v, 184 | }, 185 | } 186 | } 187 | 188 | func (e Env) NewPromise() *Promise { 189 | var result Promise 190 | result.reset(e) 191 | return &result 192 | } 193 | 194 | func (err InvalidValueTypeError) Error() string { 195 | return fmt.Sprintf("Value cannot be represented in JS: %T", err.Value) 196 | } 197 | -------------------------------------------------------------------------------- /js/func.go: -------------------------------------------------------------------------------- 1 | package js 2 | 3 | type Func struct { 4 | Value 5 | } 6 | -------------------------------------------------------------------------------- /js/promise.go: -------------------------------------------------------------------------------- 1 | package js 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/akshayganeshen/napi-go" 7 | ) 8 | 9 | type Promise struct { 10 | Promise napi.Promise 11 | ThreadsafeFunction napi.ThreadsafeFunction 12 | Result any 13 | ResultType PromiseResultType 14 | } 15 | 16 | type PromiseResultType string 17 | 18 | type PromiseProvider interface { 19 | Resolve(resolution any) 20 | Reject(rejection any) 21 | } 22 | 23 | var _ PromiseProvider = &Promise{} 24 | 25 | const ( 26 | PromiseResultTypeResolved PromiseResultType = "resolved" 27 | PromiseResultTypeRejected PromiseResultType = "rejected" 28 | ) 29 | 30 | var ErrPromiseSettled = errors.New( 31 | "Promise: Cannot resolve/reject a settled promise", 32 | ) 33 | 34 | func (p *Promise) Resolve(resolution any) { 35 | p.ensurePending() 36 | 37 | p.Result = resolution 38 | p.ResultType = PromiseResultTypeResolved 39 | 40 | // function has already been acquired during reset 41 | defer p.release() 42 | p.settle() 43 | } 44 | 45 | func (p *Promise) Reject(rejection any) { 46 | p.ensurePending() 47 | 48 | p.Result = rejection 49 | p.ResultType = PromiseResultTypeRejected 50 | 51 | // function has already been acquired during reset 52 | defer p.release() 53 | p.settle() 54 | } 55 | 56 | func (p *Promise) reset(e Env) { 57 | np, st := napi.CreatePromise(e.Env) 58 | if st != napi.StatusOK { 59 | panic(napi.StatusError(st)) 60 | } 61 | 62 | asyncResourceName := e.ValueOf("napi-go/js-promise") 63 | fn := e.FuncOf(func(env Env, this Value, args []Value) any { 64 | value := env.ValueOf(p.Result) 65 | 66 | st := napi.StatusOK 67 | switch p.ResultType { 68 | case PromiseResultTypeResolved: 69 | st = napi.ResolveDeferred(env.Env, p.Promise.Deferred, value.Value) 70 | case PromiseResultTypeRejected: 71 | st = napi.RejectDeferred(env.Env, p.Promise.Deferred, value.Value) 72 | } 73 | 74 | if st != napi.StatusOK { 75 | panic(napi.StatusError(st)) 76 | } 77 | 78 | return nil 79 | }) 80 | 81 | tsFn, st := napi.CreateThreadsafeFunction( 82 | e.Env, 83 | fn.Value.Value, 84 | nil, asyncResourceName.Value, 85 | 0, 86 | 1, // initialize with 1 acquisition 87 | ) 88 | if st != napi.StatusOK { 89 | panic(napi.StatusError(st)) 90 | } 91 | 92 | *p = Promise{ 93 | Promise: np, 94 | ThreadsafeFunction: tsFn, 95 | } 96 | } 97 | 98 | func (p *Promise) ensurePending() { 99 | if p.ResultType != "" { 100 | panic(ErrPromiseSettled) 101 | } 102 | } 103 | 104 | func (p *Promise) settle() { 105 | st := napi.CallThreadsafeFunction(p.ThreadsafeFunction) 106 | if st != napi.StatusOK { 107 | panic(napi.StatusError(st)) 108 | } 109 | } 110 | 111 | func (p *Promise) release() { 112 | st := napi.ReleaseThreadsafeFunction(p.ThreadsafeFunction) 113 | if st == napi.StatusClosing { 114 | p.ThreadsafeFunction = nil 115 | } else if st != napi.StatusOK { 116 | panic(napi.StatusError(st)) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /js/value.go: -------------------------------------------------------------------------------- 1 | package js 2 | 3 | import ( 4 | "github.com/akshayganeshen/napi-go" 5 | ) 6 | 7 | type Value struct { 8 | Env Env 9 | Value napi.Value 10 | } 11 | 12 | func (v Value) GetEnv() Env { 13 | return v.Env 14 | } 15 | -------------------------------------------------------------------------------- /js_native_api.go: -------------------------------------------------------------------------------- 1 | package napi 2 | 3 | /* 4 | #include 5 | #include 6 | */ 7 | import "C" 8 | 9 | import ( 10 | "unsafe" 11 | ) 12 | 13 | func GetUndefined(env Env) (Value, Status) { 14 | var result Value 15 | status := Status(C.napi_get_undefined( 16 | C.napi_env(env), 17 | (*C.napi_value)(unsafe.Pointer(&result)), 18 | )) 19 | return result, status 20 | } 21 | 22 | func GetNull(env Env) (Value, Status) { 23 | var result Value 24 | status := Status(C.napi_get_null( 25 | C.napi_env(env), 26 | (*C.napi_value)(unsafe.Pointer(&result)), 27 | )) 28 | return result, status 29 | } 30 | 31 | func GetGlobal(env Env) (Value, Status) { 32 | var result Value 33 | status := Status(C.napi_get_global( 34 | C.napi_env(env), 35 | (*C.napi_value)(unsafe.Pointer(&result)), 36 | )) 37 | return result, status 38 | } 39 | 40 | func GetBoolean(env Env, value bool) (Value, Status) { 41 | var result Value 42 | status := Status(C.napi_get_boolean( 43 | C.napi_env(env), 44 | C.bool(value), 45 | (*C.napi_value)(unsafe.Pointer(&result)), 46 | )) 47 | return result, status 48 | } 49 | 50 | func CreateObject(env Env) (Value, Status) { 51 | var result Value 52 | status := Status(C.napi_create_object( 53 | C.napi_env(env), 54 | (*C.napi_value)(unsafe.Pointer(&result)), 55 | )) 56 | return result, status 57 | } 58 | 59 | func CreateArray(env Env) (Value, Status) { 60 | var result Value 61 | status := Status(C.napi_create_array( 62 | C.napi_env(env), 63 | (*C.napi_value)(unsafe.Pointer(&result)), 64 | )) 65 | return result, status 66 | } 67 | 68 | func CreateArrayWithLength(env Env, length int) (Value, Status) { 69 | var result Value 70 | status := Status(C.napi_create_array_with_length( 71 | C.napi_env(env), 72 | C.size_t(length), 73 | (*C.napi_value)(unsafe.Pointer(&result)), 74 | )) 75 | return result, status 76 | } 77 | 78 | func CreateDouble(env Env, value float64) (Value, Status) { 79 | var result Value 80 | status := Status(C.napi_create_double( 81 | C.napi_env(env), 82 | C.double(value), 83 | (*C.napi_value)(unsafe.Pointer(&result)), 84 | )) 85 | return result, status 86 | } 87 | 88 | func CreateStringUtf8(env Env, str string) (Value, Status) { 89 | cstr := C.CString(str) 90 | defer C.free(unsafe.Pointer(cstr)) 91 | 92 | var result Value 93 | status := Status(C.napi_create_string_utf8( 94 | C.napi_env(env), 95 | cstr, 96 | C.size_t(len([]byte(str))), // must pass number of bytes 97 | (*C.napi_value)(unsafe.Pointer(&result)), 98 | )) 99 | return result, status 100 | } 101 | 102 | func CreateSymbol(env Env, description Value) (Value, Status) { 103 | var result Value 104 | status := Status(C.napi_create_symbol( 105 | C.napi_env(env), 106 | C.napi_value(description), 107 | (*C.napi_value)(unsafe.Pointer(&result)), 108 | )) 109 | return result, status 110 | } 111 | 112 | func CreateFunction(env Env, name string, cb Callback) (Value, Status) { 113 | provider, status := getInstanceData(env) 114 | if status != StatusOK || provider == nil { 115 | return nil, status 116 | } 117 | 118 | return provider.GetCallbackData().CreateCallback(env, name, cb) 119 | } 120 | 121 | func CreateError(env Env, code, msg Value) (Value, Status) { 122 | var result Value 123 | status := Status(C.napi_create_error( 124 | C.napi_env(env), 125 | C.napi_value(code), 126 | C.napi_value(msg), 127 | (*C.napi_value)(unsafe.Pointer(&result)), 128 | )) 129 | return result, status 130 | } 131 | 132 | func Typeof(env Env, value Value) (ValueType, Status) { 133 | var result ValueType 134 | status := Status(C.napi_typeof( 135 | C.napi_env(env), 136 | C.napi_value(value), 137 | (*C.napi_valuetype)(unsafe.Pointer(&result)), 138 | )) 139 | return result, status 140 | } 141 | 142 | func GetValueDouble(env Env, value Value) (float64, Status) { 143 | var result float64 144 | status := Status(C.napi_get_value_double( 145 | C.napi_env(env), 146 | C.napi_value(value), 147 | (*C.double)(unsafe.Pointer(&result)), 148 | )) 149 | return result, status 150 | } 151 | 152 | func GetValueBool(env Env, value Value) (bool, Status) { 153 | var result bool 154 | status := Status(C.napi_get_value_bool( 155 | C.napi_env(env), 156 | C.napi_value(value), 157 | (*C.bool)(unsafe.Pointer(&result)), 158 | )) 159 | return result, status 160 | } 161 | 162 | func GetValueStringUtf8(env Env, value Value) (string, Status) { 163 | // call napi_get_value_string_utf8 twice 164 | // first is to get number of bytes 165 | // second is to populate the actual string buffer 166 | bufsize := C.size_t(0) 167 | var strsize C.size_t 168 | 169 | status := Status(C.napi_get_value_string_utf8( 170 | C.napi_env(env), 171 | C.napi_value(value), 172 | nil, 173 | bufsize, 174 | &strsize, 175 | )) 176 | 177 | if status != StatusOK { 178 | return "", status 179 | } 180 | 181 | // ensure there is room for the null terminator as well 182 | strsize++ 183 | cstr := (*C.char)(C.malloc(C.sizeof_char * strsize)) 184 | defer C.free(unsafe.Pointer(cstr)) 185 | 186 | status = Status(C.napi_get_value_string_utf8( 187 | C.napi_env(env), 188 | C.napi_value(value), 189 | cstr, 190 | strsize, 191 | &strsize, 192 | )) 193 | 194 | if status != StatusOK { 195 | return "", status 196 | } 197 | 198 | return C.GoStringN( 199 | (*C.char)(cstr), 200 | (C.int)(strsize), 201 | ), status 202 | } 203 | 204 | func SetProperty(env Env, object, key, value Value) Status { 205 | return Status(C.napi_set_property( 206 | C.napi_env(env), 207 | C.napi_value(object), 208 | C.napi_value(key), 209 | C.napi_value(value), 210 | )) 211 | } 212 | 213 | func SetElement(env Env, object Value, index int, value Value) Status { 214 | return Status(C.napi_set_element( 215 | C.napi_env(env), 216 | C.napi_value(object), 217 | C.uint32_t(index), 218 | C.napi_value(value), 219 | )) 220 | } 221 | 222 | func StrictEquals(env Env, lhs, rhs Value) (bool, Status) { 223 | var result bool 224 | status := Status(C.napi_strict_equals( 225 | C.napi_env(env), 226 | C.napi_value(lhs), 227 | C.napi_value(rhs), 228 | (*C.bool)(&result), 229 | )) 230 | return result, status 231 | } 232 | 233 | type GetCbInfoResult struct { 234 | Args []Value 235 | This Value 236 | } 237 | 238 | func GetCbInfo(env Env, info CallbackInfo) (GetCbInfoResult, Status) { 239 | // call napi_get_cb_info twice 240 | // first is to get total number of arguments 241 | // second is to populate the actual arguments 242 | argc := C.size_t(0) 243 | status := Status(C.napi_get_cb_info( 244 | C.napi_env(env), 245 | C.napi_callback_info(info), 246 | &argc, 247 | nil, 248 | nil, 249 | nil, 250 | )) 251 | 252 | if status != StatusOK { 253 | return GetCbInfoResult{}, status 254 | } 255 | 256 | argv := make([]Value, int(argc)) 257 | var cArgv unsafe.Pointer 258 | if argc > 0 { 259 | cArgv = unsafe.Pointer(&argv[0]) // must pass element pointer 260 | } 261 | 262 | var thisArg Value 263 | 264 | status = Status(C.napi_get_cb_info( 265 | C.napi_env(env), 266 | C.napi_callback_info(info), 267 | &argc, 268 | (*C.napi_value)(cArgv), 269 | (*C.napi_value)(unsafe.Pointer(&thisArg)), 270 | nil, 271 | )) 272 | 273 | return GetCbInfoResult{ 274 | Args: argv, 275 | This: thisArg, 276 | }, status 277 | } 278 | 279 | func Throw(env Env, err Value) Status { 280 | return Status(C.napi_throw( 281 | C.napi_env(env), 282 | C.napi_value(err), 283 | )) 284 | } 285 | 286 | func ThrowError(env Env, code, msg string) Status { 287 | codeCStr, msgCCstr := C.CString(code), C.CString(msg) 288 | defer C.free(unsafe.Pointer(codeCStr)) 289 | defer C.free(unsafe.Pointer(msgCCstr)) 290 | 291 | return Status(C.napi_throw_error( 292 | C.napi_env(env), 293 | codeCStr, 294 | msgCCstr, 295 | )) 296 | } 297 | 298 | func CreatePromise(env Env) (Promise, Status) { 299 | var result Promise 300 | status := Status(C.napi_create_promise( 301 | C.napi_env(env), 302 | (*C.napi_deferred)(unsafe.Pointer(&result.Deferred)), 303 | (*C.napi_value)(unsafe.Pointer(&result.Value)), 304 | )) 305 | return result, status 306 | } 307 | 308 | func ResolveDeferred(env Env, deferred Deferred, resolution Value) Status { 309 | return Status(C.napi_resolve_deferred( 310 | C.napi_env(env), 311 | C.napi_deferred(deferred), 312 | C.napi_value(resolution), 313 | )) 314 | } 315 | 316 | func RejectDeferred(env Env, deferred Deferred, rejection Value) Status { 317 | return Status(C.napi_reject_deferred( 318 | C.napi_env(env), 319 | C.napi_deferred(deferred), 320 | C.napi_value(rejection), 321 | )) 322 | } 323 | 324 | func SetInstanceData(env Env, data any) Status { 325 | provider, status := getInstanceData(env) 326 | if status != StatusOK || provider == nil { 327 | return status 328 | } 329 | 330 | provider.SetUserData(data) 331 | return status 332 | } 333 | 334 | func GetInstanceData(env Env) (any, Status) { 335 | provider, status := getInstanceData(env) 336 | if status != StatusOK || provider == nil { 337 | return nil, status 338 | } 339 | 340 | return provider.GetUserData(), status 341 | } 342 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | all: doc 2 | clean: clean-doc 3 | 4 | EXAMPLE_DIR = docs/examples 5 | EXAMPLE_PACKAGES = \ 6 | async-promise \ 7 | callback \ 8 | describe-args \ 9 | hello-world \ 10 | js 11 | 12 | NAPI_LIB_SUFFIX = .node 13 | 14 | TARGET_BUILDDIR = build 15 | 16 | EXAMPLE_BINDINGS = $(addsuffix $(NAPI_LIB_SUFFIX),$(EXAMPLE_PACKAGES)) 17 | TARGET_EXAMPLES = \ 18 | $(addprefix $(TARGET_BUILDDIR)/, $(EXAMPLE_BINDINGS)) 19 | 20 | # TODO: Configure CGO_LDFLAGS_ALLOW for non-darwin systems. 21 | CGO_LDFLAGS_ALLOW = (-Wl,(-undefined,dynamic_lookup|-no_pie|-search_paths_first)) 22 | 23 | doc: $(TARGET_EXAMPLES) 24 | 25 | $(TARGET_EXAMPLES): | $(TARGET_BUILDDIR) 26 | $(TARGET_EXAMPLES): $(TARGET_BUILDDIR)/%$(NAPI_LIB_SUFFIX): $(EXAMPLE_DIR)/% 27 | CGO_LDFLAGS_ALLOW='$(CGO_LDFLAGS_ALLOW)' \ 28 | go build -buildmode=c-shared -o "$(@)" "./$(<)/" 29 | 30 | $(TARGET_BUILDDIR): 31 | mkdir -p "$(TARGET_BUILDDIR)" 32 | 33 | clean: 34 | rmdir "$(TARGET_BUILDDIR)" 35 | 36 | clean-doc: 37 | rm -f $(patsubst %,"%",$(TARGET_EXAMPLES)) 38 | 39 | .PHONY: all doc 40 | .PHONY: clean clean-doc 41 | -------------------------------------------------------------------------------- /node_api.go: -------------------------------------------------------------------------------- 1 | package napi 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "unsafe" 10 | ) 11 | 12 | func CreateAsyncWork( 13 | env Env, 14 | asyncResource, asyncResourceName Value, 15 | execute AsyncExecuteCallback, 16 | complete AsyncCompleteCallback, 17 | ) (AsyncWork, Status) { 18 | provider, status := getInstanceData(env) 19 | if status != StatusOK || provider == nil { 20 | return AsyncWork{}, status 21 | } 22 | 23 | return provider.GetAsyncWorkData().CreateAsyncWork( 24 | env, 25 | asyncResource, asyncResourceName, 26 | execute, 27 | complete, 28 | ) 29 | } 30 | 31 | func DeleteAsyncWork(env Env, work AsyncWork) Status { 32 | provider, status := getInstanceData(env) 33 | if status != StatusOK || provider == nil { 34 | return status 35 | } 36 | 37 | defer provider.GetAsyncWorkData().DeleteAsyncWork(work.ID) 38 | return Status(C.napi_delete_async_work( 39 | C.napi_env(env), 40 | C.napi_async_work(work.Handle), 41 | )) 42 | } 43 | 44 | func QueueAsyncWork(env Env, work AsyncWork) Status { 45 | return Status(C.napi_queue_async_work( 46 | C.napi_env(env), 47 | C.napi_async_work(work.Handle), 48 | )) 49 | } 50 | 51 | func CancelAsyncWork(env Env, work AsyncWork) Status { 52 | return Status(C.napi_cancel_async_work( 53 | C.napi_env(env), 54 | C.napi_async_work(work.Handle), 55 | )) 56 | } 57 | 58 | func GetNodeVersion(env Env) (NodeVersion, Status) { 59 | var cresult *C.napi_node_version 60 | status := Status(C.napi_get_node_version( 61 | C.napi_env(env), 62 | (**C.napi_node_version)(&cresult), 63 | )) 64 | 65 | if status != StatusOK { 66 | return NodeVersion{}, status 67 | } 68 | 69 | return NodeVersion{ 70 | Major: uint(cresult.major), 71 | Minor: uint(cresult.minor), 72 | Patch: uint(cresult.patch), 73 | Release: C.GoString(cresult.release), 74 | }, status 75 | } 76 | 77 | func GetModuleFileName(env Env) (string, Status) { 78 | var cresult *C.char 79 | status := Status(C.node_api_get_module_file_name( 80 | C.napi_env(env), 81 | (**C.char)(&cresult), 82 | )) 83 | 84 | if status != StatusOK { 85 | return "", status 86 | } 87 | 88 | return C.GoString(cresult), status 89 | } 90 | 91 | func CreateThreadsafeFunction( 92 | env Env, 93 | fn Value, 94 | asyncResource, asyncResourceName Value, 95 | maxQueueSize, initialThreadCount int, 96 | ) (ThreadsafeFunction, Status) { 97 | var result ThreadsafeFunction 98 | status := Status(C.napi_create_threadsafe_function( 99 | C.napi_env(env), 100 | C.napi_value(fn), 101 | C.napi_value(asyncResource), 102 | C.napi_value(asyncResourceName), 103 | C.size_t(maxQueueSize), 104 | C.size_t(initialThreadCount), 105 | nil, 106 | nil, 107 | nil, 108 | nil, 109 | (*C.napi_threadsafe_function)(unsafe.Pointer(&result)), 110 | )) 111 | return result, status 112 | } 113 | 114 | func CallThreadsafeFunction( 115 | fn ThreadsafeFunction, 116 | ) Status { 117 | return Status(C.napi_call_threadsafe_function( 118 | C.napi_threadsafe_function(fn), 119 | nil, 120 | C.napi_tsfn_blocking, 121 | )) 122 | } 123 | 124 | func AcquireThreadsafeFunction(fn ThreadsafeFunction) Status { 125 | return Status(C.napi_acquire_threadsafe_function( 126 | C.napi_threadsafe_function(fn), 127 | )) 128 | } 129 | 130 | func ReleaseThreadsafeFunction( 131 | fn ThreadsafeFunction, 132 | ) Status { 133 | return Status(C.napi_release_threadsafe_function( 134 | C.napi_threadsafe_function(fn), 135 | C.napi_tsfn_release, 136 | )) 137 | } 138 | -------------------------------------------------------------------------------- /node_api_cgo_flags.go: -------------------------------------------------------------------------------- 1 | package napi 2 | 3 | /* 4 | #cgo CFLAGS: -DDEBUG 5 | #cgo CFLAGS: -D_DEBUG 6 | #cgo CFLAGS: -DV8_ENABLE_CHECKS 7 | #cgo CFLAGS: -DNAPI_EXPERIMENTAL 8 | #cgo CFLAGS: -I/usr/local/include/node 9 | #cgo CXXFLAGS: -std=c++11 10 | 11 | #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup 12 | #cgo darwin LDFLAGS: -Wl,-no_pie 13 | #cgo darwin LDFLAGS: -Wl,-search_paths_first 14 | #cgo darwin LDFLAGS: -arch x86_64 15 | 16 | #cgo linux LDFLAGS: -Wl,-unresolved-symbols=ignore-all 17 | 18 | #cgo LDFLAGS: -L${SRCDIR} 19 | #cgo LDFLAGS: -stdlib=libc++ 20 | */ 21 | import "C" 22 | -------------------------------------------------------------------------------- /node_version.go: -------------------------------------------------------------------------------- 1 | package napi 2 | 3 | type NodeVersion struct { 4 | Major uint 5 | Minor uint 6 | Patch uint 7 | Release string 8 | } 9 | -------------------------------------------------------------------------------- /promise.go: -------------------------------------------------------------------------------- 1 | package napi 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | type Deferred unsafe.Pointer 8 | 9 | type Promise struct { 10 | Deferred Deferred 11 | Value Value 12 | } 13 | -------------------------------------------------------------------------------- /status.go: -------------------------------------------------------------------------------- 1 | package napi 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "fmt" 10 | ) 11 | 12 | type Status int 13 | 14 | type StatusError Status 15 | 16 | var _ error = StatusError(0) 17 | 18 | const ( 19 | StatusOK Status = C.napi_ok 20 | StatusInvalidArg Status = C.napi_invalid_arg 21 | StatusObjectExpected Status = C.napi_object_expected 22 | StatusStringExpected Status = C.napi_string_expected 23 | StatusNameExpected Status = C.napi_name_expected 24 | StatusFunctionExpected Status = C.napi_function_expected 25 | StatusNumberExpected Status = C.napi_number_expected 26 | StatusBooleanExpected Status = C.napi_boolean_expected 27 | StatusArrayExpected Status = C.napi_array_expected 28 | StatusGenericFailure Status = C.napi_generic_failure 29 | StatusPendingException Status = C.napi_pending_exception 30 | StatusCancelled Status = C.napi_cancelled 31 | StatusEscapeCalledTwice Status = C.napi_escape_called_twice 32 | StatusHandleScopeMismatch Status = C.napi_handle_scope_mismatch 33 | StatusCallbackScopeMismatch Status = C.napi_callback_scope_mismatch 34 | StatusQueueFull Status = C.napi_queue_full 35 | StatusClosing Status = C.napi_closing 36 | StatusBigintExpected Status = C.napi_bigint_expected 37 | StatusDateExpected Status = C.napi_date_expected 38 | StatusArraybufferExpected Status = C.napi_arraybuffer_expected 39 | StatusDetachableArraybufferExpected Status = C.napi_detachable_arraybuffer_expected 40 | StatusWouldDeadlock Status = C.napi_would_deadlock 41 | ) 42 | 43 | func (s Status) String() string { 44 | switch s { 45 | case StatusOK: 46 | return "napi_ok" 47 | case StatusInvalidArg: 48 | return "napi_invalid_arg" 49 | case StatusObjectExpected: 50 | return "napi_object_expected" 51 | case StatusStringExpected: 52 | return "napi_string_expected" 53 | case StatusNameExpected: 54 | return "napi_name_expected" 55 | case StatusFunctionExpected: 56 | return "napi_function_expected" 57 | case StatusNumberExpected: 58 | return "napi_number_expected" 59 | case StatusBooleanExpected: 60 | return "napi_boolean_expected" 61 | case StatusArrayExpected: 62 | return "napi_array_expected" 63 | case StatusGenericFailure: 64 | return "napi_generic_failure" 65 | case StatusPendingException: 66 | return "napi_pending_exception" 67 | case StatusCancelled: 68 | return "napi_cancelled" 69 | case StatusEscapeCalledTwice: 70 | return "napi_escape_called_twice" 71 | case StatusHandleScopeMismatch: 72 | return "napi_handle_scope_mismatch" 73 | case StatusCallbackScopeMismatch: 74 | return "napi_callback_scope_mismatch" 75 | case StatusQueueFull: 76 | return "napi_queue_full" 77 | case StatusClosing: 78 | return "napi_closing" 79 | case StatusBigintExpected: 80 | return "napi_bigint_expected" 81 | case StatusDateExpected: 82 | return "napi_date_expected" 83 | case StatusArraybufferExpected: 84 | return "napi_arraybuffer_expected" 85 | case StatusDetachableArraybufferExpected: 86 | return "napi_detachable_arraybuffer_expected" 87 | case StatusWouldDeadlock: 88 | return "napi_would_deadlock" 89 | } 90 | 91 | return "napi_go_status_unknown" 92 | } 93 | 94 | func (err StatusError) Error() string { 95 | return fmt.Sprintf("napi_status error: %s", Status(err)) 96 | } 97 | -------------------------------------------------------------------------------- /threadsafe_function.go: -------------------------------------------------------------------------------- 1 | package napi 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | type ThreadsafeFunction unsafe.Pointer 8 | -------------------------------------------------------------------------------- /value.go: -------------------------------------------------------------------------------- 1 | package napi 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | type Value unsafe.Pointer 8 | -------------------------------------------------------------------------------- /value_type.go: -------------------------------------------------------------------------------- 1 | package napi 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | type ValueType int 9 | 10 | const ( 11 | ValueTypeUndefined ValueType = C.napi_undefined 12 | ValueTypeNull ValueType = C.napi_null 13 | ValueTypeBoolean ValueType = C.napi_boolean 14 | ValueTypeNumber ValueType = C.napi_number 15 | ValueTypeString ValueType = C.napi_string 16 | ValueTypeSymbol ValueType = C.napi_symbol 17 | ValueTypeObject ValueType = C.napi_object 18 | ValueTypeFunction ValueType = C.napi_function 19 | ValueTypeExternal ValueType = C.napi_external 20 | ValueTypeBigint ValueType = C.napi_bigint 21 | ) 22 | --------------------------------------------------------------------------------