impl {{ wrapper_name }} {
/// Create a new bridge wrapping a NAPI Object.
///
/// Validates that the object provides all required methods.
pub fn new(env: napi::Env, js_obj: napi::bindgen_prelude::Object<'_>) -> napi::Result<Self> {
{%- for method in required_methods %}
if !js_obj.has_named_property("{{ method.name }}").unwrap_or(false)
&& !js_obj.has_named_property("{{ method.snake_case_name }}").unwrap_or(false) {
return Err(napi::Error::new(
napi::Status::GenericFailure,
format!("Object missing required method: {}", "{{ method.name }}")
));
}
{%- endfor %}
{%- if requires_plugin_name %}
if !js_obj.has_named_property("name").unwrap_or(false) {
return Err(napi::Error::new(
napi::Status::GenericFailure,
"Object missing required method: name".to_string()
));
}
{%- endif %}
// Cache the plugin name before wrapping in Reference.
// Try function form first, then string property.
let cached_name = js_obj
.get_named_property::<napi::bindgen_prelude::Function<(), String>>("name")
.and_then(|f| f.call(()))
.or_else(|_| js_obj.get_named_property::<String>("name"))
{%- if requires_plugin_name %}
.map_err(|e| napi::Error::new(
napi::Status::GenericFailure,
format!("Object missing required method: name ({e})")
))?;
{%- else %}
.unwrap_or_default();
{%- endif %}
// Wrap the JS object in a Reference to safely pin it to the V8 heap
// across method calls. References are tied to the Node.js runtime
// and cannot be garbage collected while held.
let inner = env.create_reference(js_obj)?;
Ok(Self {
inner,
cached_name,
})
}
/// Extract napi::Env from the stored Reference.
fn env(&self) -> napi::Result<napi::Env> {
// get_napi_env() is available on napi::Reference
self.inner.get_napi_env()
}
}
// SAFETY: The bridge holds an napi::Reference which is thread-safe within
// the Node.js runtime. All access occurs on the event loop thread.
// Send+Sync are required by the Plugin trait.
unsafe impl Send for {{ wrapper_name }} {}
unsafe impl Sync for {{ wrapper_name }} {}