ChainSafe / ChainSafe/zapi

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

Open
#44 0 comments 1 reaction 0 assignees View on GitHub
Dominant language
Zig
Stars
4
Forks
4
Avg merge
16h 14m
Merged PRs (30d)
2

Description

## 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:

```zig
pub const BitArray = js.Object(struct {
uint8Array: js.Uint8Array,
bitLen: js.Number,
});
```

But creating an instance at runtime currently requires the low-level dance:

```zig
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:

```zig
/// 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:

```zig
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.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.