Skip to content

Commit cb82b52

Browse files
committed
feat: add NodeObject
1 parent c4f4329 commit cb82b52

6 files changed

Lines changed: 175 additions & 22 deletions

File tree

src/Convert.zig

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,17 @@ pub fn nodeFromNative(env: c.napi_env, value: anytype) !c.napi_value {
9797
}
9898
}
9999
},
100-
.many, .c => {
101-
@compileError("Cannot convert c-style pointer values, they are not supported.");
100+
.many => {
101+
std.log.debug("serializing {s} of type {s}", .{ value, @typeName(T) });
102+
if (p.child == u8) {
103+
try s2e(c.napi_create_string_utf8(env, value, std.mem.len(value), &res));
104+
} else {
105+
@compileError(std.fmt.comptimePrint("Cannot convert node value to '{s}', Cannot convert c-style pointer values, they are not supported.", .{@typeName(T)}));
106+
}
107+
},
108+
.c => {
109+
// @compileError("Cannot convert c-style pointer values, they are not supported.");
110+
@compileError(std.fmt.comptimePrint("Cannot convert node value to '{s}', Cannot convert c-style pointer values, they are not supported.", .{@typeName(T)}));
102111
},
103112
}
104113
},
@@ -158,7 +167,6 @@ pub fn nodeFromNative(env: c.napi_env, value: anytype) !c.napi_value {
158167
@compileError(std.fmt.comptimePrint("Cannot convert value of type '{s}', untagged unions are not supported.", .{@typeName(T)}));
159168
}
160169
},
161-
162170
else => @compileError(std.fmt.comptimePrint("Cannot convert value of type '{s}'", .{@typeName(T)})),
163171
}
164172

