Summary
Add a constructor (init / from) to js.Object(T) so user code can create and populate a JS object in a single call, instead of manually creating a raw object, wrapping it, and calling set.
Motivation
Defining an object shape is ergonomic:
pub const BitArray = js.Object(struct {
uint8Array: js.Uint8Array,
bitLen: js.Number,
});
But creating an instance at runtime currently requires the low-level dance:
const e = js.env();
const raw = try e.createObject(); // napi.Value, a fresh {} object
var obj = BitArray{ .val = raw }; // wrap it
try obj.set(.{ .uint8Array = ..., .bitLen = ... });
Object(T) (src/js/object.zig) currently exposes only validateArg, get, set, and toValue — there is no constructor. This boilerplate is repetitive and leaks N-API details into otherwise high-level DSL code.
Proposed solution
Add an init method to the type returned by Object(T). It already knows T and has set, so it just needs to create the underlying object and populate it:
/// Creates a new JS object and populates it from the Zig struct `T`.
pub fn init(value: T) !Self {
const self = Self{ .val = try env().createObject() };
try self.set(value);
return self;
}
(using the in-scope env accessor, matching how String.from / Number.from reach the env)
Call site collapses to:
const ba = try BitArray.init(.{
.uint8Array = js.Uint8Array.from(&bytes),
.bitLen = js.Number.from(@as(i32, 42)),
});
Naming
The scalar DSL wrappers already use from(...) (String.from, Number.from, Boolean.from, Date.from, Uint8Array.from). For consistency we could name it from instead of init — open to either. init reads slightly better here since the argument is a struct of fields rather than a single scalar.
Notes / open questions
- Should there also be an empty constructor (e.g.
empty() / init(.{}) when all fields are optional) for incremental population? Probably out of scope for the first pass.
- Keep the existing
set/get/toValue API unchanged; this is purely additive.
Summary
Add a constructor (
init/from) tojs.Object(T)so user code can create and populate a JS object in a single call, instead of manually creating a raw object, wrapping it, and callingset.Motivation
Defining an object shape is ergonomic:
But creating an instance at runtime currently requires the low-level dance:
Object(T)(src/js/object.zig) currently exposes onlyvalidateArg,get,set, andtoValue— there is no constructor. This boilerplate is repetitive and leaks N-API details into otherwise high-level DSL code.Proposed solution
Add an
initmethod to the type returned byObject(T). It already knowsTand hasset, so it just needs to create the underlying object and populate it:(using the in-scope env accessor, matching how
String.from/Number.fromreach the env)Call site collapses to:
Naming
The scalar DSL wrappers already use
from(...)(String.from,Number.from,Boolean.from,Date.from,Uint8Array.from). For consistency we could name itfrominstead ofinit— open to either.initreads slightly better here since the argument is a struct of fields rather than a single scalar.Notes / open questions
empty()/init(.{})when all fields are optional) for incremental population? Probably out of scope for the first pass.set/get/toValueAPI unchanged; this is purely additive.