Classes
Contents
Wrap a Zig struct as a JS class with napi.class. Methods become methods, the constructor returns a stateful instance, and the underlying memory is managed for you.
A counter
Rules
initcan return eitherTor!T. An error rejects the constructor call.Envis recognized by type. As a constructor first param or method second param, it does not consume a JS argument.- The instance is heap-allocated once during
newand reused across every method call. There is no per-call boxing. - The Zig allocation is freed automatically when the JS instance is collected, whether you define
deinitor not. Definedeinitonly if your struct holds resources you need to release (file handles, sockets, freed allocations from a long-lived allocator).
A class that allocates
If your class holds memory that outlives a single call (string fields, slices, references), use a long-lived allocator and free in deinit. The per-call arena will not work because the arena resets between calls.
init here uses smp_allocator for the field that lives across calls. say uses the per-call arena (env.allocator()) because the result string is consumed by the JS bridge before the function returns and does not need to outlive it.
The general rule: state that lives on the instance uses a long-lived allocator; scratch within a single method uses env.allocator().
Iterators
If your class follows Zig's iterator convention (a next method that takes only self, plus an optional Env, and returns an optional), instances automatically implement the JS iterator protocol. They work with for..of, spread, Array.from, and anything else that consumes an iterable. No extra code needed.
The generated .d.ts includes both views:
What qualifies as an iterator:
pub fn next(self: *Self) ?Item: the canonical shape.nextmay takeEnvafterselfand may return an error union (!?Item). A thrown error propagates to the consuming loop.nextmust take no other parameters. Anextwith extra arguments, or one returning a non-optional, is treated as a regular method and the class is not iterable.
Details worth knowing:
nextstays exposed as a regular JS method returningItem | null, exactly like any other method. The iterator protocol is layered on top.- Iteration state lives in the Zig instance, matching Zig semantics: if you
breakout of afor..ofloop, a second loop over the same instance continues where the first stopped rather than restarting. Construct a fresh instance to iterate from the start. - Each
[Symbol.iterator]()call returns an iterator object that keeps the instance alive, so the underlying Zig memory cannot be collected mid-iteration.
Why not just use pub const?
A pub const struct with pub fn declarations becomes a namespace: a static set of functions on a JS object. It has no this. napi.class is for stateful instances backed by a Zig struct, instantiated with new, with methods that take *Self.