feat(js): add high-level DSL with JS-aligned types#11
Conversation
lodekeeper-z
left a comment
There was a problem hiding this comment.
zapi PR #11 — High-Level DSL Review
Great architectural PR. The comptime machinery in wrapFunction/wrapClass is well-designed, the setEnv/restoreEnv thread-local pattern correctly handles nested callbacks, the !T/?T/!?T return-type dispatch is clean, and the example coverage is excellent. This should meaningfully reduce NAPI boilerplate for lodestar-z bindings.
I have a few issues that need attention before this lands — one is a fundamental Zig limitation that affects the public API contract, and a few are safety/correctness gaps in the generated callbacks. Inline comments below.
Critical
1. exportModule exports ALL functions, not just pub fns
In Zig 0.12+, std.builtin.Type.Declaration no longer has an is_pub field — it was removed. So @typeInfo(Module).@"struct".decls iterates every declaration regardless of visibility. The README says "pub functions are auto-exported" but that's not true: any function in the file, public or private, will be picked up. If a user has private helpers with native Zig types (not DSL), wrapFunction will @compileError on them, making the whole module fail to compile. This is a breaking UX issue for non-trivial modules that have helper functions. Options:
- Require an explicit
pub const js_exports = .{add, Counter, ...}list (most explicit, no magic) - Document that all helper functions must be in a separate file
- Use a naming convention (e.g. skip
_-prefixed functions)
2. Missing argument count validation in wrapFunction and wrapMethod
After napi_get_cb_info, actual_argc reflects how many args JS actually passed. But the code always iterates 0..argc unconditionally, using zeroed raw_args[i] entries for slots beyond actual_argc. A JS caller that passes too few arguments silently gets a null napi_value wrapped in a DSL struct. The first meaningful operation on it (e.g. assertI32()) will panic and crash Node.js — rather than throwing a JS TypeError. Same issue exists in wrapMethod.
3. utf8name pointer in getPropertyDescriptors is not null-terminated
desc.utf8name = decl.name.ptr uses the raw pointer from a []const u8 slice. napi_define_class treats utf8name as a C string (const char*) and reads until \0. decl.name as a Zig slice is not guaranteed to be null-terminated in the type system — even if comptime string literals happen to have a trailing null in practice. Compare: exportModule correctly does decl.name ++ "" to get [:0]const u8. getPropertyDescriptors should do the same.
Important
4. TypedArray slice lifetime is too loose
toSlice() returns a []Element pointing directly into the ArrayBuffer's V8-managed backing buffer. This is safe only within the current callback frame, before any JS interop that could trigger GC. If the slice escapes (stored in a Zig struct, passed to an async work callback, etc.) it becomes a dangling pointer. NAPI pins the buffer during the callback, but not past it. The doc comment says "valid as long as the ArrayBuffer is alive" but that's not actionable — callers can't know when V8 will collect it. Consider: return a copied []Element (safer, clear ownership) and separately offer a withSlice(callback) API for zero-copy work within a bounded scope.
5. js_class presence checked but not its value
@hasDecl(InnerType, "js_class") is true for pub const js_class = false or pub const js_class = "oops". Should be @field(InnerType, "js_class") == true.
6. as*() narrowing methods don't actually validate the JS type
asNumber(), asString(), etc. are typed !T suggesting they can fail — but they blindly wrap the value without calling napi_typeof. Type errors are deferred to the first toI32()/toBool() call, which will return a NAPI error. At minimum, the docs should be updated. Ideally, these methods should actually check the type and return a descriptive error (e.g. error.TypeMismatch) if it doesn't match.
7. Async Promise model is incomplete for true async use
createPromise/resolve works for synchronous resolution but the intended async pattern (resolve from a work callback or another thread) requires napi_threadsafe_function + storing the napi_deferred across the callback boundary. Once napi_resolve_deferred is called the deferred is consumed. For actual async: the user needs to:
- Create the promise, extract the deferred
- Return the promise JS value
- Post work to another thread (via
napi.AsyncWork) - In the completion callback, call resolve/reject via a threadsafe function
The current Promise(T) struct doesn't help with steps 3–4. Consider adding a Promise.Resolver type that holds the deferred and can safely be passed to async work, or at least document the expected pattern.
Minor
Promise.reject()only acceptsjs.String. Can't reject with a JSErrorobject (the idiomatic JS rejection type). Consider also acceptingValueornapi.Value.toOwnedSliceallocates two buffers (one for the null-terminated read, one for the result). Could use a singlestr_len + 1allocation and slice off the null.Buffer.getSize()castsusizetoi32via@intCast— overflows for buffers > 2GiB (unlikely in practice, but worth a comment).- The
assert*()panic philosophy (crash Node.js on type mismatch) is a valid design choice, but the README should explicitly document it so users know to validate inputs before using assert variants.
|
@lodekeeper-z Please re-review this PR. |
lodekeeper-z
left a comment
There was a problem hiding this comment.
All five issues from the previous review have been addressed:
- export_module.zig: Now skips non-DSL functions (checks each param type), and validates
js_class == truewith type check (@TypeOf(...) == bool). ✅ - wrap_function.zig: Argument count validation is in place — throws
TypeErrorifactual_argc < argc. ✅ - wrap_class.zig:
utf8nameuses[:0]const u8(decl.name ++ "") so the pointer is null-terminated. ✅ - typed_arrays.zig:
toSlicenow has a clear doc comment warning about the V8-managed backing store lifetime.fromcopies data into a new ArrayBuffer. ✅ - value.zig: All
as*()narrowing methods now callnapi_typeof(orisArray/isDate/isTypedarray) and returnerror.TypeMismatchon mismatch. ✅ - promise.zig:
rejectnow accepts any DSL/napi value, andrejectWithMessagecreates a proper JS Error object. ✅
LGTM. 🚀
lodekeeper-z
left a comment
There was a problem hiding this comment.
Re-reviewed after fix commits. All previously raised issues addressed:
as*()methods now validate JS types at runtime viaexpectType()✅reject()acceptsanytype, withrejectWithMessage()for proper Error objects ✅exportModulefilters by DSL function signature, validatesjs_classasbool✅- Null-terminated names via
decl.name ++ ""trick ✅ - Typed array subtype validation added ✅
Clean design. LGTM.
|
@lodekeeper-z Please review the last commit and see what implications can there be for adding |
390d398 to
e9e54dd
Compare
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…se async model, Object(T) accessors, init contract, wrapFunction error handling, null/undefined distinction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ucture Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use raw C APIs instead of Env.createFunction/defineClass - Make convertArg/callAndConvert/convertReturn pub - Fix invalid unit tests in context.zig - Fix isDslType to use @typeinfo field iteration Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restructure root.zig into napi + js namespaces while preserving backwards-compatible flat exports via usingnamespace. Add thread-local context (env/allocator), and zero-cost DSL wrappers for Number, String, Boolean, BigInt, and Date. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Array, Object(T), Function, TypedArray variants, Promise(T), and Value escape hatch. Update js.zig with full re-exports including all 11 typed array types, createPromise, and throwError helper. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Array.get() and Function.call() now return !Value (DSL type) instead of !napi.Value
- Promise(T) stores val field alongside deferred; createPromise returns error union
- Value is* methods return bool (using catch return false pattern)
- Value as* methods return error unions
- Added missing Value.asObject(comptime T) method
- throwError uses catch {} instead of catch @Panic
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Core comptime module that converts DSL-typed Zig functions into N-API C callbacks. Includes isDslType, convertArg, convertReturn, callAndConvert, and wrapFunction which handles error unions, optionals, and plain returns. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Generates N-API constructor, finalizer, property descriptors, and method wrappers for Zig structs marked with js_class=true. Instance methods are detected by self-param type; static methods use wrapFunction directly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Iterates module declarations at comptime, wrapping public functions via wrapFunction and js_class structs via wrapClass, then registers them using raw N-API C calls and napi.module.register. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add wrapFunction, wrapClass, exportModule, and helper functions (isDslType, convertArg, convertReturn, callAndConvert) to the public js module API. Reference new modules in test block. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a class method takes `self: T` (by value), wrapMethod was passing `*T` instead of dereferencing to `T`. Now correctly distinguishes by-value (`T`) from pointer (`*T`, `*const T`) self parameters. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add example demonstrating the JS DSL with functions (add, greet, findValue, willThrow) and a Counter class. Update build.zig to build the example as a .node addon. Fix wrapClass property descriptor generation to use a comptime block for Zig 0.14 compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover all DSL types: Number, String, Boolean, BigInt, Date, Array, Object, Function, Value, Uint8Array, Float64Array, Promise, and classes with deinit. Includes error handling, typed objects, callbacks, and mixed DSL + N-API usage sections. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add 29 tests across 11 describe blocks: basic functions, error handling, primitives (Number, Boolean, String, BigInt, Date), typed objects, arrays, typed arrays, promises, callbacks, Counter/Buffer classes, and mixed DSL + N-API interop. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
wemeetagain
left a comment
There was a problem hiding this comment.
Great work, this is really nice. Good UX and small, manageable runtime.
The merge with main brought in the js_dsl feature (ChainSafe#11) and a refactor of `src/root.zig` (napi symbols moved to `src/napi.zig`, re-exported via `pub usingnamespace`). All of that was written for 0.14.1 and hits several 0.16 breaking changes: * `b.modules.put(key, value)` → `b.modules.put(allocator, key, value)`. Two call sites in `build.zig` were missed by the previous update pass (the `napi` module and the `example_js_dsl` module). * `src/napi.zig` exported `pub const c = @import("c.zig")`, but every internal file and every example accesses napi types through the inner `.c` namespace (`c.napi_env`, `c.napi_value`, …). Restore the `.c` on the import so call sites keep compiling. * `pub usingnamespace` was removed in Zig 0.16. Replace it in `src/root.zig` with explicit `pub const foo = napi.foo;` re-exports for every symbol previously namespace-injected from `napi.zig`. * `callconv(.C)` → `callconv(.c)` (6 NAPI callback shims in `src/js/wrap_class.zig` and `src/js/wrap_function.zig`). The `builtin.CallingConvention` union renamed `.C` to `.c`. * `std.Thread.Mutex` → `std.Io.Mutex`. `src/js/class_runtime.zig` kept a small per-class registry behind a mutex; Zig 0.16 moved Mutex into `std.Io` and lock/unlock now require an `Io` argument. Use the stdlib-provided `std.Io.Threaded.global_single_threaded` instance for this hardcoded-reference case (stdlib doc explicitly permits it for non-library code). After this commit `zig build` succeeds, and `zig build test` runs the 42 regular tests green. `example_js_dsl` has a separate, pre-existing link issue in `zig build test` (undefined `_napi_wrap` and friends, which Node resolves at runtime when loading the `.node` file) — that's orthogonal to the 0.16 migration.
The merge with main brought in the js_dsl feature (ChainSafe#11) and a refactor of `src/root.zig` (napi symbols moved to `src/napi.zig`, re-exported via `pub usingnamespace`). All of that was written for 0.14.1 and hits several 0.16 breaking changes: * `b.modules.put(key, value)` → `b.modules.put(allocator, key, value)`. Two call sites in `build.zig` were missed by the previous update pass (the `napi` module and the `example_js_dsl` module). * `src/napi.zig` exported `pub const c = @import("c.zig")`, but every internal file and every example accesses napi types through the inner `.c` namespace (`c.napi_env`, `c.napi_value`, …). Restore the `.c` on the import so call sites keep compiling. * `pub usingnamespace` was removed in Zig 0.16. Replace it in `src/root.zig` with explicit `pub const foo = napi.foo;` re-exports for every symbol previously namespace-injected from `napi.zig`. * `callconv(.C)` → `callconv(.c)` (6 NAPI callback shims in `src/js/wrap_class.zig` and `src/js/wrap_function.zig`). The `builtin.CallingConvention` union renamed `.C` to `.c`. * `std.Thread.Mutex` → `std.Io.Mutex`. `src/js/class_runtime.zig` kept a small per-class registry behind a mutex; Zig 0.16 moved Mutex into `std.Io` and lock/unlock now require an `Io` argument. - Public API (`registerClass`, `getConstructor`, `materializeClassInstance`) now takes `io: std.Io` as an explicit parameter and uses the cancelable `try mutex.lock(io)`, so callers can propagate cancellation. - `Entry` caches the registering caller's `io`; `cleanupHook` (a napi callback with a fixed `(*Entry)` signature that cannot return errors) reads `entry.io` and uses `lockUncancelable` — unwinding on cancel would leak the entry and leave napi holding a dangling pointer. - `src/js/runtime_io.zig` (new) owns an explicitly-initialized `std.Io.Threaded` instance and exposes `io()`. The two callers of `registerClass`/`materializeClassInstance` that run under napi C callback boundaries (no user-threaded io available) read from this file instead of hard-coding `std.Io.Threaded.global_single_threaded` — keeps the stdlib- policy violation ("library code should avoid that global") confined to a single, self-documented declaration. After this commit `zig build` succeeds, and `zig build test` runs the 42 regular tests green. `example_js_dsl` has a separate, pre-existing link issue in `zig build test` (undefined `_napi_wrap` and friends, which Node resolves at runtime when loading the `.node` file) — that's orthogonal to the 0.16 migration.
The merge with main brought in the js_dsl feature (ChainSafe#11) and a refactor of `src/root.zig` (napi symbols moved to `src/napi.zig`, re-exported via `pub usingnamespace`). All of that was written for 0.14.1 and hits several 0.16 breaking changes: * `b.modules.put(key, value)` → `b.modules.put(allocator, key, value)`. Two call sites in `build.zig` were missed by the previous update pass (the `napi` module and the `example_js_dsl` module). * `src/napi.zig` exported `pub const c = @import("c.zig")`, but every internal file and every example accesses napi types through the inner `.c` namespace (`c.napi_env`, `c.napi_value`, …). Restore the `.c` on the import. * `pub usingnamespace` was removed in Zig 0.16. Replace it in `src/root.zig` with explicit `pub const foo = napi.foo;` re-exports for every symbol previously namespace-injected from `napi.zig`. * `callconv(.C)` → `callconv(.c)` (6 NAPI callback shims in `src/js/wrap_class.zig` and `src/js/wrap_function.zig`). The `builtin.CallingConvention` union renamed `.C` to `.c`. * `std.Thread.Mutex` → `std.Io.Mutex`. The per-class registry in `src/js/class_runtime.zig` needs a 0.16-compatible mutex. Rather than hardcoding `std.Io.Threaded.global_single_threaded` across scattered call sites (stdlib explicitly discourages that in library code), `Env` gains an `io: std.Io` field: - `Env.fromRaw(raw_env)` is the new constructor used by napi C callbacks. It pairs the raw `napi_env` with an `Io` drawn from a single explicitly-initialized `std.Io.Threaded` instance in `src/Env.zig` (`Env.processIo()`). - All 19 direct `Env{ .env = raw_env }` construction sites (in `module.zig`, `callback.zig`, `finalize_callback.zig`, `async_work.zig`, `threadsafe_function.zig`, `wrap_function.zig`, `wrap_class.zig`) switched to `Env.fromRaw`, so every callback path carries `io` alongside the env. - `class_runtime.zig`'s mutex now uses `env.io` directly. Public APIs (`registerClass`, `getConstructor`, `materializeClassInstance`) take `env` (which already carries `io`) — no separate `io` parameter. - Normal paths use cancelable `try mutex.lock(env.io)` so caller cancellation can propagate. - `Entry` caches `env.io` so `cleanupHook` (a napi C callback with a fixed `(*Entry)` signature that cannot return errors) can still operate the shared mutex. That path uses `lockUncancelable` deliberately — unwinding on cancel would leak the entry and leave napi holding a dangling pointer. After this commit `zig build` succeeds, and `zig build test` runs the 42 regular tests green. `example_js_dsl` has a separate, pre-existing link issue in `zig build test` (undefined `_napi_wrap` and friends, which Node resolves at runtime when loading the `.node` file) — orthogonal to the 0.16 migration.
The merge with main brought in the js_dsl feature (ChainSafe#11) and a refactor of `src/root.zig` (napi symbols moved to `src/napi.zig`, re-exported via `pub usingnamespace`). All of that was written for 0.14.1 and hits several 0.16 breaking changes: * `b.modules.put(key, value)` → `b.modules.put(allocator, key, value)`. Two call sites in `build.zig` were missed by the previous update pass (the `napi` module and the `example_js_dsl` module). * `src/napi.zig` exported `pub const c = @import("c.zig")`, but every internal file and every example accesses napi types through the inner `.c` namespace. Restore the `.c` on the import. * `pub usingnamespace` was removed in Zig 0.16. Replace it in `src/root.zig` with explicit `pub const foo = napi.foo;` re-exports. * `callconv(.C)` → `callconv(.c)` (6 NAPI callback shims in `src/js/wrap_class.zig` and `src/js/wrap_function.zig`). * `std.Thread.Mutex` → `std.Io.Mutex`. The per-class registry in `src/js/class_runtime.zig` needs a 0.16-compatible mutex, which requires an `Io`. Rather than reaching for stdlib's `global_single_threaded` directly at every call site, introduce `src/runtime.zig`: - `pub fn init(io: std.Io) void` — caller-settable; intended for the user's moduleInit to inject their application's `Io`. - `pub fn io() std.Io` — returns the configured `Io`, or a default zapi-owned `init_single_threaded` instance if `init` was never called, so simple modules keep working without extra setup. `Env` stays a pure napi-env wrapper (no `io` field). Every napi C callback still uses `Env.fromRaw(raw_env)` (added so future additions to `Env` don't require touching 20+ call sites), and `class_runtime.zig`'s mutex reads `runtime.io()` directly: - `registerClass`/`getConstructor` use cancelable `try mutex.lock()` so caller cancellation propagates. - `cleanupHook` (a napi C callback with a fixed `(*Entry)` signature that cannot return errors) uses `lockUncancelable` deliberately — unwinding on cancel would leak the entry and hand napi a dangling pointer. After this commit `zig build` succeeds, and `zig build test` runs the 42 regular tests green. `example_js_dsl` has a separate, pre-existing link issue in `zig build test` (undefined `_napi_wrap` and friends, resolved by Node at runtime when loading the `.node` file) — orthogonal to the 0.16 migration.
The merge with main brought in the js_dsl feature (ChainSafe#11) and a refactor of `src/root.zig` (napi symbols moved to `src/napi.zig`, re-exported via `pub usingnamespace`). All of that was written for 0.14.1 and hits several 0.16 breaking changes: * `b.modules.put(key, value)` → `b.modules.put(allocator, key, value)`. Two call sites in `build.zig` were missed by the previous update pass (the `napi` module and the `example_js_dsl` module). * `src/napi.zig` exported `pub const c = @import("c.zig")`, but every internal file accesses napi types through the inner `.c` namespace. Restore the `.c` on the import. * `pub usingnamespace` was removed in Zig 0.16. Replace it in `src/root.zig` with explicit `pub const foo = napi.foo;` re-exports. * `callconv(.C)` → `callconv(.c)` (6 NAPI callback shims in `src/js/wrap_class.zig` and `src/js/wrap_function.zig`). * `std.Thread.Mutex` → `std.Io.Mutex`. The DSL's per-class registry in `src/js/class_runtime.zig` needs a 0.16-compatible mutex, which requires an `Io`. The `Io` is threaded in through the DSL's entry point rather than letting zapi carry global state: - `js.exportModule` gains an optional `.io = fn () std.Io` field in `options`. The generated `moduleInit` calls it after the user's `options.init` hook and passes the result down into `registerDecls` → `class_runtime.registerClass`. If `.io` is omitted, a DSL-local `init_single_threaded` fallback keeps zero-config modules compiling. - `registerClass` caches the resolved `Io` in per-`T` `state(T).io`; `getConstructor`, `materializeClassInstance`, and `cleanupHook` all read from this cache because they execute inside napi C callbacks that can't receive user-provided `Io`. - Normal paths (`registerClass`, `getConstructor`) use cancelable `try mutex.lock(io)` so caller cancellation can propagate. - `cleanupHook` (a napi C callback with a fixed `(*Entry)` signature that cannot return errors) uses `lockUncancelable` deliberately — unwinding on cancel would leak the entry and hand napi a dangling pointer. * `Env` gains a `fromRaw(raw_env)` constructor so the 19 napi C callback sites don't all hand-roll the `Env{ .env = ... }` literal. `Env` itself stays a pure `napi_env` wrapper — no `io` field. After this commit `zig build` succeeds, and `zig build test` runs the 42 regular tests green. `example_js_dsl` has a separate, pre-existing link issue in `zig build test` (undefined `_napi_wrap` and friends, resolved by Node at runtime when loading the `.node` file) — orthogonal to the 0.16 migration.
The merge with main brought in the js_dsl feature (ChainSafe#11) and a refactor of `src/root.zig` (napi symbols moved to `src/napi.zig`, re-exported via `pub usingnamespace`). All of that was written for 0.14.1 and hits several 0.16 breaking changes: * `b.modules.put(key, value)` → `b.modules.put(allocator, key, value)`. Two call sites in `build.zig` were missed by the previous update pass (the `napi` module and the `example_js_dsl` module). * `src/napi.zig` exported `pub const c = @import("c.zig")`, but every internal file accesses napi types through the inner `.c` namespace. Restore the `.c` on the import. * `pub usingnamespace` was removed in Zig 0.16. Replace it in `src/root.zig` with explicit `pub const foo = napi.foo;` re-exports. * `callconv(.C)` → `callconv(.c)` (6 NAPI callback shims in `src/js/wrap_class.zig` and `src/js/wrap_function.zig`). * `std.Thread.Mutex` → `std.Io.Mutex`. The DSL's per-class registry in `src/js/class_runtime.zig` needs a 0.16-compatible mutex, which requires an `Io`. The `Io` is threaded in through the DSL's entry point rather than letting zapi carry global state: - `js.exportModule` gains an optional `.io = fn () std.Io` field in `options`. The generated `moduleInit` calls it after the user's `options.init` hook and passes the result down into `registerDecls` → `class_runtime.registerClass`. If `.io` is omitted, a DSL-local `init_single_threaded` fallback keeps zero-config modules compiling. - `registerClass` caches the resolved `Io` in per-`T` `state(T).io`; `getConstructor`, `materializeClassInstance`, and `cleanupHook` all read from this cache because they execute inside napi C callbacks that can't receive user-provided `Io`. - Normal paths (`registerClass`, `getConstructor`) use cancelable `try mutex.lock(io)` so caller cancellation can propagate. - `cleanupHook` (a napi C callback with a fixed `(*Entry)` signature that cannot return errors) uses `lockUncancelable` deliberately — unwinding on cancel would leak the entry and hand napi a dangling pointer. * `Env` gains a `fromRaw(raw_env)` constructor so the 19 napi C callback sites don't all hand-roll the `Env{ .env = ... }` literal. `Env` itself stays a pure `napi_env` wrapper — no `io` field. After this commit `zig build` succeeds, and `zig build test` runs the 42 regular tests green. `example_js_dsl` has a separate, pre-existing link issue in `zig build test` (undefined `_napi_wrap` and friends, resolved by Node at runtime when loading the `.node` file) — orthogonal to the 0.16 migration.
The merge with main brought in the js_dsl feature (ChainSafe#11) and a refactor of `src/root.zig` (napi symbols moved to `src/napi.zig`, re-exported via `pub usingnamespace`). All of that was written for 0.14.1 and hits several 0.16 breaking changes: * `b.modules.put(key, value)` → `b.modules.put(allocator, key, value)`. Two call sites in `build.zig` were missed by the previous update pass (the `napi` module and the `example_js_dsl` module). * `src/napi.zig` exported `pub const c = @import("c.zig")`, but every internal file accesses napi types through the inner `.c` namespace. Restore the `.c` on the import. * `pub usingnamespace` was removed in Zig 0.16. Replace it in `src/root.zig` with explicit `pub const foo = napi.foo;` re-exports. * `callconv(.C)` → `callconv(.c)` (6 NAPI callback shims in `src/js/wrap_class.zig` and `src/js/wrap_function.zig`). * `std.Thread.Mutex` → `std.Io.Mutex`. The DSL's per-class registry in `src/js/class_runtime.zig` needs a 0.16-compatible mutex, which requires an `Io`. The `Io` is threaded in through the DSL's entry point rather than letting zapi carry global state: - `js.exportModule` gains an optional `.io = fn () std.Io` field in `options`. The generated `moduleInit` calls it after the user's `options.init` hook and passes the result down into `registerDecls` → `class_runtime.registerClass`. If `.io` is omitted, a DSL-local `init_single_threaded` fallback keeps zero-config modules compiling. - `registerClass` caches the resolved `Io` in per-`T` `state(T).io`; `getConstructor`, `materializeClassInstance`, and `cleanupHook` all read from this cache because they execute inside napi C callbacks that can't receive user-provided `Io`. - Normal paths (`registerClass`, `getConstructor`) use cancelable `try mutex.lock(io)` so caller cancellation can propagate. - `cleanupHook` (a napi C callback with a fixed `(*Entry)` signature that cannot return errors) uses `lockUncancelable` deliberately — unwinding on cancel would leak the entry and hand napi a dangling pointer. * `Env` gains a `fromRaw(raw_env)` constructor so the 19 napi C callback sites don't all hand-roll the `Env{ .env = ... }` literal. `Env` itself stays a pure `napi_env` wrapper — no `io` field. After this commit `zig build` succeeds, and `zig build test` runs the 42 regular tests green. `example_js_dsl` has a separate, pre-existing link issue in `zig build test` (undefined `_napi_wrap` and friends, resolved by Node at runtime when loading the `.node` file) — orthogonal to the 0.16 migration.
The merge with main brought in the js_dsl feature (ChainSafe#11) and a refactor of `src/root.zig` (napi symbols moved to `src/napi.zig`, re-exported via `pub usingnamespace`). All of that was written for 0.14.1 and hits several 0.16 breaking changes: * `b.modules.put(key, value)` → `b.modules.put(allocator, key, value)`. Two call sites in `build.zig` were missed by the previous update pass (the `napi` module and the `example_js_dsl` module). * `src/napi.zig` exported `pub const c = @import("c.zig")`, but every internal file accesses napi types through the inner `.c` namespace. Restore the `.c` on the import. * `pub usingnamespace` was removed in Zig 0.16. Replace it in `src/root.zig` with explicit `pub const foo = napi.foo;` re-exports. * `callconv(.C)` → `callconv(.c)` (6 NAPI callback shims in `src/js/wrap_class.zig` and `src/js/wrap_function.zig`). * `std.Thread.Mutex` → `std.Io.Mutex`. The DSL's per-class registry in `src/js/class_runtime.zig` needs a 0.16-compatible mutex, which requires an `Io`. The `Io` is threaded in through the DSL's entry point rather than letting zapi carry global state: - `js.exportModule` gains an optional `.io = fn () std.Io` field in `options`. The generated `moduleInit` calls it after the user's `options.init` hook and passes the result down into `registerDecls` → `class_runtime.registerClass`. If `.io` is omitted, a DSL-local `init_single_threaded` fallback keeps zero-config modules compiling. - `registerClass` caches the resolved `Io` in per-`T` `state(T).io`; `getConstructor`, `materializeClassInstance`, and `cleanupHook` all read from this cache because they execute inside napi C callbacks that can't receive user-provided `Io`. - Normal paths (`registerClass`, `getConstructor`) use cancelable `try mutex.lock(io)` so caller cancellation can propagate. - `cleanupHook` (a napi C callback with a fixed `(*Entry)` signature that cannot return errors) uses `lockUncancelable` deliberately — unwinding on cancel would leak the entry and hand napi a dangling pointer. * `Env` gains a `fromRaw(raw_env)` constructor so the 19 napi C callback sites don't all hand-roll the `Env{ .env = ... }` literal. `Env` itself stays a pure `napi_env` wrapper — no `io` field. After this commit `zig build` succeeds, and `zig build test` runs the 42 regular tests green. `example_js_dsl` has a separate, pre-existing link issue in `zig build test` (undefined `_napi_wrap` and friends, resolved by Node at runtime when loading the `.node` file) — orthogonal to the 0.16 migration.
The merge with main brought in the js_dsl feature (ChainSafe#11) and a refactor of `src/root.zig` (napi symbols moved to `src/napi.zig`, re-exported via `pub usingnamespace`). All of that was written for 0.14.1 and hits several 0.16 breaking changes: * `b.modules.put(key, value)` → `b.modules.put(allocator, key, value)`. Two call sites in `build.zig` were missed by the previous update pass (the `napi` module and the `example_js_dsl` module). * `src/napi.zig` exported `pub const c = @import("c.zig")`, but every internal file accesses napi types through the inner `.c` namespace. Restore the `.c` on the import. * `pub usingnamespace` was removed in Zig 0.16. Replace it in `src/root.zig` with explicit `pub const foo = napi.foo;` re-exports. * `callconv(.C)` → `callconv(.c)` (6 NAPI callback shims in `src/js/wrap_class.zig` and `src/js/wrap_function.zig`). * `std.Thread.Mutex` → `std.Io.Mutex`. The DSL's per-class registry in `src/js/class_runtime.zig` needs a 0.16-compatible mutex, which requires an `Io`. The `Io` is threaded in through the DSL's entry point rather than letting zapi carry global state: - `js.exportModule` gains an optional `.io = fn () std.Io` field in `options`. The generated `moduleInit` calls it after the user's `options.init` hook and passes the result down into `registerDecls` → `class_runtime.registerClass`. If `.io` is omitted, a DSL-local `init_single_threaded` fallback keeps zero-config modules compiling. - `registerClass` caches the resolved `Io` in per-`T` `state(T).io`; `getConstructor`, `materializeClassInstance`, and `cleanupHook` all read from this cache because they execute inside napi C callbacks that can't receive user-provided `Io`. - Normal paths (`registerClass`, `getConstructor`) use cancelable `try mutex.lock(io)` so caller cancellation can propagate. - `cleanupHook` (a napi C callback with a fixed `(*Entry)` signature that cannot return errors) uses `lockUncancelable` deliberately — unwinding on cancel would leak the entry and hand napi a dangling pointer. * `Env` gains a `fromRaw(raw_env)` constructor so the 19 napi C callback sites don't all hand-roll the `Env{ .env = ... }` literal. `Env` itself stays a pure `napi_env` wrapper — no `io` field. After this commit `zig build` succeeds, and `zig build test` runs the 42 regular tests green. `example_js_dsl` has a separate, pre-existing link issue in `zig build test` (undefined `_napi_wrap` and friends, resolved by Node at runtime when loading the `.node` file) — orthogonal to the 0.16 migration.
The merge with main brought in the js_dsl feature (ChainSafe#11) and a refactor of `src/root.zig` (napi symbols moved to `src/napi.zig`, re-exported via `pub usingnamespace`). All of that was written for 0.14.1 and hits several 0.16 breaking changes: * `b.modules.put(key, value)` → `b.modules.put(allocator, key, value)`. Two call sites in `build.zig` were missed by the previous update pass (the `napi` module and the `example_js_dsl` module). * `src/napi.zig` exported `pub const c = @import("c.zig")`, but every internal file accesses napi types through the inner `.c` namespace. Restore the `.c` on the import. * `pub usingnamespace` was removed in Zig 0.16. Replace it in `src/root.zig` with explicit `pub const foo = napi.foo;` re-exports. * `callconv(.C)` → `callconv(.c)` (6 NAPI callback shims in `src/js/wrap_class.zig` and `src/js/wrap_function.zig`). * `std.Thread.Mutex` → `std.Io.Mutex`. The DSL's per-class registry in `src/js/class_runtime.zig` needs a 0.16-compatible mutex, which requires an `Io`. The `Io` is threaded in through the DSL's entry point rather than letting zapi carry global state: - `js.exportModule` gains an optional `.io = fn () std.Io` field in `options`. The generated `moduleInit` calls it after the user's `options.init` hook and passes the result down into `registerDecls` → `class_runtime.registerClass`. If `.io` is omitted, a DSL-local `init_single_threaded` fallback keeps zero-config modules compiling. - `registerClass` caches the resolved `Io` in per-`T` `state(T).io`; `getConstructor`, `materializeClassInstance`, and `cleanupHook` all read from this cache because they execute inside napi C callbacks that can't receive user-provided `Io`. - Normal paths (`registerClass`, `getConstructor`) use cancelable `try mutex.lock(io)` so caller cancellation can propagate. - `cleanupHook` (a napi C callback with a fixed `(*Entry)` signature that cannot return errors) uses `lockUncancelable` deliberately — unwinding on cancel would leak the entry and hand napi a dangling pointer. * `Env` gains a `fromRaw(raw_env)` constructor so the 19 napi C callback sites don't all hand-roll the `Env{ .env = ... }` literal. `Env` itself stays a pure `napi_env` wrapper — no `io` field. After this commit `zig build` succeeds, and `zig build test` runs the 42 regular tests green. `example_js_dsl` has a separate, pre-existing link issue in `zig build test` (undefined `_napi_wrap` and friends, resolved by Node at runtime when loading the `.node` file) — orthogonal to the 0.16 migration.
The merge with main brought in the js_dsl feature (ChainSafe#11) and a refactor of `src/root.zig` (napi symbols moved to `src/napi.zig`, re-exported via `pub usingnamespace`). All of that was written for 0.14.1 and hits several 0.16 breaking changes: * `b.modules.put(key, value)` → `b.modules.put(allocator, key, value)`. Two call sites in `build.zig` were missed by the previous update pass (the `napi` module and the `example_js_dsl` module). * `src/napi.zig` exported `pub const c = @import("c.zig")`, but every internal file accesses napi types through the inner `.c` namespace. Restore the `.c` on the import. * `pub usingnamespace` was removed in Zig 0.16. Replace it in `src/root.zig` with explicit `pub const foo = napi.foo;` re-exports. * `callconv(.C)` → `callconv(.c)` (6 NAPI callback shims in `src/js/wrap_class.zig` and `src/js/wrap_function.zig`). * `std.Thread.Mutex` → `std.Io.Mutex`. The DSL's per-class registry in `src/js/class_runtime.zig` needs a 0.16-compatible mutex, which requires an `Io`. The `Io` is threaded in through the DSL's entry point rather than letting zapi carry global state: - `js.exportModule` gains an optional `.io = fn () std.Io` field in `options`. The generated `moduleInit` calls it after the user's `options.init` hook and passes the result down into `registerDecls` → `class_runtime.registerClass`. If `.io` is omitted, a DSL-local `init_single_threaded` fallback keeps zero-config modules compiling. - `registerClass` caches the resolved `Io` in per-`T` `state(T).io`; `getConstructor`, `materializeClassInstance`, and `cleanupHook` all read from this cache because they execute inside napi C callbacks that can't receive user-provided `Io`. - Normal paths (`registerClass`, `getConstructor`) use cancelable `try mutex.lock(io)` so caller cancellation can propagate. - `cleanupHook` (a napi C callback with a fixed `(*Entry)` signature that cannot return errors) uses `lockUncancelable` deliberately — unwinding on cancel would leak the entry and hand napi a dangling pointer. * `Env` gains a `fromRaw(raw_env)` constructor so the 19 napi C callback sites don't all hand-roll the `Env{ .env = ... }` literal. `Env` itself stays a pure `napi_env` wrapper — no `io` field. After this commit `zig build` succeeds, and `zig build test` runs the 42 regular tests green. `example_js_dsl` has a separate, pre-existing link issue in `zig build test` (undefined `_napi_wrap` and friends, resolved by Node at runtime when loading the `.node` file) — orthogonal to the 0.16 migration.
The merge with main brought in the js_dsl feature (ChainSafe#11) and a refactor of `src/root.zig` (napi symbols moved to `src/napi.zig`, re-exported via `pub usingnamespace`). All of that was written for 0.14.1 and hits several 0.16 breaking changes: * `b.modules.put(key, value)` → `b.modules.put(allocator, key, value)`. Two call sites in `build.zig` were missed by the previous update pass (the `napi` module and the `example_js_dsl` module). * `src/napi.zig` exported `pub const c = @import("c.zig")`, but every internal file accesses napi types through the inner `.c` namespace. Restore the `.c` on the import. * `pub usingnamespace` was removed in Zig 0.16. Replace it in `src/root.zig` with explicit `pub const foo = napi.foo;` re-exports. * `callconv(.C)` → `callconv(.c)` (6 NAPI callback shims in `src/js/wrap_class.zig` and `src/js/wrap_function.zig`). * `std.Thread.Mutex` → `std.Io.Mutex`. The DSL's per-class registry in `src/js/class_runtime.zig` needs a 0.16-compatible mutex, which requires an `Io`. The `Io` is threaded in through the DSL's entry point rather than letting zapi carry global state: - `js.exportModule` gains an optional `.io = fn () std.Io` field in `options`. The generated `moduleInit` calls it after the user's `options.init` hook and passes the result down into `registerDecls` → `class_runtime.registerClass`. If `.io` is omitted, a DSL-local `init_single_threaded` fallback keeps zero-config modules compiling. - `registerClass` caches the resolved `Io` in per-`T` `state(T).io`; `getConstructor`, `materializeClassInstance`, and `cleanupHook` all read from this cache because they execute inside napi C callbacks that can't receive user-provided `Io`. - Normal paths (`registerClass`, `getConstructor`) use cancelable `try mutex.lock(io)` so caller cancellation can propagate. - `cleanupHook` (a napi C callback with a fixed `(*Entry)` signature that cannot return errors) uses `lockUncancelable` deliberately — unwinding on cancel would leak the entry and hand napi a dangling pointer. * `Env` gains a `fromRaw(raw_env)` constructor so the 19 napi C callback sites don't all hand-roll the `Env{ .env = ... }` literal. `Env` itself stays a pure `napi_env` wrapper — no `io` field. After this commit `zig build` succeeds, and `zig build test` runs the 42 regular tests green. `example_js_dsl` has a separate, pre-existing link issue in `zig build test` (undefined `_napi_wrap` and friends, resolved by Node at runtime when loading the `.node` file) — orthogonal to the 0.16 migration.
The merge with main brought in the js_dsl feature (ChainSafe#11) and a refactor of `src/root.zig` (napi symbols moved to `src/napi.zig`, re-exported via `pub usingnamespace`). All of that was written for 0.14.1 and hits several 0.16 breaking changes: * `b.modules.put(key, value)` → `b.modules.put(allocator, key, value)`. Two call sites in `build.zig` were missed by the previous update pass (the `napi` module and the `example_js_dsl` module). * `src/napi.zig` exported `pub const c = @import("c.zig")`, but every internal file accesses napi types through the inner `.c` namespace. Restore the `.c` on the import. * `pub usingnamespace` was removed in Zig 0.16. Replace it in `src/root.zig` with explicit `pub const foo = napi.foo;` re-exports. * `callconv(.C)` → `callconv(.c)` (6 NAPI callback shims in `src/js/wrap_class.zig` and `src/js/wrap_function.zig`). * `std.Thread.Mutex` → `std.Io.Mutex`. The DSL's per-class registry in `src/js/class_runtime.zig` needs a 0.16-compatible mutex, which requires an `Io`. The `Io` is threaded in through the DSL's entry point rather than letting zapi carry global state: - `js.exportModule` gains an optional `.io = fn () std.Io` field in `options`. The generated `moduleInit` calls it after the user's `options.init` hook and passes the result down into `registerDecls` → `class_runtime.registerClass`. If `.io` is omitted, a DSL-local `init_single_threaded` fallback keeps zero-config modules compiling. - `registerClass` caches the resolved `Io` in per-`T` `state(T).io`; `getConstructor`, `materializeClassInstance`, and `cleanupHook` all read from this cache because they execute inside napi C callbacks that can't receive user-provided `Io`. - Normal paths (`registerClass`, `getConstructor`) use cancelable `try mutex.lock(io)` so caller cancellation can propagate. - `cleanupHook` (a napi C callback with a fixed `(*Entry)` signature that cannot return errors) uses `lockUncancelable` deliberately — unwinding on cancel would leak the entry and hand napi a dangling pointer. * `Env` gains a `fromRaw(raw_env)` constructor so the 19 napi C callback sites don't all hand-roll the `Env{ .env = ... }` literal. `Env` itself stays a pure `napi_env` wrapper — no `io` field. After this commit `zig build` succeeds, and `zig build test` runs the 42 regular tests green. `example_js_dsl` has a separate, pre-existing link issue in `zig build test` (undefined `_napi_wrap` and friends, resolved by Node at runtime when loading the `.node` file) — orthogonal to the 0.16 migration.
The merge with main brought in the js_dsl feature (ChainSafe#11) and a refactor of `src/root.zig` (napi symbols moved to `src/napi.zig`, re-exported via `pub usingnamespace`). All of that was written for 0.14.1 and hits several 0.16 breaking changes: * `b.modules.put(key, value)` → `b.modules.put(allocator, key, value)`. Two call sites in `build.zig` were missed by the previous update pass (the `napi` module and the `example_js_dsl` module). * `src/napi.zig` exported `pub const c = @import("c.zig")`, but every internal file accesses napi types through the inner `.c` namespace. Restore the `.c` on the import. * `pub usingnamespace` was removed in Zig 0.16. Replace it in `src/root.zig` with explicit `pub const foo = napi.foo;` re-exports. * `callconv(.C)` → `callconv(.c)` (6 NAPI callback shims in `src/js/wrap_class.zig` and `src/js/wrap_function.zig`). * `std.Thread.Mutex` → `std.Io.Mutex`. The DSL's per-class registry in `src/js/class_runtime.zig` needs a 0.16-compatible mutex, which requires an `Io`. The `Io` is threaded in through the DSL's entry point rather than letting zapi carry global state: - `js.exportModule` gains an optional `.io = fn () std.Io` field in `options`. The generated `moduleInit` calls it after the user's `options.init` hook and passes the result down into `registerDecls` → `class_runtime.registerClass`. If `.io` is omitted, a DSL-local `init_single_threaded` fallback keeps zero-config modules compiling. - `registerClass` caches the resolved `Io` in per-`T` `state(T).io`; `getConstructor`, `materializeClassInstance`, and `cleanupHook` all read from this cache because they execute inside napi C callbacks that can't receive user-provided `Io`. - Normal paths (`registerClass`, `getConstructor`) use cancelable `try mutex.lock(io)` so caller cancellation can propagate. - `cleanupHook` (a napi C callback with a fixed `(*Entry)` signature that cannot return errors) uses `lockUncancelable` deliberately — unwinding on cancel would leak the entry and hand napi a dangling pointer. * `Env` gains a `fromRaw(raw_env)` constructor so the 19 napi C callback sites don't all hand-roll the `Env{ .env = ... }` literal. `Env` itself stays a pure `napi_env` wrapper — no `io` field. After this commit `zig build` succeeds, and `zig build test` runs the 42 regular tests green. `example_js_dsl` has a separate, pre-existing link issue in `zig build test` (undefined `_napi_wrap` and friends, resolved by Node at runtime when loading the `.node` file) — orthogonal to the 0.16 migration.
The merge with main brought in the js_dsl feature (ChainSafe#11) and a refactor of `src/root.zig` (napi symbols moved to `src/napi.zig`, re-exported via `pub usingnamespace`). All of that was written for 0.14.1 and hits several 0.16 breaking changes: * `b.modules.put(key, value)` → `b.modules.put(allocator, key, value)`. Two call sites in `build.zig` were missed by the previous update pass (the `napi` module and the `example_js_dsl` module). * `src/napi.zig` exported `pub const c = @import("c.zig")`, but every internal file accesses napi types through the inner `.c` namespace. Restore the `.c` on the import. * `pub usingnamespace` was removed in Zig 0.16. Replace it in `src/root.zig` with explicit `pub const foo = napi.foo;` re-exports. * `callconv(.C)` → `callconv(.c)` (6 NAPI callback shims in `src/js/wrap_class.zig` and `src/js/wrap_function.zig`). * `std.Thread.Mutex` → `std.Io.Mutex`. The DSL's per-class registry in `src/js/class_runtime.zig` needs a 0.16-compatible mutex, which requires an `Io`. The `Io` is threaded in through the DSL's entry point rather than letting zapi carry global state: - `js.exportModule` gains an optional `.io = fn () std.Io` field in `options`. The generated `moduleInit` calls it after the user's `options.init` hook and passes the result down into `registerDecls` → `class_runtime.registerClass`. If `.io` is omitted, a DSL-local `init_single_threaded` fallback keeps zero-config modules compiling. - `registerClass` caches the resolved `Io` in per-`T` `state(T).io`; `getConstructor`, `materializeClassInstance`, and `cleanupHook` all read from this cache because they execute inside napi C callbacks that can't receive user-provided `Io`. - Normal paths (`registerClass`, `getConstructor`) use cancelable `try mutex.lock(io)` so caller cancellation can propagate. - `cleanupHook` (a napi C callback with a fixed `(*Entry)` signature that cannot return errors) uses `lockUncancelable` deliberately — unwinding on cancel would leak the entry and hand napi a dangling pointer. * `Env` gains a `fromRaw(raw_env)` constructor so the 19 napi C callback sites don't all hand-roll the `Env{ .env = ... }` literal. `Env` itself stays a pure `napi_env` wrapper — no `io` field. After this commit `zig build` succeeds, and `zig build test` runs the 42 regular tests green. `example_js_dsl` has a separate, pre-existing link issue in `zig build test` (undefined `_napi_wrap` and friends, resolved by Node at runtime when loading the `.node` file) — orthogonal to the 0.16 migration.
The merge with main brought in the js_dsl feature (ChainSafe#11) and a refactor of `src/root.zig` (napi symbols moved to `src/napi.zig`, re-exported via `pub usingnamespace`). All of that was written for 0.14.1 and hits several 0.16 breaking changes: * `b.modules.put(key, value)` → `b.modules.put(allocator, key, value)`. Two call sites in `build.zig` were missed by the previous update pass (the `napi` module and the `example_js_dsl` module). * `src/napi.zig` exported `pub const c = @import("c.zig")`, but every internal file accesses napi types through the inner `.c` namespace. Restore the `.c` on the import. * `pub usingnamespace` was removed in Zig 0.16. Replace it in `src/root.zig` with explicit `pub const foo = napi.foo;` re-exports. * `callconv(.C)` → `callconv(.c)` (6 NAPI callback shims in `src/js/wrap_class.zig` and `src/js/wrap_function.zig`). * `std.Thread.Mutex` → `std.Io.Mutex`. The DSL's per-class registry in `src/js/class_runtime.zig` needs a 0.16-compatible mutex, which requires an `Io`. The `Io` is threaded in through the DSL's entry point rather than letting zapi carry global state: - `js.exportModule` gains an optional `.io = fn () std.Io` field in `options`. The generated `moduleInit` calls it after the user's `options.init` hook and passes the result down into `registerDecls` → `class_runtime.registerClass`. If `.io` is omitted, a DSL-local `init_single_threaded` fallback keeps zero-config modules compiling. - `registerClass` caches the resolved `Io` in per-`T` `state(T).io`; `getConstructor`, `materializeClassInstance`, and `cleanupHook` all read from this cache because they execute inside napi C callbacks that can't receive user-provided `Io`. - Normal paths (`registerClass`, `getConstructor`) use cancelable `try mutex.lock(io)` so caller cancellation can propagate. - `cleanupHook` (a napi C callback with a fixed `(*Entry)` signature that cannot return errors) uses `lockUncancelable` deliberately — unwinding on cancel would leak the entry and hand napi a dangling pointer. * `Env` gains a `fromRaw(raw_env)` constructor so the 19 napi C callback sites don't all hand-roll the `Env{ .env = ... }` literal. `Env` itself stays a pure `napi_env` wrapper — no `io` field. After this commit `zig build` succeeds, and `zig build test` runs the 42 regular tests green. `example_js_dsl` has a separate, pre-existing link issue in `zig build test` (undefined `_napi_wrap` and friends, resolved by Node at runtime when loading the `.node` file) — orthogonal to the 0.16 migration.
Summary
This PR upgrades the JS DSL from marker-based class exports to a typed class metadata model and adds automatic JS instance
materialization for exported class returns.
Highlights
pub const js_meta = js.class(.{ ... })js.prop(.{ .get = ..., .set = ... })js.Valueas the escape hatch for dynamic/heterogeneous JS interopundefinedfor optional DSL argumentsu64values abovei64max inNumber.fromExamples
examples/js_dslto the newjs_metapatternNotes
build.zigis generated and was regenerated fromzbuild.zonmainVerification
zbuild syncpnpm test