@@ -256,7 +264,7 @@ pub fn nativeFromNode(env: c.napi_env, comptime T: type, js_value: c.napi_value,
256264
}
257265
},
258266
.@"struct" => |s| {
259-
if (T == NodeValue) {
267+
if (T == NodeValue or T == NodeObject or T == NodeArray) {
260268
return T{ .napi_env = env, .napi_value = js_value };
261269
}
262270
if (@hasDecl(T, "__is_node_function")) {

src/node_values.zig

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ pub const NodeValue = struct {
6969
return v;
7070
}
7171

72+
/// Casts the value to an NodeObject. Fails is the typeof != .Object.
7273
pub fn asObject(self: NodeValue.Self) !NodeObject {
7374
if (self.typeof() == .Object) {
7475
return NodeObject{
@@ -80,6 +81,7 @@ pub const NodeValue = struct {
8081
return error.NodeValueIsNoObject;
8182
}
8283

84+
/// Casts the value to an NodeArray. Fails is the typeof != .Array.
8385
pub fn asArray(self: NodeValue.Self) !NodeArray {
8486
if (self.typeof() == .Object) {
8587
return NodeObject{
@@ -91,19 +93,6 @@ pub const NodeValue = struct {
9193
return error.NodeValueIsNoObject;
9294
}
9395

94-
pub fn asFunction(self: NodeValue.Self) !NodeFunction {
95-
if (try self.typeof() == .Function) {
96-
return NodeFunction{
97-
.napi_env = self.napi_env,
98-
.napi_value = self.napi_value,
99-
};
100-
}
101-
102-
return error.NodeValueIsNoFunction;
103-
}
104-
105-
// TODO: more as... methods
106-
10796
pub fn isArray(self: NodeValue.Self) !bool {
10897
return self.is(c.napi_is_array);
10998
}
@@ -136,9 +125,9 @@ pub const NodeValue = struct {
136125
return self.is(c.napi_is_promise);
137126
}
138127

139-
fn is(self: NodeValue.Self, f: anytype) !bool {
128+
fn is(self: NodeValue.Self, is_fn: anytype) !bool {
140129
var b: bool = undefined;
141-
s2e(f(self.napi_env, self.napi_value, &b));
130+
s2e(is_fn(self.napi_env, self.napi_value, &b));
142131
return b;
143132
}
144133
};
@@ -157,9 +146,50 @@ pub const NodeObject = struct {
157146
};
158147
}
159148

160-
// set prop
149+
/// Gets the value of a property.
150+
pub fn get(self: NodeObject.Self, property_name: []const u8) !NodeValue {
151+
var v: c.napi_value = undefined;
152+
try s2e(c.napi_get_named_property(self.napi_env, self.napi_value, property_name.ptr, &v));
153+
return NodeValue{ .napi_env = self.napi_env, .napi_value = v };
154+
}
155+
156+
/// Sets the value of a property.
157+
pub fn set(self: NodeObject.Self, property_name: []const u8, value: NodeValue) !NodeValue {
158+
try s2e(c.napi_set_named_property(self.napi_env, self.napi_value, property_name.ptr, value.napi_value));
159+
return value;
160+
}
161+
162+
/// Gets a NodeValue indicating wether the object has a property with the specified name.
163+
pub fn has(self: NodeObject.Self, property_name: []const u8) !bool {
164+
var v: bool = undefined;
165+
try s2e(c.napi_has_named_property(self.napi_env, self.napi_value, property_name.ptr, &v));
166+
return v;
167+
}
168+
169+
/// Gets a NodeValue indicating wether the object has the named own property. key must be a string or a symbol, or an error will be thrown.
170+
pub fn hasOwn(self: NodeObject.Self, property_name: []const u8) !bool {
171+
var v: bool = undefined;
172+
try s2e(c.napi_has_own_property(self.napi_env, self.napi_value, try Convert.nodeFromNative(self.napi_env, property_name), &v));
173+
return v;
174+
}
175+
/// Gets the value of a property.
176+
pub fn delete(self: NodeObject.Self, property_name: []const u8) !bool {
177+
var v: bool = undefined;
178+
try s2e(c.napi_delete_property(
179+
self.napi_env,
180+
self.napi_value,
181+
try Convert.nodeFromNative(self.napi_env, property_name),
182+
&v,
183+
));
184+
return v;
185+
}
161186

162-
// get prop
187+
/// Returns the names of the enumerable properties as a NodeArray of strings. Symbol keys will not be included.
188+
pub fn getPropertyNames(self: NodeObject.Self) !NodeArray {
189+
var v: c.napi_value = undefined;
190+
try s2e(c.napi_get_property_names(self.napi_env, self.napi_value, &v));
191+
return NodeArray{ .napi_env = self.napi_env, .napi_value = v };
192+
}
163193
};
164194

165195
/// Represents a JS function.

src/root.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ pub const NodeContext = @import("Node.zig").NodeContext;
88

99
/// Represents a Node value.
1010
pub const NodeValue = NodeValues.NodeValue;
11+
pub const NodeObject = NodeValues.NodeObject;
12+
pub const NodeArray = NodeValues.NodeArray;
1113
pub const NodeFunction = NodeValues.NodeFunction;
1214

1315
/// The InitFunction to pass to the `register` method. The `ctx` parameter

tests/node-object.spec.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import addon from "node-api-test-module";
2+
import { describe, it, expect } from "bun:test";
3+
4+
describe("NodeObject", () => {
5+
describe("has", () => {
6+
it("should return true for existing ", () => {
7+
expect(addon.nodeObject.has({ prop: "bar" })).toEqual(true);
8+
});
9+
it("should return false for non-existing ", () => {
10+
expect(addon.nodeObject.has({ foo: "bar" })).toEqual(false);
11+
});
12+
});
13+
14+
describe("hasOwn", () => {
15+
it("should return true for existing ", () => {
16+
expect(addon.nodeObject.hasOwn({ prop: "bar" })).toEqual(true);
17+
});
18+
it("should return false for non-existing ", () => {
19+
expect(addon.nodeObject.hasOwn({ foo: "bar" })).toEqual(false);
20+
});
21+
});
22+
23+
describe("getPropertyNames", () => {
24+
it("should return array of properties ", () => {
25+
const obj = { foo: "foo", bar: 123 };
26+
27+
expect(addon.nodeObject.getPropertyNames(obj)).toEqual(["foo", "bar"]);
28+
});
29+
});
30+
31+
describe("get", () => {
32+
it("should return value of existing prop ", () => {
33+
expect(addon.nodeObject.get({ prop: "bar" })).toEqual("bar");
34+
});
35+
it("should return undefined for non-existing ", () => {
36+
expect(addon.nodeObject.get({ foo: "bar" })).toEqual(undefined);
37+
});
38+
});
39+
40+
describe("get", () => {
41+
it("should return value of existing prop ", () => {
42+
expect(addon.nodeObject.get({ prop: "bar" })).toEqual("bar");
43+
});
44+
it("should return undefined for non-existing ", () => {
45+
expect(addon.nodeObject.get({ foo: "bar" })).toEqual(undefined);
46+
});
47+
});
48+
49+
describe("set", () => {
50+
it("should set value of existing prop ", () => {
51+
const obj = { prop: "bar" };
52+
expect(addon.nodeObject.set(obj, "foo")).toEqual("foo");
53+
expect(obj.prop).toEqual("foo");
54+
});
55+
it("should add prop if non-existing ", () => {
56+
const obj = { other: "bar" } as any;
57+
expect(addon.nodeObject.set(obj, "foo")).toEqual("foo");
58+
expect(obj.prop).toEqual("foo");
59+
});
60+
});
61+
62+
describe("delete", () => {
63+
it("should remove key for existing prop ", () => {
64+
const obj = { prop: "bar" } as any;
65+
expect(addon.nodeObject.delete(obj)).toEqual(true);
66+
expect(obj).toEqual({});
67+
});
68+
it("should return undefined for non-existing ", () => {
69+
const obj = { foo: "bar" } as any;
70+
expect(addon.nodeObject.delete(obj)).toEqual(true);
71+
expect(obj).toEqual({ foo: "bar" });
72+
});
73+
});
74+
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const std = @import("std");
2+
const node_api = @import("node-api");
3+
4+
const NodeContext = node_api.NodeContext;
5+
const NodeValue = node_api.NodeValue;
6+
const NodeObject = node_api.NodeObject;
7+
const NodeArray = node_api.NodeArray;
8+
9+
pub fn init() @This() {
10+
return @This(){};
11+
}
12+
13+
// TODO: make this optional
14+
pub fn deinit() !void {}
15+
16+
pub fn has(obj: NodeObject) !bool {
17+
return try obj.has("prop");
18+
}
19+
20+
pub fn hasOwn(obj: NodeObject) !bool {
21+
return try obj.hasOwn("prop");
22+
}
23+
24+
pub fn get(obj: NodeObject) !NodeValue {
25+
return try obj.get("prop");
26+
}
27+
28+
pub fn set(obj: NodeObject, v: NodeValue) !NodeValue {
29+
return try obj.set("prop", v);
30+
}
31+
32+
pub fn delete(obj: NodeObject) !bool {
33+
return try obj.delete("prop");
34+
}
35+
36+
pub fn getPropertyNames(obj: NodeObject) !NodeArray {
37+
return try obj.getPropertyNames();
38+
}

tests/zig/test-module.zig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const std = @import("std");
22
const node_api = @import("node-api");
33

44
const TestClass = @import("TestClass.zig");
5-
5+
const NodeObjectTests = @import("node_values/NodeObjectTests.zig");
66
const WrapTarget = @import("WrapTarget.zig");
77
const Serialization = @import("Serialization.zig");
88

@@ -15,6 +15,7 @@ fn init(node: node_api.NodeContext) !?node_api.NodeValue {
1515
ptr.* = .{ .foo = 123, .bar = "hopla" };
1616

1717
return try node.serialize(.{
18+
.nodeObject = NodeObjectTests,
1819
.serialization = Serialization,
1920
.TestClass = TestClass,
2021
.wrappedInstance = try node.wrapInstance(WrapTarget, .{ .foo = 123, .bar = "hopla" }),

0 commit comments

Comments
 (0)