use crate::type_map::NapiMapper;
use ahash::AHashSet;
use alef_codegen::builder::{ImplBuilder, RustFileBuilder, StructBuilder};
use alef_codegen::generators::{self, AsyncPattern, RustBindingConfig};
use alef_codegen::naming::to_node_name;
use alef_codegen::shared::{can_auto_delegate, function_params, partition_methods};
use alef_codegen::type_mapper::TypeMapper;
use alef_core::backend::{Backend, BuildConfig, Capabilities, GeneratedFile, PostBuildStep};
use alef_core::config::{AlefConfig, Language, resolve_output_dir};
use alef_core::ir::{ApiSurface, EnumDef, FunctionDef, MethodDef, ParamDef, TypeDef, TypeRef};
use std::path::PathBuf;
pub struct NapiBackend;
impl NapiBackend {
fn binding_config(core_import: &str) -> RustBindingConfig<'_> {
RustBindingConfig {
struct_attrs: &["napi"],
field_attrs: &[],
struct_derives: &["Clone"],
method_block_attr: Some("napi"),
constructor_attr: "#[napi(constructor)]",
static_attr: None,
function_attr: "#[napi]",
enum_attrs: &["napi(string_enum)"],
enum_derives: &["Clone"],
needs_signature: false,
signature_prefix: "",
signature_suffix: "",
core_import,
async_pattern: AsyncPattern::NapiNativeAsync,
has_serde: false,
type_name_prefix: "Js",
}
}
}
impl Backend for NapiBackend {
fn name(&self) -> &str {
"napi"
}
fn language(&self) -> Language {
Language::Node
}
fn capabilities(&self) -> Capabilities {
Capabilities {
supports_async: true,
supports_classes: true,
supports_enums: true,
supports_option: true,
supports_result: true,
..Capabilities::default()
}
}
fn generate_bindings(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
let mapper = NapiMapper;
let core_import = config.core_import();
let cfg = Self::binding_config(&core_import);
let mut builder = RustFileBuilder::new().with_generated_header();
builder.add_import("napi::*");
builder.add_import("napi_derive::napi");
for trait_path in generators::collect_trait_imports(api) {
builder.add_import(&trait_path);
}
let has_maps = api
.types
.iter()
.any(|t| t.fields.iter().any(|f| matches!(&f.ty, TypeRef::Map(_, _))))
|| api
.functions
.iter()
.any(|f| matches!(&f.return_type, TypeRef::Map(_, _)));
if has_maps {
builder.add_import("std::collections::HashMap");
}
let has_async =
api.functions.iter().any(|f| f.is_async) || api.types.iter().any(|t| t.methods.iter().any(|m| m.is_async));
if has_async {
builder.add_item(&gen_tokio_runtime());
}
let opaque_types: AHashSet<String> = api
.types
.iter()
.filter(|t| t.is_opaque)
.map(|t| t.name.clone())
.collect();
if !opaque_types.is_empty() {
builder.add_import("std::sync::Arc");
}
for typ in &api.types {
if typ.is_opaque {
builder.add_item(&alef_codegen::generators::gen_opaque_struct_prefixed(typ, &cfg, "Js"));
builder.add_item(&gen_opaque_struct_methods(typ, &mapper, &cfg, &opaque_types));
} else {
builder.add_item(&gen_struct(typ, &mapper));
if typ.has_default {
builder.add_item(&alef_codegen::generators::gen_struct_default_impl(typ, "Js"));
}
}
}
for enum_def in &api.enums {
builder.add_item(&gen_enum(enum_def));
}
for func in &api.functions {
let has_opaque_param = func.params.iter().any(|p| {
if let alef_core::ir::TypeRef::Named(n) = &p.ty {
opaque_types.contains(n)
} else {
false
}
});
if !has_opaque_param {
builder.add_item(&gen_function(func, &mapper, &cfg, &opaque_types));
}
}
let binding_to_core = alef_codegen::conversions::convertible_types(api);
let core_to_binding = alef_codegen::conversions::core_to_binding_convertible_types(api);
let napi_conv_config = alef_codegen::conversions::ConversionConfig {
type_name_prefix: "Js",
cast_large_ints_to_i64: true,
cast_f32_to_f64: true,
optionalize_defaults: true,
include_cfg_metadata: true,
..Default::default()
};
for typ in &api.types {
if alef_codegen::conversions::can_generate_conversion(typ, &binding_to_core) {
builder.add_item(&alef_codegen::conversions::gen_from_binding_to_core_cfg(
typ,
&core_import,
&napi_conv_config,
));
}
if alef_codegen::conversions::can_generate_conversion(typ, &core_to_binding) {
builder.add_item(&alef_codegen::conversions::gen_from_core_to_binding_cfg(
typ,
&core_import,
&opaque_types,
&napi_conv_config,
));
}
}
for e in &api.enums {
if alef_codegen::conversions::can_generate_enum_conversion(e) {
builder.add_item(&alef_codegen::conversions::gen_enum_from_binding_to_core_cfg(
e,
&core_import,
&napi_conv_config,
));
}
if alef_codegen::conversions::can_generate_enum_conversion_from_core(e) {
builder.add_item(&alef_codegen::conversions::gen_enum_from_core_to_binding_cfg(
e,
&core_import,
&napi_conv_config,
));
}
}
for error in &api.errors {
builder.add_item(&alef_codegen::error_gen::gen_napi_error_types(error));
builder.add_item(&alef_codegen::error_gen::gen_napi_error_converter(error, &core_import));
}
let _adapter_bodies = alef_adapters::build_adapter_bodies(config, Language::Node)?;
let content = builder.build();
let output_dir = resolve_output_dir(
config.output.node.as_ref(),
&config.crate_config.name,
"crates/{name}-node/src/",
);
Ok(vec![GeneratedFile {
path: PathBuf::from(&output_dir).join("lib.rs"),
content,
generated_header: false,
}])
}
fn generate_public_api(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
let mut type_exports = vec![];
let mut function_exports = vec![];
for typ in &api.types {
type_exports.push(format!("Js{}", typ.name));
}
for enum_def in &api.enums {
function_exports.push(format!("Js{}", enum_def.name));
}
for func in &api.functions {
let js_name = to_node_name(&func.name);
function_exports.push(js_name);
}
type_exports.sort();
function_exports.sort();
let mut lines = vec![
"// This file is auto-generated by alef. DO NOT EDIT.".to_string(),
"".to_string(),
];
let mut all_exports: Vec<String> = Vec::new();
for name in &function_exports {
all_exports.push(format!(" {name},"));
}
for name in &type_exports {
all_exports.push(format!(" type {name},"));
}
if !all_exports.is_empty() {
lines.push("export {".to_string());
lines.extend(all_exports);
lines.push(format!("}} from '{}';", config.node_package_name()));
}
let custom_mods = config.custom_modules.for_language(Language::Node);
for module_name in custom_mods {
lines.push(format!("export * from './{module_name}';"));
}
let content = lines.join("\n");
let output_path = PathBuf::from("packages/typescript/src/index.ts");
Ok(vec![GeneratedFile {
path: output_path,
content,
generated_header: false,
}])
}
fn build_config(&self) -> Option<BuildConfig> {
Some(BuildConfig {
tool: "napi",
crate_suffix: "-node",
depends_on_ffi: false,
post_build: vec![PostBuildStep::PatchFile {
path: "index.d.ts",
find: "export declare const enum",
replace: "export declare enum",
}],
})
}
}
fn gen_struct(typ: &TypeDef, mapper: &NapiMapper) -> String {
let mut struct_builder = StructBuilder::new(&format!("Js{}", typ.name));
struct_builder.add_attr("napi(object)");
struct_builder.add_derive("Clone");
for field in &typ.fields {
let mapped_type = mapper.map_type(&field.ty);
let field_type = if field.optional || typ.has_default {
format!("Option<{}>", mapped_type)
} else {
mapped_type
};
let js_name = to_node_name(&field.name);
let attrs = if js_name != field.name {
vec![format!("napi(js_name = \"{}\")", js_name)]
} else {
vec![]
};
struct_builder.add_field(&field.name, &field_type, attrs);
}
if typ.has_stripped_cfg_fields && typ.name == "ConversionResult" {
struct_builder.add_field("metadata", "Option<JsHtmlMetadata>", vec![]);
}
struct_builder.build()
}
fn gen_opaque_struct_methods(
typ: &TypeDef,
mapper: &NapiMapper,
cfg: &RustBindingConfig,
opaque_types: &AHashSet<String>,
) -> String {
let mut impl_builder = ImplBuilder::new(&format!("Js{}", typ.name));
impl_builder.add_attr("napi");
let (instance, statics) = partition_methods(&typ.methods);
for method in &instance {
impl_builder.add_method(&gen_opaque_instance_method(method, mapper, typ, cfg, opaque_types));
}
for method in &statics {
impl_builder.add_method(&gen_static_method(method, mapper, typ, cfg, opaque_types));
}
impl_builder.build()
}
fn gen_opaque_instance_method(
method: &MethodDef,
mapper: &NapiMapper,
typ: &TypeDef,
cfg: &RustBindingConfig,
opaque_types: &AHashSet<String>,
) -> String {
let params = function_params(&method.params, &|ty| mapper.map_type(ty));
let return_type = mapper.map_type(&method.return_type);
let return_annotation = mapper.wrap_return(&return_type, method.error_type.is_some());
let js_name = to_node_name(&method.name);
let js_name_attr = if js_name != method.name {
format!("(js_name = \"{}\")", js_name)
} else {
String::new()
};
let async_kw = if method.is_async { "async " } else { "" };
let type_name = &typ.name;
let is_owned_receiver = matches!(method.receiver.as_ref(), Some(alef_core::ir::ReceiverKind::Owned));
let call_args = napi_gen_call_args(&method.params, opaque_types);
let opaque_can_delegate = !method.sanitized
&& (!is_owned_receiver || typ.is_clone)
&& method
.params
.iter()
.all(|p| !p.sanitized && alef_codegen::shared::is_delegatable_param(&p.ty, opaque_types))
&& alef_codegen::shared::is_opaque_delegatable_type(&method.return_type);
let make_core_call = |method_name: &str| -> String {
if is_owned_receiver {
format!("(*self.inner).clone().{method_name}({call_args})")
} else {
format!("self.inner.{method_name}({call_args})")
}
};
let make_async_core_call = |method_name: &str| -> String { format!("inner.{method_name}({call_args})") };
let async_result_wrap = napi_wrap_return(
"result",
&method.return_type,
type_name,
opaque_types,
true,
method.returns_ref,
);
let body = if !opaque_can_delegate {
if cfg.has_serde
&& !method.sanitized
&& generators::has_named_params(&method.params, opaque_types)
&& method.error_type.is_some()
&& alef_codegen::shared::is_opaque_delegatable_type(&method.return_type)
{
let err_conv = ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))";
let serde_bindings =
generators::gen_serde_let_bindings(&method.params, opaque_types, cfg.core_import, err_conv, " ");
let serde_call_args = generators::gen_call_args_with_let_bindings(&method.params, opaque_types);
let core_call = format!("self.inner.{}({serde_call_args})", method.name);
if matches!(method.return_type, TypeRef::Unit) {
format!("{serde_bindings}{core_call}{err_conv}?;\n Ok(())")
} else {
let wrap = napi_wrap_return(
"result",
&method.return_type,
type_name,
opaque_types,
true,
method.returns_ref,
);
format!("{serde_bindings}let result = {core_call}{err_conv}?;\n Ok({wrap})")
}
} else {
generators::gen_unimplemented_body(
&method.return_type,
&format!("{type_name}.{}", method.name),
method.error_type.is_some(),
cfg,
&method.params,
)
}
} else if method.is_async {
let inner_clone_line = "let inner = self.inner.clone();\n ";
let core_call_str = make_async_core_call(&method.name);
generators::gen_async_body(
&core_call_str,
cfg,
method.error_type.is_some(),
&async_result_wrap,
true,
inner_clone_line,
matches!(method.return_type, TypeRef::Unit),
)
} else {
let core_call = make_core_call(&method.name);
if method.error_type.is_some() {
let err_conv = ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))";
if matches!(method.return_type, TypeRef::Unit) {
format!("{core_call}{err_conv}?;\n Ok(())")
} else {
let wrap = napi_wrap_return(
"result",
&method.return_type,
type_name,
opaque_types,
true,
method.returns_ref,
);
format!("let result = {core_call}{err_conv}?;\n Ok({wrap})")
}
} else {
napi_wrap_return(
&core_call,
&method.return_type,
type_name,
opaque_types,
true,
method.returns_ref,
)
}
};
let mut attrs = String::new();
if method.params.len() + 1 > 7 {
attrs.push_str("#[allow(clippy::too_many_arguments)]\n");
}
if method.error_type.is_some() {
attrs.push_str("#[allow(clippy::missing_errors_doc)]\n");
}
if generators::is_trait_method_name(&method.name) {
attrs.push_str("#[allow(clippy::should_implement_trait)]\n");
}
format!(
"{attrs}#[napi{js_name_attr}]\npub {async_kw}fn {}(&self, {params}) -> {return_annotation} {{\n \
{body}\n}}",
method.name
)
}
fn gen_static_method(
method: &MethodDef,
mapper: &NapiMapper,
typ: &TypeDef,
cfg: &RustBindingConfig,
opaque_types: &AHashSet<String>,
) -> String {
let params = function_params(&method.params, &|ty| mapper.map_type(ty));
let return_type = mapper.map_type(&method.return_type);
let return_annotation = mapper.wrap_return(&return_type, method.error_type.is_some());
let js_name = to_node_name(&method.name);
let js_name_attr = if js_name != method.name {
format!("(js_name = \"{}\")", js_name)
} else {
String::new()
};
let type_name = &typ.name;
let core_type_path = typ.rust_path.replace('-', "_");
let call_args = napi_gen_call_args(&method.params, opaque_types);
let can_delegate_static = can_auto_delegate(method, opaque_types);
let async_kw = if method.is_async { "async " } else { "" };
let body = if !can_delegate_static {
generators::gen_unimplemented_body(
&method.return_type,
&format!("{type_name}::{}", method.name),
method.error_type.is_some(),
cfg,
&method.params,
)
} else if method.is_async {
let core_call = format!("{core_type_path}::{}({call_args})", method.name);
let return_wrap = napi_wrap_return(
"result",
&method.return_type,
type_name,
opaque_types,
typ.is_opaque,
method.returns_ref,
);
generators::gen_async_body(
&core_call,
cfg,
method.error_type.is_some(),
&return_wrap,
false,
"",
matches!(method.return_type, TypeRef::Unit),
)
} else {
let core_call = format!("{core_type_path}::{}({call_args})", method.name);
if method.error_type.is_some() {
let err_conv = ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))";
let wrapped = napi_wrap_return(
"val",
&method.return_type,
type_name,
opaque_types,
typ.is_opaque,
method.returns_ref,
);
if wrapped == "val" {
format!("{core_call}{err_conv}")
} else {
format!("{core_call}.map(|val| {wrapped}){err_conv}")
}
} else {
napi_wrap_return(
&core_call,
&method.return_type,
type_name,
opaque_types,
typ.is_opaque,
method.returns_ref,
)
}
};
let mut attrs = String::new();
if method.params.len() > 7 {
attrs.push_str("#[allow(clippy::too_many_arguments)]\n");
}
if method.error_type.is_some() {
attrs.push_str("#[allow(clippy::missing_errors_doc)]\n");
}
if generators::is_trait_method_name(&method.name) {
attrs.push_str("#[allow(clippy::should_implement_trait)]\n");
}
format!(
"{attrs}#[napi{js_name_attr}]\npub {async_kw}fn {}({params}) -> {return_annotation} {{\n \
{body}\n}}",
method.name
)
}
fn gen_enum(enum_def: &EnumDef) -> String {
let mut lines = vec![
"#[napi(string_enum)]".to_string(),
"#[derive(Clone)]".to_string(),
format!("pub enum Js{} {{", enum_def.name),
];
for variant in &enum_def.variants {
lines.push(format!(" {},", variant.name));
}
lines.push("}".to_string());
if let Some(first) = enum_def.variants.first() {
lines.push(String::new());
lines.push(format!("impl Default for Js{} {{", enum_def.name));
lines.push(format!(" fn default() -> Self {{ Self::{} }}", first.name));
lines.push("}".to_string());
}
lines.join("\n")
}
fn gen_function(
func: &FunctionDef,
mapper: &NapiMapper,
cfg: &RustBindingConfig,
opaque_types: &AHashSet<String>,
) -> String {
let params = function_params(&func.params, &|ty| mapper.map_type(ty));
let return_type = mapper.map_type(&func.return_type);
let return_annotation = mapper.wrap_return(&return_type, func.error_type.is_some());
let js_name = to_node_name(&func.name);
let js_name_attr = if js_name != func.name {
format!("(js_name = \"{}\")", js_name)
} else {
String::new()
};
let core_import = cfg.core_import;
let core_fn_path = {
let path = func.rust_path.replace('-', "_");
if path.starts_with(core_import) {
path
} else {
format!("{core_import}::{}", func.name)
}
};
let use_let_bindings = generators::has_named_params(&func.params, opaque_types);
let call_args = if use_let_bindings {
generators::gen_call_args_with_let_bindings(&func.params, opaque_types)
} else {
napi_gen_call_args(&func.params, opaque_types)
};
let can_delegate_fn = alef_codegen::shared::can_auto_delegate_function(func, opaque_types);
let err_conv = ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))";
let async_kw = if func.is_async { "async " } else { "" };
let body = if !can_delegate_fn {
if cfg.has_serde && use_let_bindings && func.error_type.is_some() {
let serde_bindings =
generators::gen_serde_let_bindings(&func.params, opaque_types, core_import, err_conv, " ");
let core_call = format!("{core_fn_path}({call_args})");
if matches!(func.return_type, TypeRef::Unit) {
format!("{serde_bindings}{core_call}{err_conv}?;\n Ok(())")
} else {
let wrapped = napi_wrap_return_fn("val", &func.return_type, opaque_types, func.returns_ref);
if wrapped == "val" {
format!("{serde_bindings}{core_call}{err_conv}")
} else {
format!("{serde_bindings}{core_call}.map(|val| {wrapped}){err_conv}")
}
}
} else {
generators::gen_unimplemented_body(
&func.return_type,
&func.name,
func.error_type.is_some(),
cfg,
&func.params,
)
}
} else if func.is_async {
let core_call = format!("{core_fn_path}({call_args})");
let return_wrap = napi_wrap_return_fn("result", &func.return_type, opaque_types, func.returns_ref);
generators::gen_async_body(
&core_call,
cfg,
func.error_type.is_some(),
&return_wrap,
false,
"",
matches!(func.return_type, TypeRef::Unit),
)
} else {
let core_call = format!("{core_fn_path}({call_args})");
let let_bindings = if use_let_bindings {
generators::gen_named_let_bindings_pub(&func.params, opaque_types)
} else {
String::new()
};
if func.error_type.is_some() {
let wrapped = napi_wrap_return_fn("val", &func.return_type, opaque_types, func.returns_ref);
if wrapped == "val" {
format!("{let_bindings}{core_call}{err_conv}")
} else {
format!("{let_bindings}{core_call}.map(|val| {wrapped}){err_conv}")
}
} else {
format!(
"{let_bindings}{}",
napi_wrap_return_fn(&core_call, &func.return_type, opaque_types, func.returns_ref)
)
}
};
let mut attrs = String::new();
if func.params.len() > 7 {
attrs.push_str("#[allow(clippy::too_many_arguments)]\n");
}
if func.error_type.is_some() {
attrs.push_str("#[allow(clippy::missing_errors_doc)]\n");
}
format!(
"{attrs}#[napi{js_name_attr}]\npub {async_kw}fn {}({params}) -> {return_annotation} {{\n \
{body}\n}}",
func.name
)
}
fn napi_gen_call_args(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
params
.iter()
.map(|p| match &p.ty {
TypeRef::Primitive(prim) if needs_napi_cast(prim) => {
let core_ty = core_prim_str(prim);
if p.optional {
format!("{}.map(|v| v as {})", p.name, core_ty)
} else {
format!("{} as {}", p.name, core_ty)
}
}
TypeRef::Duration => {
if p.optional {
format!("{}.map(|v| std::time::Duration::from_secs(v as u64))", p.name)
} else {
format!("std::time::Duration::from_secs({} as u64)", p.name)
}
}
TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
if p.optional {
format!("{}.as_ref().map(|v| &v.inner)", p.name)
} else {
format!("&{}.inner", p.name)
}
}
TypeRef::Named(_) => {
if p.optional {
format!("{}.map(Into::into)", p.name)
} else {
format!("{}.into()", p.name)
}
}
TypeRef::String | TypeRef::Char => format!("&{}", p.name),
TypeRef::Path => format!("std::path::PathBuf::from({})", p.name),
TypeRef::Bytes => format!("&{}", p.name),
_ => p.name.clone(),
})
.collect::<Vec<_>>()
.join(", ")
}
fn napi_wrap_return(
expr: &str,
return_type: &TypeRef,
type_name: &str,
opaque_types: &AHashSet<String>,
self_is_opaque: bool,
returns_ref: bool,
) -> String {
match return_type {
TypeRef::Primitive(p) if needs_napi_cast(p) => {
format!("{expr} as i64")
}
TypeRef::Duration => format!("{expr}.as_secs() as i64"),
TypeRef::Named(n) if n == type_name && self_is_opaque => {
if returns_ref {
format!("Self {{ inner: Arc::new({expr}.clone()) }}")
} else {
format!("Self {{ inner: Arc::new({expr}) }}")
}
}
TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
if returns_ref {
format!("Js{n} {{ inner: Arc::new({expr}.clone()) }}")
} else {
format!("Js{n} {{ inner: Arc::new({expr}) }}")
}
}
TypeRef::Named(_) => {
if returns_ref {
format!("{expr}.clone().into()")
} else {
format!("{expr}.into()")
}
}
_ => generators::wrap_return(expr, return_type, type_name, opaque_types, self_is_opaque, returns_ref),
}
}
fn napi_wrap_return_fn(
expr: &str,
return_type: &TypeRef,
opaque_types: &AHashSet<String>,
returns_ref: bool,
) -> String {
match return_type {
TypeRef::Primitive(p) if needs_napi_cast(p) => {
format!("{expr} as i64")
}
TypeRef::Duration => format!("{expr}.as_secs() as i64"),
TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
if returns_ref {
format!("Js{n} {{ inner: Arc::new({expr}.clone()) }}")
} else {
format!("Js{n} {{ inner: Arc::new({expr}) }}")
}
}
TypeRef::Named(_) => {
if returns_ref {
format!("{expr}.clone().into()")
} else {
format!("{expr}.into()")
}
}
TypeRef::String | TypeRef::Char | TypeRef::Bytes => {
if returns_ref {
format!("{expr}.into()")
} else {
expr.to_string()
}
}
TypeRef::Path => format!("{expr}.to_string_lossy().to_string()"),
TypeRef::Json => format!("{expr}.to_string()"),
TypeRef::Optional(inner) => match inner.as_ref() {
TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
if returns_ref {
format!("{expr}.map(|v| Js{name} {{ inner: Arc::new(v.clone()) }})")
} else {
format!("{expr}.map(|v| Js{name} {{ inner: Arc::new(v) }})")
}
}
TypeRef::Named(_) => {
if returns_ref {
format!("{expr}.map(|v| v.clone().into())")
} else {
format!("{expr}.map(Into::into)")
}
}
TypeRef::Path => {
format!("{expr}.map(Into::into)")
}
TypeRef::String | TypeRef::Char | TypeRef::Bytes => {
if returns_ref {
format!("{expr}.map(Into::into)")
} else {
expr.to_string()
}
}
_ => expr.to_string(),
},
TypeRef::Vec(inner) => match inner.as_ref() {
TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
if returns_ref {
format!("{expr}.into_iter().map(|v| Js{name} {{ inner: Arc::new(v.clone()) }}).collect()")
} else {
format!("{expr}.into_iter().map(|v| Js{name} {{ inner: Arc::new(v) }}).collect()")
}
}
TypeRef::Named(_) => {
if returns_ref {
format!("{expr}.into_iter().map(|v| v.clone().into()).collect()")
} else {
format!("{expr}.into_iter().map(Into::into).collect()")
}
}
TypeRef::Path => {
format!("{expr}.into_iter().map(Into::into).collect()")
}
TypeRef::String | TypeRef::Char | TypeRef::Bytes => {
if returns_ref {
format!("{expr}.into_iter().map(Into::into).collect()")
} else {
expr.to_string()
}
}
_ => expr.to_string(),
},
_ => expr.to_string(),
}
}
fn needs_napi_cast(p: &alef_core::ir::PrimitiveType) -> bool {
matches!(
p,
alef_core::ir::PrimitiveType::U64 | alef_core::ir::PrimitiveType::Usize | alef_core::ir::PrimitiveType::Isize
)
}
fn core_prim_str(p: &alef_core::ir::PrimitiveType) -> &'static str {
match p {
alef_core::ir::PrimitiveType::U64 => "u64",
alef_core::ir::PrimitiveType::Usize => "usize",
alef_core::ir::PrimitiveType::Isize => "isize",
_ => unreachable!(),
}
}
fn gen_tokio_runtime() -> String {
"static WORKER_POOL: std::sync::LazyLock<tokio::runtime::Runtime> = std::sync::LazyLock::new(|| {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect(\"Failed to create Tokio runtime\")
});"
.to_string()
}