Skip to content

Commit c6ed29b

Browse files
feat: create napi-go/js helper library
Create `js/` wrapper around `napi`. Add `js.Value`, `js.Callback`, `js.Func`, and `js.Promise` implementations. Add `js.AsCallback` to provide `napi.Callback` values, generally to `entry.Export`.
1 parent e2671b0 commit c6ed29b

File tree

5 files changed

+365
-0
lines changed

5 files changed

+365
-0
lines changed

js/callback.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
}

js/env.go

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
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+
}

js/func.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package js
2+
3+
type Func struct {
4+
Value
5+
}

js/promise.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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+
}

js/value.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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+
}

0 commit comments

Comments
 (0)