From 653e7b35ce1279fac894ea569a24ffe551374bd2 Mon Sep 17 00:00:00 2001 From: richerfu Date: Wed, 8 Oct 2025 18:38:30 +0800 Subject: [PATCH 1/2] Class init --- src/napi/util/allocator.zig | 15 +++ src/napi/wrapper/callback_info.zig | 10 +- src/napi/wrapper/class.zig | 181 +++++++++++++++++++++++++++++ 3 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 src/napi/util/allocator.zig create mode 100644 src/napi/wrapper/class.zig diff --git a/src/napi/util/allocator.zig b/src/napi/util/allocator.zig new file mode 100644 index 0000000..fb347b8 --- /dev/null +++ b/src/napi/util/allocator.zig @@ -0,0 +1,15 @@ +const std = @import("std"); + +pub var global_allocator: std.mem.Allocator = std.heap.page_allocator; + +const self = @This(); + +/// Default allocator +pub fn globalAllocator() std.mem.Allocator { + return self.global_allocator; +} + +/// Set the global allocator +pub fn setGlobalAllocator(new_allocator: std.mem.Allocator) void { + self.global_allocator = new_allocator; +} diff --git a/src/napi/wrapper/callback_info.zig b/src/napi/wrapper/callback_info.zig index 08f64da..57149f8 100644 --- a/src/napi/wrapper/callback_info.zig +++ b/src/napi/wrapper/callback_info.zig @@ -7,6 +7,7 @@ pub const CallbackInfo = struct { raw: napi.napi_callback_info, env: napi.napi_env, args: []const value.NapiValue, + this: napi.napi_value, pub fn from_raw(env: napi.napi_env, raw: napi.napi_callback_info) CallbackInfo { var init_argc: usize = 0; @@ -19,7 +20,9 @@ pub const CallbackInfo = struct { const args_raw = allocator.alloc(napi.napi_value, init_argc) catch @panic("OOM"); defer allocator.free(args_raw); - const status2 = napi.napi_get_cb_info(env, raw, &init_argc, args_raw.ptr, null, null); + var this: napi.napi_value = undefined; + + const status2 = napi.napi_get_cb_info(env, raw, &init_argc, args_raw.ptr, &this, null); if (status2 != napi.napi_ok) { @panic("Failed to get callback info"); } @@ -34,6 +37,7 @@ pub const CallbackInfo = struct { .raw = raw, .env = env, .args = result, + .this = this, }; } @@ -44,4 +48,8 @@ pub const CallbackInfo = struct { pub fn Get(self: CallbackInfo, index: usize) value.NapiValue { return self.args[index]; } + + pub fn This(self: CallbackInfo) napi.napi_value { + return self.this; + } }; diff --git a/src/napi/wrapper/class.zig b/src/napi/wrapper/class.zig new file mode 100644 index 0000000..e3c697b --- /dev/null +++ b/src/napi/wrapper/class.zig @@ -0,0 +1,181 @@ +const std = @import("std"); +const napi = @import("napi-sys").napi_sys; +const CallbackInfo = @import("./callback_info.zig").CallbackInfo; +const Function = @import("../value/function.zig").Function; +const napi_env = @import("../env.zig"); +const helper = @import("../util/helper.zig"); +const allocator = @import("../util/allocator.zig").global_allocator; + +var class_constructor_ref: std.AutoHashMap([]const u8, napi.napi_ref) = std.AutoHashMap([]const u8, napi.napi_ref).init(allocator); + +pub fn ClassInstance(comptime T: type) type { + const type_info = @typeInfo(T); + if (type_info != .@"struct") { + @compileError("Class must be a struct type"); + } + + if (type_info.@"struct".is_tuple) { + @compileError("Class must be a named struct type, don't support tuple type"); + } + + const class_name = @typeName(T); + const class_fields = type_info.@"struct".fields; + const class_methods = type_info.@"struct".decls; + + const constructor = blk: for (class_methods) |method| { + if (std.mem.eql(u8, method.name, "Constructor")) { + break :blk method.value; + } + break :blk null; + } else { + @compileError("Class must have a public constructor"); + }; + + const factory = blk: for (class_methods) |method| { + if (std.mem.eql(u8, method.name, "Factory")) { + break :blk method.value; + } + break :blk null; + } else { + @compileError("Class must have a public factory"); + }; + + return struct { + name: []const u8, + env: napi.napi_env, + raw: napi.napi_value, + ref: napi.napi_ref, + value: T, + + const Self = @This(); + + pub fn New(env: napi.napi_env, callback_info: napi.napi_callback_info) *Self { + const self = allocator.create(Self) catch @panic("OOM"); + + const data = allocator.create(T) catch @panic("OOM"); + self.value = data.*; + + var new_target: napi.napi_value = undefined; + _ = napi.napi_get_new_target(env, callback_info, &new_target); + + if (new_target == null) { + const constructor_ref = class_constructor_ref.get(@typeName(T)); + if (constructor_ref == null) { + @panic("Class constructor reference not found"); + } + + _ = napi.napi_get_reference_value(env, constructor_ref.?, &self.raw); + _ = napi.napi_wrap(env, self.raw, data, null, null, &self.ref); + + return self; + } else { + const this = infos.This(); + + _ = napi.napi_wrap(env, this, data, null, null, &self.ref); + + // align with node.js behavior + var ref_count: u32 = undefined; + napi.napi_reference_unref(env, self.ref, &ref_count); + + return self; + } + } + }; +} + +pub fn Class(env: napi_env.Env, comptime T: type) ClassInstance(@TypeOf(T)) { + const type_info = @typeInfo(T); + if (type_info != .@"struct") { + @compileError("Class must be a struct type"); + } + + if (type_info.@"struct".is_tuple) { + @compileError("Class must be a named struct type, don't support tuple type"); + } + + const class_name = @typeName(T); + const class_fields = type_info.@"struct".fields; + const class_methods = type_info.@"struct".decls; + + comptime var properties: [class_fields.len + class_methods.len]napi.napi_property_descriptor = undefined; + + comptime var property_count: usize = 0; + + inline for (class_fields) |field| { + const Property = struct { + pub fn setter(setter_env: napi.napi_env, setter_info: napi.napi_callback_info) napi.napi_value {} + + pub fn getter(getter_env: napi.napi_env, getter_info: napi.napi_callback_info) napi.napi_value {} + }; + + properties[property_count] = napi.napi_property_descriptor{ + .utf8name = @ptrCast(field.name), + .utf8name_length = field.name.len, + .getter = &Property.getter, + .setter = &Property.setter, + }; + property_count += 1; + } + + inline for (class_methods) |method| { + const method_infos = @typeInfo(method); + + if (method_infos != .@"fn") { + @compileError("Method must be a function type"); + } + + const params = method_infos.@"fn".params; + const hasSelf = comptime params.len > 0 and params[0].type.? == T; + + if (std.mem.startsWith(u8, method.name, "Getter")) { + const name = method.name[6..]; + const Getter = struct { + pub fn getter(getter_env: napi.napi_env, getter_info: napi.napi_callback_info) napi.napi_value {} + }; + properties[property_count] = napi.napi_property_descriptor{ + .utf8name = @ptrCast(name), + .utf8name_length = name.len, + .getter = &Getter.getter, + .setter = null, + }; + } + if (std.mem.startsWith(u8, method.name, "Setter")) { + const name = method.name[6..]; + const Setter = struct { + pub fn setter(setter_env: napi.napi_env, setter_info: napi.napi_callback_info) napi.napi_value {} + }; + properties[property_count] = napi.napi_property_descriptor{ + .utf8name = @ptrCast(name), + .utf8name_length = name.len, + .getter = null, + .setter = &Setter.setter, + }; + } + + const Method = struct { + pub fn call(method_env: napi.napi_env, method_info: napi.napi_callback_info) napi.napi_value {} + }; + + properties[property_count] = napi.napi_property_descriptor{ + .utf8name = @ptrCast(method.name), + .utf8name_length = method.name.len, + .method = &Method.call, + .getter = null, + .setter = null, + .attributes = if (hasSelf) napi.napi_default_method else napi.napi_static, + }; + property_count += 1; + } + + const instance = ClassInstance(@TypeOf(T)); + + var con: napi.napi_value = undefined; + _ = napi.napi_define_class(env.raw, @ptrCast(class_name), class_name.len, instance.New, null, property_count, &properties, &con); + + var ref: napi.napi_ref = undefined; + _ = napi.napi_create_reference(env.raw, con, 1, &ref); + + class_constructor_ref.put(class_name, ref); + + return instance; +} From 86ce104b1b486692cd3ee85c022316f0b398ca5b Mon Sep 17 00:00:00 2001 From: richerfu Date: Fri, 10 Oct 2025 16:46:29 +0800 Subject: [PATCH 2/2] Basic implementation for Class --- examples/basic/src/class.zig | 8 + examples/basic/src/hello.zig | 3 + src/napi.zig | 3 + src/napi/util/allocator.zig | 28 +++- src/napi/util/helper.zig | 5 + src/napi/util/napi.zig | 6 +- src/napi/wrapper/class.zig | 312 ++++++++++++++++++++--------------- 7 files changed, 224 insertions(+), 141 deletions(-) create mode 100644 examples/basic/src/class.zig diff --git a/examples/basic/src/class.zig b/examples/basic/src/class.zig new file mode 100644 index 0000000..53aab2b --- /dev/null +++ b/examples/basic/src/class.zig @@ -0,0 +1,8 @@ +const napi = @import("napi"); + +const Test = struct { + name: []u8, + age: i32, +}; + +pub const TestClass = napi.Class(Test); diff --git a/examples/basic/src/hello.zig b/examples/basic/src/hello.zig index 23b391c..08c80c0 100644 --- a/examples/basic/src/hello.zig +++ b/examples/basic/src/hello.zig @@ -8,6 +8,7 @@ const array = @import("array.zig"); const object = @import("object.zig"); const function = @import("function.zig"); const thread_safe_function = @import("thread_safe_function.zig"); +const class = @import("class.zig"); const log = @import("log/log.zig"); pub const test_i32 = number.test_i32; @@ -38,6 +39,8 @@ pub const create_function = function.create_function; pub const call_thread_safe_function = thread_safe_function.call_thread_safe_function; +pub const TestClass = class.TestClass; + pub const test_hilog = log.test_hilog; comptime { diff --git a/src/napi.zig b/src/napi.zig index 924f417..3be015e 100644 --- a/src/napi.zig +++ b/src/napi.zig @@ -6,6 +6,7 @@ const module = @import("./prelude/module.zig"); const worker = @import("./napi/wrapper/worker.zig"); const err = @import("./napi/wrapper/error.zig"); const thread_safe_function = @import("./napi/wrapper/thread_safe_function.zig"); +const class = @import("./napi/wrapper/class.zig"); pub const napi_sys = @import("napi-sys"); @@ -30,6 +31,8 @@ pub const Function = function.Function; pub const CallbackInfo = callback_info.CallbackInfo; pub const Worker = worker.Worker; pub const ThreadSafeFunction = thread_safe_function.ThreadSafeFunction; +pub const Class = class.Class; +pub const ClassWithoutInit = class.ClassWithoutInit; pub const NODE_API_MODULE = module.NODE_API_MODULE; pub const NODE_API_MODULE_WITH_INIT = module.NODE_API_MODULE_WITH_INIT; diff --git a/src/napi/util/allocator.zig b/src/napi/util/allocator.zig index fb347b8..703bd74 100644 --- a/src/napi/util/allocator.zig +++ b/src/napi/util/allocator.zig @@ -1,15 +1,31 @@ const std = @import("std"); -pub var global_allocator: std.mem.Allocator = std.heap.page_allocator; +pub const AllocatorManager = struct { + allocator: std.mem.Allocator, -const self = @This(); + const Self = @This(); + + pub fn init() Self { + return Self{ + .allocator = std.heap.page_allocator, + }; + } + + pub fn get(self: *const Self) std.mem.Allocator { + return self.allocator; + } + + pub fn set(self: *Self, new_allocator: std.mem.Allocator) void { + self.allocator = new_allocator; + } +}; + +pub const global_manager = AllocatorManager.init(); -/// Default allocator pub fn globalAllocator() std.mem.Allocator { - return self.global_allocator; + return global_manager.get(); } -/// Set the global allocator pub fn setGlobalAllocator(new_allocator: std.mem.Allocator) void { - self.global_allocator = new_allocator; + global_manager.set(new_allocator); } diff --git a/src/napi/util/helper.zig b/src/napi/util/helper.zig index 7d18cbc..8d1354f 100644 --- a/src/napi/util/helper.zig +++ b/src/napi/util/helper.zig @@ -192,3 +192,8 @@ pub fn collectFunctionArgs(comptime functions: anytype) type { .is_tuple = true, } }); } + +pub fn shortTypeName(comptime T: type) []const u8 { + var iter = std.mem.splitBackwardsScalar(u8, @typeName(T), '.'); + return iter.first(); +} diff --git a/src/napi/util/napi.zig b/src/napi/util/napi.zig index 2e91376..177acb1 100644 --- a/src/napi/util/napi.zig +++ b/src/napi/util/napi.zig @@ -6,6 +6,7 @@ const Env = @import("../env.zig").Env; const NapiError = @import("../wrapper/error.zig"); const Function = @import("../value/function.zig").Function; const ThreadSafeFunction = @import("../wrapper/thread_safe_function.zig").ThreadSafeFunction; +const class = @import("../wrapper/class.zig"); pub const Napi = struct { pub fn from_napi_value(env: napi.napi_env, raw: napi.napi_value, comptime T: type) T { @@ -202,6 +203,7 @@ pub const Napi = struct { const array = try NapiValue.Array.New(Env.from_raw(env), value); return array.raw; } + const object = try NapiValue.Object.New(Env.from_raw(env), value); return object.raw; }, @@ -227,7 +229,9 @@ pub const Napi = struct { return NapiValue.String.New(Env.from_raw(env), value).raw; }, else => { - + if (comptime class.isClass(value)) { + return try value.to_napi_value(Env.from_raw(env)); + } // TODO: Implement this @compileError("Unsupported type: " ++ @typeName(value)); }, diff --git a/src/napi/wrapper/class.zig b/src/napi/wrapper/class.zig index e3c697b..0877f69 100644 --- a/src/napi/wrapper/class.zig +++ b/src/napi/wrapper/class.zig @@ -1,181 +1,225 @@ const std = @import("std"); const napi = @import("napi-sys").napi_sys; const CallbackInfo = @import("./callback_info.zig").CallbackInfo; -const Function = @import("../value/function.zig").Function; const napi_env = @import("../env.zig"); +const Napi = @import("../util/napi.zig").Napi; const helper = @import("../util/helper.zig"); -const allocator = @import("../util/allocator.zig").global_allocator; +const GlobalAllocator = @import("../util/allocator.zig"); -var class_constructor_ref: std.AutoHashMap([]const u8, napi.napi_ref) = std.AutoHashMap([]const u8, napi.napi_ref).init(allocator); +var class_constructors: std.StringHashMap(napi.napi_value) = std.StringHashMap(napi.napi_value).init(GlobalAllocator.globalAllocator()); -pub fn ClassInstance(comptime T: type) type { +pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type { const type_info = @typeInfo(T); if (type_info != .@"struct") { - @compileError("Class must be a struct type"); + @compileError("Class() only support struct type"); } if (type_info.@"struct".is_tuple) { - @compileError("Class must be a named struct type, don't support tuple type"); + @compileError("Class() does not support tuple type"); } - const class_name = @typeName(T); - const class_fields = type_info.@"struct".fields; - const class_methods = type_info.@"struct".decls; - - const constructor = blk: for (class_methods) |method| { - if (std.mem.eql(u8, method.name, "Constructor")) { - break :blk method.value; - } - break :blk null; - } else { - @compileError("Class must have a public constructor"); - }; - - const factory = blk: for (class_methods) |method| { - if (std.mem.eql(u8, method.name, "Factory")) { - break :blk method.value; - } - break :blk null; - } else { - @compileError("Class must have a public factory"); - }; + const fields = type_info.@"struct".fields; + const decls = type_info.@"struct".decls; + const class_name = comptime helper.shortTypeName(T); return struct { - name: []const u8, + const WrappedType = T; + env: napi.napi_env, raw: napi.napi_value, - ref: napi.napi_ref, - value: T, const Self = @This(); - pub fn New(env: napi.napi_env, callback_info: napi.napi_callback_info) *Self { - const self = allocator.create(Self) catch @panic("OOM"); - - const data = allocator.create(T) catch @panic("OOM"); - self.value = data.*; + // 构造器回调 + fn constructor_callback(env: napi.napi_env, callback_info: napi.napi_callback_info) callconv(.c) napi.napi_value { + const infos = CallbackInfo.from_raw(env, callback_info); - var new_target: napi.napi_value = undefined; - _ = napi.napi_get_new_target(env, callback_info, &new_target); + const data = GlobalAllocator.globalAllocator().create(T) catch return null; - if (new_target == null) { - const constructor_ref = class_constructor_ref.get(@typeName(T)); - if (constructor_ref == null) { - @panic("Class constructor reference not found"); + if (@hasDecl(T, "init")) { + var tuple_args: std.meta.ArgsTuple(T.init) = undefined; + inline for (@typeInfo(std.meta.ArgsTuple(T.init)).@"fn".params, 0..) |arg, i| { + tuple_args[i] = Napi.from_napi_value(infos.env, infos.args[i].raw, arg.type.?); } - _ = napi.napi_get_reference_value(env, constructor_ref.?, &self.raw); - _ = napi.napi_wrap(env, self.raw, data, null, null, &self.ref); - - return self; + data.* = @call(.auto, T.init, tuple_args) catch { + GlobalAllocator.globalAllocator().destroy(data); + return null; + }; } else { - const this = infos.This(); - - _ = napi.napi_wrap(env, this, data, null, null, &self.ref); + // init with zero + data.* = std.mem.zeroes(T); + // if HasInit, init with args with order of fields + if (comptime HasInit) { + inline for (fields, 0..) |field, i| { + @field(data.*, field.name) = Napi.from_napi_value(infos.env, infos.args[i].raw, field.type); + } + } + } - // align with node.js behavior - var ref_count: u32 = undefined; - napi.napi_reference_unref(env, self.ref, &ref_count); + const this_obj = infos.This(); - return self; + var ref: napi.napi_ref = undefined; + const status = napi.napi_wrap(env, this_obj, data, finalize_callback, null, &ref); + if (status != napi.napi_ok) { + GlobalAllocator.globalAllocator().destroy(data); + return null; } - } - }; -} - -pub fn Class(env: napi_env.Env, comptime T: type) ClassInstance(@TypeOf(T)) { - const type_info = @typeInfo(T); - if (type_info != .@"struct") { - @compileError("Class must be a struct type"); - } - if (type_info.@"struct".is_tuple) { - @compileError("Class must be a named struct type, don't support tuple type"); - } + var ref_count: u32 = undefined; + _ = napi.napi_reference_unref(env, ref, &ref_count); - const class_name = @typeName(T); - const class_fields = type_info.@"struct".fields; - const class_methods = type_info.@"struct".decls; + return this_obj; + } - comptime var properties: [class_fields.len + class_methods.len]napi.napi_property_descriptor = undefined; + fn finalize_callback(env: napi.napi_env, data: ?*anyopaque, hint: ?*anyopaque) callconv(.c) void { + _ = env; + _ = hint; - comptime var property_count: usize = 0; + if (data) |ptr| { + const typed_data: *T = @ptrCast(@alignCast(ptr)); + if (@hasDecl(T, "deinit")) { + typed_data.deinit(); + } + GlobalAllocator.globalAllocator().destroy(typed_data); + } + } - inline for (class_fields) |field| { - const Property = struct { - pub fn setter(setter_env: napi.napi_env, setter_info: napi.napi_callback_info) napi.napi_value {} + fn define_class(env: napi.napi_env) !napi.napi_value { + comptime var property_count: usize = 0; + inline for (fields) |_| { + property_count += 1; + } + inline for (decls) |decl| { + if (@typeInfo(@TypeOf(@field(T, decl.name))) == .Fn and + !std.mem.eql(u8, decl.name, "init") and + !std.mem.eql(u8, decl.name, "deinit")) + { + property_count += 1; + } + } - pub fn getter(getter_env: napi.napi_env, getter_info: napi.napi_callback_info) napi.napi_value {} - }; + var properties: [property_count]napi.napi_property_descriptor = undefined; + var prop_idx: usize = 0; + + inline for (fields) |field| { + const FieldAccessor = struct { + fn getter(getter_env: napi.napi_env, info: napi.napi_callback_info) callconv(.c) napi.napi_value { + const cb_info = CallbackInfo.from_raw(getter_env, info); + var data: ?*anyopaque = null; + _ = napi.napi_unwrap(getter_env, cb_info.This(), &data); + if (data == null) return null; + + const instance: *T = @ptrCast(@alignCast(data.?)); + const field_value = @field(instance.*, field.name); + return Napi.to_napi_value(getter_env, field_value, field.name) catch null; + } + + fn setter(setter_env: napi.napi_env, info: napi.napi_callback_info) callconv(.c) napi.napi_value { + const cb_info = CallbackInfo.from_raw(setter_env, info); + var data: ?*anyopaque = null; + _ = napi.napi_unwrap(setter_env, cb_info.This(), &data); + if (data == null) return null; + + const instance: *T = @ptrCast(@alignCast(data.?)); + const args = cb_info.args; + if (args.len > 0) { + const new_value = Napi.from_napi_value(setter_env, args[0].raw, field.type); + @field(instance.*, field.name) = new_value; + } + + return null; + } + }; + + properties[prop_idx] = napi.napi_property_descriptor{ + .utf8name = @ptrCast(field.name.ptr), + .name = null, + .method = null, + .getter = FieldAccessor.getter, + .setter = FieldAccessor.setter, + .value = null, + .attributes = napi.napi_default, + .data = null, + }; + prop_idx += 1; + } - properties[property_count] = napi.napi_property_descriptor{ - .utf8name = @ptrCast(field.name), - .utf8name_length = field.name.len, - .getter = &Property.getter, - .setter = &Property.setter, - }; - property_count += 1; - } + inline for (decls) |decl| { + if (@typeInfo(@TypeOf(@field(T, decl.name))) == .Fn and + !std.mem.eql(u8, decl.name, "init") and + !std.mem.eql(u8, decl.name, "deinit")) + { + const method = @field(T, decl.name); + const method_info = @typeInfo(@TypeOf(method)); + const params = method_info.Fn.params; + const is_instance_method = params.len > 0 and params[0].type.? == *T; + + const MethodWrapper = struct { + fn call(method_env: napi.napi_env, info: napi.napi_callback_info) callconv(.c) napi.napi_value { + const cb_info = CallbackInfo.from_raw(method_env, info); + + if (is_instance_method) { + var data: ?*anyopaque = null; + _ = napi.napi_unwrap(method_env, cb_info.This(), &data); + if (data == null) return null; + + const instance: *T = @ptrCast(@alignCast(data.?)); + const result = method(instance); + return Napi.to_napi_value(method_env, result, decl.name) catch null; + } else { + const result = method(); + return Napi.to_napi_value(method_env, result, decl.name) catch null; + } + } + }; + + properties[prop_idx] = napi.napi_property_descriptor{ + .utf8name = @ptrCast(decl.name.ptr), + .name = null, + .method = MethodWrapper.call, + .getter = null, + .setter = null, + .value = null, + .attributes = if (is_instance_method) napi.napi_default else napi.napi_static, + .data = null, + }; + prop_idx += 1; + } + } - inline for (class_methods) |method| { - const method_infos = @typeInfo(method); + var constructor: napi.napi_value = undefined; + _ = napi.napi_define_class(env, class_name.ptr, class_name.len, constructor_callback, null, prop_idx, &properties, &constructor); - if (method_infos != .@"fn") { - @compileError("Method must be a function type"); + try class_constructors.put(class_name, constructor); + return constructor; } - const params = method_infos.@"fn".params; - const hasSelf = comptime params.len > 0 and params[0].type.? == T; - - if (std.mem.startsWith(u8, method.name, "Getter")) { - const name = method.name[6..]; - const Getter = struct { - pub fn getter(getter_env: napi.napi_env, getter_info: napi.napi_callback_info) napi.napi_value {} - }; - properties[property_count] = napi.napi_property_descriptor{ - .utf8name = @ptrCast(name), - .utf8name_length = name.len, - .getter = &Getter.getter, - .setter = null, - }; - } - if (std.mem.startsWith(u8, method.name, "Setter")) { - const name = method.name[6..]; - const Setter = struct { - pub fn setter(setter_env: napi.napi_env, setter_info: napi.napi_callback_info) napi.napi_value {} - }; - properties[property_count] = napi.napi_property_descriptor{ - .utf8name = @ptrCast(name), - .utf8name_length = name.len, - .getter = null, - .setter = &Setter.setter, - }; - } + fn define_custom_method(_: napi.napi_env, _: napi.napi_value) !void {} - const Method = struct { - pub fn call(method_env: napi.napi_env, method_info: napi.napi_callback_info) napi.napi_value {} - }; - - properties[property_count] = napi.napi_property_descriptor{ - .utf8name = @ptrCast(method.name), - .utf8name_length = method.name.len, - .method = &Method.call, - .getter = null, - .setter = null, - .attributes = if (hasSelf) napi.napi_default_method else napi.napi_static, - }; - property_count += 1; - } + /// to_napi_value will create a class constructor and return it + pub fn to_napi_value(env: napi_env.Env) !napi.napi_value { + const constructor = try Self.define_class(env.raw); + + try Self.define_custom_method(env.raw, constructor); - const instance = ClassInstance(@TypeOf(T)); + return constructor; + } + }; +} - var con: napi.napi_value = undefined; - _ = napi.napi_define_class(env.raw, @ptrCast(class_name), class_name.len, instance.New, null, property_count, &properties, &con); +/// Create a class with default constructor function +pub fn Class(comptime T: type) type { + return ClassWrapper(T, true); +} - var ref: napi.napi_ref = undefined; - _ = napi.napi_create_reference(env.raw, con, 1, &ref); +/// Create a class without default constructor function +pub fn ClassWithoutInit(comptime T: type) type { + return ClassWrapper(T, false); +} - class_constructor_ref.put(class_name, ref); +pub fn isClass(T: anytype) bool { + const type_name = @typeName(T); - return instance; + return std.mem.indexOf(u8, type_name, "ClassWrapper") != null; }