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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions