Skip to content

feat(napi): convert composite types in createCallback - #84

Draft
nazarhussain wants to merge 1 commit into
mainfrom
feat/to-from-value-composite
Draft

nazarhussain wants to merge 1 commit into
mainfrom
feat/to-from-value-composite

Conversation

@nazarhussain

Copy link
Copy Markdown
Contributor

Motivation

napi.createCallback could only convert bool, integers, floats and Buffer byte slices. A function taking a string, an optional, a struct or an array needed a hand-written callback. This brings over the reflection-based reader/writer idea from the early prototype at nazarhussain/zapi, rewritten against the current Value/Env API with checked conversions.

Changes

fromValue / toValue in src/to_from_value.zig now convert recursively:

Zig type JavaScript
?T null/undefined ↔ null
enum(Int) integer tag; unknown tags → InvalidArg
struct { ... } object with matching field names (non-object → ObjectExpected)
tuple, [N]T array of exactly that length (otherwise ArrayExpected / InvalidArg)
[]T, []const T array
[]const u8 argument with .auto accepts a string or a Buffer (previously a runtime GenericFailure)
  • The arg/return hint passes down to nested elements, so .returns = .string makes a struct's []const u8 fields come back as strings.
  • createCallback gives fromValue a per-call ArenaAllocator for strings and slices. The arena is freed after the return value has been converted, so returning a borrowed argument slice is safe.
  • An omitted trailing argument for an optional parameter is now null instead of throwing ERR_INVALID_ARG_COUNT. This matches the DSL's convertArgWithOptional.
  • fromValue is internal (not re-exported), so adding the allocator parameter is not a public API change.

Behaviour changes to note

  • Before this PR, returning an optional, enum, struct, tuple, [N]T or non-byte slice failed at runtime with GenericFailure. Those now convert.
  • A [N]u8 returned by value now becomes a JS array. *[N]u8 and []u8 still become Buffers.
  • Composite conversion makes one N-API call per field or element, so it is not meant for large numeric data. Typed arrays and Buffers remain the zero-copy path.

Tests

  • New hello_world example functions (greet, scalePoint, sum, swap, reverse3, nextColor) with vitest cases, including rejection paths: wrong type, wrong tuple/array length, unknown enum tag, non-object struct argument.
  • zig build test:zapi, pnpm build:zig and pnpm test:js (150 passed, 1 skipped) all pass locally. zig fmt --check is clean.

🤖 Generated with Claude Code

fromValue/toValue now handle optionals, enums, structs, tuples, fixed
arrays and slices recursively, and read string arguments into a
per-call arena that is freed when the callback returns. Omitted
trailing optional arguments become null.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

1 participant