Skip to content

refactor(node): move rest bag adaptation into the napi binding#536

Merged
DorianZheng merged 3 commits into
mainfrom
refactor/node-rest-binding-options-class
May 16, 2026
Merged

refactor(node): move rest bag adaptation into the napi binding#536
DorianZheng merged 3 commits into
mainfrom
refactor/node-rest-binding-options-class

Conversation

@DorianZheng

Copy link
Copy Markdown
Member

Summary

Follow-up to #535 (the Proxy hotfix). Research (napi-rs docs + @node-rs/*, swc, Prisma, lightningcss, and our own Python SDK) confirmed the Proxy is a non-idiomatic workaround: option-bag adaptation belongs in Rust. The Python SDK already does this for the identical concept (sdks/python/src/options.rs:858 PyBoxliteRestOptions + impl From, consumed by runtime.rs:50).

  • Add #[napi] JsBoxliteRestOptions + into_core() in sdks/node/src/options.rs — the napi twin of Python's From.
  • Retarget JsBoxlite::rest to take &JsBoxliteRestOptions (was the lone positional binding method).
  • Delete the JS Proxy in lib/index.ts; replace with a plain ES subclass that restates the bag-taking rest and inherits new/withDefaultConfig/initDefault.
  • lib/options.ts stays pure-TS → public TypeScript API byte-identical; new BoxliteRestOptions({...}) and credential identity preserved.

Stacked on fix/node-rest-binding-readonly (#535); supersedes its Proxy. Merge #535 first (or rebase onto main after).

napi-rs #1164 (class subclassing) — verified, not a risk here

Added integration coverage proving inherited construction (new JsBoxlite({homeDir}) via super()) and the inherited withDefaultConfig static both work. No fallback needed.

Test plan

  • make dev:node — Rust compiles, napi bindings regenerated, tsc clean
  • Rust unit test rest_options_into_core_maps_fields (cargo test -p boxlite-node) passes
  • make test:integration:node: 9 passed / 1 skipped, 46 passed / 17 skipped, exit 0
  • Previously-failing suites (credential, images, snapshot-clone-export) + rest-binding pass; no import-time TypeError
  • pre-push make test green on push

napi-rs registers class statics as non-writable AND non-configurable.
`lib/index.ts` reassigned `JsBoxlite.rest` to adapt the native
positional `rest(url, credential?, prefix?)` to the public
`BoxliteRestOptions` bag, which threw `TypeError: Cannot assign to read
only property 'rest'` at module import — breaking every consumer of the
SDK (3 integration suites failed on import).

A Proxy over the native class is also rejected: a `get` trap may not
substitute a non-configurable, non-writable own data property. So the
Proxy target is a fresh function that owns no such property; `rest`
resolves to the bag adapter while construction and every other static
forward to the native class untouched.

Adds rest-binding.integration.test.ts covering import + bag adaptation.
The native `rest` was the lone positional binding method while every
other option is a struct; `lib/index.ts` bridged the public
`BoxliteRestOptions` bag to it by wrapping the native class in a JS
Proxy (PR #535 hotfix). napi-rs idiom — and our own Python SDK
(`PyBoxliteRestOptions` + `impl From`) — is to shape option bags in
Rust, not reshape native objects in JS.

Add a `#[napi]` `JsBoxliteRestOptions` class with `into_core()` (the
napi twin of Python's `From`), retarget `JsBoxlite::rest` to take it,
and replace the Proxy with a plain ES subclass that restates the
bag-taking `rest` and inherits `new`/`withDefaultConfig`/`initDefault`.
The public TypeScript API is byte-identical; `lib/options.ts` stays
pure-TS (preserves the object-bag ctor + credential identity).

Verified napi-rs class subclassing works here (napi-rs #1164 risk did
not materialize): added integration coverage for inherited construct +
static paths, plus a Rust unit test for `into_core`.
@DorianZheng DorianZheng changed the base branch from fix/node-rest-binding-readonly to main May 16, 2026 03:49
@DorianZheng DorianZheng reopened this May 16, 2026
CI flagged clippy::wrong_self_convention on `into_core(&self)` (an
`into_*` method must take self by value) plus a rustfmt diff. Replace
the inherent method with `impl From<&JsBoxliteRestOptions> for
BoxliteRestOptions` — borrowed source since napi passes class args by
reference — which also tracks Python's `impl From` more closely. Apply
rustfmt.
@DorianZheng DorianZheng merged commit 170d926 into main May 16, 2026
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant