Skip to content

feat(dsl): add constructor to js.Object(T) for one-line object creation #44

Description

@nazarhussain

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions