zignapi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,116 @@
1
+ //! Module registration: turn a struct of `name = fn` pairs into the
2
+ //! `napi_register_module_v1` entry point Node looks up when it loads a `.node`.
3
+ //!
4
+ //! Usage from an addon's root source file:
5
+ //!
6
+ //! ```zig
7
+ //! const zigbind = @import("zigbind");
8
+ //! pub fn add(a: i32, b: i32) i32 { return a + b; }
9
+ //! comptime { zigbind.register(.{ .add = add }); }
10
+ //! ```
11
+
12
+ const std = @import("std");
13
+ const napi = @import("napi.zig");
14
+ const convert = @import("convert.zig");
15
+ const c = napi.c;
16
+
17
+ /// Export `napi_register_module_v1` for the given set of functions.
18
+ ///
19
+ /// `defs` must be an anonymous struct literal whose field names become the JS
20
+ /// property names and whose field values are the Zig functions to expose.
21
+ /// Must be called from a `comptime {}` block at container scope so the `@export`
22
+ /// runs while building the module.
23
+ pub fn register(comptime defs: anytype) void {
24
+ const Defs = @TypeOf(defs);
25
+ switch (@typeInfo(Defs)) {
26
+ .@"struct" => {},
27
+ else => @compileError("zigbind.register expects a struct literal, e.g. .{ .add = add }"),
28
+ }
29
+
30
+ const Registrar = struct {
31
+ fn entry(env: napi.Env, exports: napi.Value) callconv(.c) napi.Value {
32
+ inline for (@typeInfo(Defs).@"struct".fields) |field| {
33
+ const func = @field(defs, field.name);
34
+ registerOne(env, exports, field.name, func) catch {
35
+ napi.throwError(env, "zigbind: failed to register '" ++ field.name ++ "'");
36
+ return null;
37
+ };
38
+ }
39
+ return exports;
40
+ }
41
+ };
42
+
43
+ @export(&Registrar.entry, .{ .name = "napi_register_module_v1", .linkage = .strong });
44
+ }
45
+
46
+ /// Bind a single function onto `exports` under `name`.
47
+ fn registerOne(
48
+ env: napi.Env,
49
+ exports: napi.Value,
50
+ comptime name: [:0]const u8,
51
+ comptime func: anytype,
52
+ ) !void {
53
+ const fn_value = try napi.createFunction(env, name, Wrap(func).callback);
54
+ try napi.setNamedProperty(env, exports, name, fn_value);
55
+ }
56
+
57
+ /// Build the `napi_callback` trampoline for a specific Zig function.
58
+ ///
59
+ /// Each distinct `func` produces its own type (and therefore its own callback),
60
+ /// which is how we smuggle the target function into the fixed C callback
61
+ /// signature without any runtime closure/state.
62
+ fn Wrap(comptime func: anytype) type {
63
+ const fn_info = @typeInfo(@TypeOf(func)).@"fn";
64
+
65
+ return struct {
66
+ fn callback(env: napi.Env, info: napi.CallbackInfo) callconv(.c) napi.Value {
67
+ // Collect the JS arguments (one slot per Zig parameter).
68
+ var argv: [fn_info.params.len]napi.Value = undefined;
69
+ _ = napi.getCallbackInfo(env, info, &argv) catch {
70
+ napi.throwError(env, "zigbind: failed to read arguments");
71
+ return null;
72
+ };
73
+
74
+ // String arguments are copied out of V8 into this arena, which is
75
+ // freed once the call (and any return-value conversion) is done.
76
+ var arena = std.heap.ArenaAllocator.init(std.heap.c_allocator);
77
+ defer arena.deinit();
78
+ const allocator = arena.allocator();
79
+
80
+ // Convert each argument from JS into its Zig type.
81
+ var args: std.meta.ArgsTuple(@TypeOf(func)) = undefined;
82
+ inline for (fn_info.params, 0..) |param, i| {
83
+ args[i] = convert.fromJs(param.type.?, env, argv[i], allocator) catch {
84
+ napi.throwError(env, "zigbind: failed to convert argument");
85
+ return null;
86
+ };
87
+ }
88
+
89
+ const result = @call(.auto, func, args);
90
+ return finish(env, fn_info.return_type.?, result);
91
+ }
92
+
93
+ /// Convert the Zig return value to JS, turning `!T` error unions into
94
+ /// thrown JS exceptions and `void` into `undefined`.
95
+ fn finish(env: napi.Env, comptime RetType: type, result: RetType) napi.Value {
96
+ switch (@typeInfo(RetType)) {
97
+ .error_union => |eu| {
98
+ const payload = result catch |err| {
99
+ napi.throwError(env, @errorName(err));
100
+ return null;
101
+ };
102
+ return toJs(env, eu.payload, payload);
103
+ },
104
+ .void => return null,
105
+ else => return toJs(env, RetType, result),
106
+ }
107
+ }
108
+
109
+ fn toJs(env: napi.Env, comptime T: type, value: T) napi.Value {
110
+ return convert.toJs(T, env, value) catch {
111
+ napi.throwError(env, "zigbind: failed to convert return value");
112
+ return null;
113
+ };
114
+ }
115
+ };
116
+ }
@@ -0,0 +1,31 @@
1
+ //! zigbind — write native Node.js addons in Zig.
2
+ //!
3
+ //! This is the public entry point of the Zig module consumers import with
4
+ //! `@import("zigbind")`. It re-exports the pieces an addon author needs:
5
+ //!
6
+ //! - `register` : expose Zig functions to JS and emit `napi_register_module_v1`.
7
+ //! - `napi` : the raw N-API bindings and thin wrappers, for escape hatches.
8
+ //! - `convert` : the comptime Zig <-> JS value conversions.
9
+ //! - `c` : the imported N-API C API (`@cImport`), for advanced use.
10
+
11
+ const std = @import("std");
12
+
13
+ pub const napi = @import("napi.zig");
14
+ pub const convert = @import("convert.zig");
15
+ pub const async_ = @import("async.zig");
16
+
17
+ /// Register Zig functions as a Node addon. See `register.zig`.
18
+ pub const register = @import("register.zig").register;
19
+
20
+ /// The raw imported N-API C API (`napi_*` functions and types).
21
+ pub const c = napi.c;
22
+
23
+ test {
24
+ // Pull every submodule into the test build so their comptime checks and
25
+ // unit tests run with `zig build test`.
26
+ std.testing.refAllDecls(@This());
27
+ _ = napi;
28
+ _ = convert;
29
+ _ = async_;
30
+ _ = @import("register.zig");
31
+ }