1use crate::type_map::NapiMapper;
2use ahash::AHashSet;
3use alef_codegen::builder::{ImplBuilder, RustFileBuilder, StructBuilder};
4use alef_codegen::generators::{self, AsyncPattern, RustBindingConfig};
5use alef_codegen::naming::to_node_name;
6use alef_codegen::shared::{can_auto_delegate, function_params, partition_methods};
7use alef_codegen::type_mapper::TypeMapper;
8use alef_core::backend::{Backend, BuildConfig, Capabilities, GeneratedFile, PostBuildStep};
9use alef_core::config::{AlefConfig, Language, resolve_output_dir};
10use alef_core::ir::{ApiSurface, EnumDef, FunctionDef, MethodDef, ParamDef, TypeDef, TypeRef};
11use std::path::PathBuf;
12
13pub struct NapiBackend;
14
15impl NapiBackend {
16 fn binding_config(core_import: &str) -> RustBindingConfig<'_> {
17 RustBindingConfig {
18 struct_attrs: &["napi"],
19 field_attrs: &[],
20 struct_derives: &["Clone"],
21 method_block_attr: Some("napi"),
22 constructor_attr: "#[napi(constructor)]",
23 static_attr: None,
24 function_attr: "#[napi]",
25 enum_attrs: &["napi(string_enum)"],
26 enum_derives: &["Clone"],
27 needs_signature: false,
28 signature_prefix: "",
29 signature_suffix: "",
30 core_import,
31 async_pattern: AsyncPattern::NapiNativeAsync,
32 has_serde: false,
34 type_name_prefix: "Js",
35 }
36 }
37}
38
39impl Backend for NapiBackend {
40 fn name(&self) -> &str {
41 "napi"
42 }
43
44 fn language(&self) -> Language {
45 Language::Node
46 }
47
48 fn capabilities(&self) -> Capabilities {
49 Capabilities {
50 supports_async: true,
51 supports_classes: true,
52 supports_enums: true,
53 supports_option: true,
54 supports_result: true,
55 ..Capabilities::default()
56 }
57 }
58
59 fn generate_bindings(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
60 let mapper = NapiMapper;
61 let core_import = config.core_import();
62 let cfg = Self::binding_config(&core_import);
63
64 let mut builder = RustFileBuilder::new().with_generated_header();
65 builder.add_import("napi::*");
66 builder.add_import("napi_derive::napi");
67
68 for trait_path in generators::collect_trait_imports(api) {
70 builder.add_import(&trait_path);
71 }
72
73 let has_maps = api
75 .types
76 .iter()
77 .any(|t| t.fields.iter().any(|f| matches!(&f.ty, TypeRef::Map(_, _))))
78 || api
79 .functions
80 .iter()
81 .any(|f| matches!(&f.return_type, TypeRef::Map(_, _)));
82 if has_maps {
83 builder.add_import("std::collections::HashMap");
84 }
85
86 let has_async =
91 api.functions.iter().any(|f| f.is_async) || api.types.iter().any(|t| t.methods.iter().any(|m| m.is_async));
92
93 if has_async {
94 builder.add_item(&gen_tokio_runtime());
95 }
96
97 let opaque_types: AHashSet<String> = api
99 .types
100 .iter()
101 .filter(|t| t.is_opaque)
102 .map(|t| t.name.clone())
103 .collect();
104 if !opaque_types.is_empty() {
105 builder.add_import("std::sync::Arc");
106 }
107
108 for typ in &api.types {
112 if typ.is_opaque {
113 builder.add_item(&alef_codegen::generators::gen_opaque_struct_prefixed(typ, &cfg, "Js"));
114 builder.add_item(&gen_opaque_struct_methods(typ, &mapper, &cfg, &opaque_types));
115 } else {
116 builder.add_item(&gen_struct(typ, &mapper));
120 }
121 }
122
123 for enum_def in &api.enums {
124 builder.add_item(&gen_enum(enum_def));
125 }
126
127 for func in &api.functions {
128 let has_opaque_param = func.params.iter().any(|p| {
131 if let alef_core::ir::TypeRef::Named(n) = &p.ty {
132 opaque_types.contains(n)
133 } else {
134 false
135 }
136 });
137 if !has_opaque_param {
138 builder.add_item(&gen_function(func, &mapper, &cfg, &opaque_types));
139 }
140 }
141
142 let binding_to_core = alef_codegen::conversions::convertible_types(api);
143 let core_to_binding = alef_codegen::conversions::core_to_binding_convertible_types(api);
144 let napi_conv_config = alef_codegen::conversions::ConversionConfig {
145 type_name_prefix: "Js",
146 cast_large_ints_to_i64: true,
147 cast_f32_to_f64: true,
148 optionalize_defaults: true,
152 include_cfg_metadata: true,
153 ..Default::default()
154 };
155 for typ in &api.types {
157 if alef_codegen::conversions::can_generate_conversion(typ, &binding_to_core) {
158 builder.add_item(&alef_codegen::conversions::gen_from_binding_to_core_cfg(
159 typ,
160 &core_import,
161 &napi_conv_config,
162 ));
163 }
164 if alef_codegen::conversions::can_generate_conversion(typ, &core_to_binding) {
165 builder.add_item(&alef_codegen::conversions::gen_from_core_to_binding_cfg(
166 typ,
167 &core_import,
168 &opaque_types,
169 &napi_conv_config,
170 ));
171 }
172 }
173 for e in &api.enums {
174 if alef_codegen::conversions::can_generate_enum_conversion(e) {
175 builder.add_item(&alef_codegen::conversions::gen_enum_from_binding_to_core_cfg(
176 e,
177 &core_import,
178 &napi_conv_config,
179 ));
180 }
181 if alef_codegen::conversions::can_generate_enum_conversion_from_core(e) {
182 builder.add_item(&alef_codegen::conversions::gen_enum_from_core_to_binding_cfg(
183 e,
184 &core_import,
185 &napi_conv_config,
186 ));
187 }
188 }
189
190 for error in &api.errors {
192 builder.add_item(&alef_codegen::error_gen::gen_napi_error_types(error));
193 builder.add_item(&alef_codegen::error_gen::gen_napi_error_converter(error, &core_import));
194 }
195
196 let _adapter_bodies = alef_adapters::build_adapter_bodies(config, Language::Node)?;
198
199 let content = builder.build();
200
201 let output_dir = resolve_output_dir(
202 config.output.node.as_ref(),
203 &config.crate_config.name,
204 "crates/{name}-node/src/",
205 );
206
207 Ok(vec![GeneratedFile {
208 path: PathBuf::from(&output_dir).join("lib.rs"),
209 content,
210 generated_header: false,
211 }])
212 }
213
214 fn generate_public_api(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
215 let mut type_exports = vec![];
217 let mut function_exports = vec![];
218
219 for typ in &api.types {
221 type_exports.push(format!("Js{}", typ.name));
222 }
223
224 for enum_def in &api.enums {
228 function_exports.push(format!("Js{}", enum_def.name));
229 }
230
231 for func in &api.functions {
236 let js_name = to_node_name(&func.name);
238 function_exports.push(js_name);
239 }
240
241 type_exports.sort();
243 function_exports.sort();
244
245 let mut lines = vec![
248 "// This file is auto-generated by alef. DO NOT EDIT.".to_string(),
249 "".to_string(),
250 ];
251
252 let mut all_exports: Vec<String> = Vec::new();
254 for name in &function_exports {
255 all_exports.push(format!(" {name},"));
256 }
257 for name in &type_exports {
258 all_exports.push(format!(" type {name},"));
259 }
260 if !all_exports.is_empty() {
261 lines.push("export {".to_string());
262 lines.extend(all_exports);
263 lines.push(format!("}} from '{}';", config.node_package_name()));
264 }
265
266 let custom_mods = config.custom_modules.for_language(Language::Node);
268 for module_name in custom_mods {
269 lines.push(format!("export * from './{module_name}';"));
270 }
271
272 let content = lines.join("\n");
273
274 let output_path = PathBuf::from("packages/typescript/src/index.ts");
276
277 Ok(vec![GeneratedFile {
278 path: output_path,
279 content,
280 generated_header: false,
281 }])
282 }
283
284 fn build_config(&self) -> Option<BuildConfig> {
285 Some(BuildConfig {
286 tool: "napi",
287 crate_suffix: "-node",
288 depends_on_ffi: false,
289 post_build: vec![PostBuildStep::PatchFile {
290 path: "index.d.ts",
291 find: "export declare const enum",
292 replace: "export declare enum",
293 }],
294 })
295 }
296}
297
298fn gen_struct(typ: &TypeDef, mapper: &NapiMapper) -> String {
300 let mut struct_builder = StructBuilder::new(&format!("Js{}", typ.name));
301 struct_builder.add_attr("napi(object)");
303 struct_builder.add_derive("Clone");
304 if typ.has_default {
306 struct_builder.add_derive("Default");
307 }
308
309 for field in &typ.fields {
310 let mapped_type = mapper.map_type(&field.ty);
311 let field_type = if field.optional || typ.has_default {
314 format!("Option<{}>", mapped_type)
315 } else {
316 mapped_type
317 };
318 let js_name = to_node_name(&field.name);
319 let attrs = if js_name != field.name {
320 vec![format!("napi(js_name = \"{}\")", js_name)]
321 } else {
322 vec![]
323 };
324 struct_builder.add_field(&field.name, &field_type, attrs);
325 }
326
327 if typ.has_stripped_cfg_fields && typ.name == "ConversionResult" {
332 struct_builder.add_field("metadata", "Option<JsHtmlMetadata>", vec![]);
334 }
335
336 struct_builder.build()
337}
338
339fn gen_opaque_struct_methods(
341 typ: &TypeDef,
342 mapper: &NapiMapper,
343 cfg: &RustBindingConfig,
344 opaque_types: &AHashSet<String>,
345) -> String {
346 let mut impl_builder = ImplBuilder::new(&format!("Js{}", typ.name));
347 impl_builder.add_attr("napi");
348
349 let (instance, statics) = partition_methods(&typ.methods);
350
351 for method in &instance {
352 impl_builder.add_method(&gen_opaque_instance_method(method, mapper, typ, cfg, opaque_types));
353 }
354 for method in &statics {
355 impl_builder.add_method(&gen_static_method(method, mapper, typ, cfg, opaque_types));
356 }
357
358 impl_builder.build()
359}
360
361fn gen_opaque_instance_method(
363 method: &MethodDef,
364 mapper: &NapiMapper,
365 typ: &TypeDef,
366 cfg: &RustBindingConfig,
367 opaque_types: &AHashSet<String>,
368) -> String {
369 let params = function_params(&method.params, &|ty| mapper.map_type(ty));
370 let return_type = mapper.map_type(&method.return_type);
371 let return_annotation = mapper.wrap_return(&return_type, method.error_type.is_some());
372
373 let js_name = to_node_name(&method.name);
374 let js_name_attr = if js_name != method.name {
375 format!("(js_name = \"{}\")", js_name)
376 } else {
377 String::new()
378 };
379
380 let async_kw = if method.is_async { "async " } else { "" };
381
382 let type_name = &typ.name;
383 let is_owned_receiver = matches!(method.receiver.as_ref(), Some(alef_core::ir::ReceiverKind::Owned));
384 let call_args = napi_gen_call_args(&method.params, opaque_types);
385
386 let opaque_can_delegate = !method.sanitized
388 && (!is_owned_receiver || typ.is_clone)
389 && method
390 .params
391 .iter()
392 .all(|p| !p.sanitized && alef_codegen::shared::is_delegatable_param(&p.ty, opaque_types))
393 && alef_codegen::shared::is_opaque_delegatable_type(&method.return_type);
394
395 let make_core_call = |method_name: &str| -> String {
396 if is_owned_receiver {
397 format!("(*self.inner).clone().{method_name}({call_args})")
398 } else {
399 format!("self.inner.{method_name}({call_args})")
400 }
401 };
402
403 let make_async_core_call = |method_name: &str| -> String { format!("inner.{method_name}({call_args})") };
404
405 let async_result_wrap = napi_wrap_return(
406 "result",
407 &method.return_type,
408 type_name,
409 opaque_types,
410 true,
411 method.returns_ref,
412 );
413
414 let body = if !opaque_can_delegate {
415 if cfg.has_serde
417 && !method.sanitized
418 && generators::has_named_params(&method.params, opaque_types)
419 && method.error_type.is_some()
420 && alef_codegen::shared::is_opaque_delegatable_type(&method.return_type)
421 {
422 let err_conv = ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))";
423 let serde_bindings =
424 generators::gen_serde_let_bindings(&method.params, opaque_types, cfg.core_import, err_conv, " ");
425 let serde_call_args = generators::gen_call_args_with_let_bindings(&method.params, opaque_types);
426 let core_call = format!("self.inner.{}({serde_call_args})", method.name);
427 if matches!(method.return_type, TypeRef::Unit) {
428 format!("{serde_bindings}{core_call}{err_conv}?;\n Ok(())")
429 } else {
430 let wrap = napi_wrap_return(
431 "result",
432 &method.return_type,
433 type_name,
434 opaque_types,
435 true,
436 method.returns_ref,
437 );
438 format!("{serde_bindings}let result = {core_call}{err_conv}?;\n Ok({wrap})")
439 }
440 } else {
441 generators::gen_unimplemented_body(
442 &method.return_type,
443 &format!("{type_name}.{}", method.name),
444 method.error_type.is_some(),
445 cfg,
446 &method.params,
447 )
448 }
449 } else if method.is_async {
450 let inner_clone_line = "let inner = self.inner.clone();\n ";
451 let core_call_str = make_async_core_call(&method.name);
452 generators::gen_async_body(
453 &core_call_str,
454 cfg,
455 method.error_type.is_some(),
456 &async_result_wrap,
457 true,
458 inner_clone_line,
459 matches!(method.return_type, TypeRef::Unit),
460 )
461 } else {
462 let core_call = make_core_call(&method.name);
463 if method.error_type.is_some() {
464 let err_conv = ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))";
465 if matches!(method.return_type, TypeRef::Unit) {
466 format!("{core_call}{err_conv}?;\n Ok(())")
467 } else {
468 let wrap = napi_wrap_return(
469 "result",
470 &method.return_type,
471 type_name,
472 opaque_types,
473 true,
474 method.returns_ref,
475 );
476 format!("let result = {core_call}{err_conv}?;\n Ok({wrap})")
477 }
478 } else {
479 napi_wrap_return(
480 &core_call,
481 &method.return_type,
482 type_name,
483 opaque_types,
484 true,
485 method.returns_ref,
486 )
487 }
488 };
489
490 let mut attrs = String::new();
491 if method.params.len() + 1 > 7 {
493 attrs.push_str("#[allow(clippy::too_many_arguments)]\n");
494 }
495 if method.error_type.is_some() {
497 attrs.push_str("#[allow(clippy::missing_errors_doc)]\n");
498 }
499 if generators::is_trait_method_name(&method.name) {
501 attrs.push_str("#[allow(clippy::should_implement_trait)]\n");
502 }
503 format!(
504 "{attrs}#[napi{js_name_attr}]\npub {async_kw}fn {}(&self, {params}) -> {return_annotation} {{\n \
505 {body}\n}}",
506 method.name
507 )
508}
509
510fn gen_static_method(
512 method: &MethodDef,
513 mapper: &NapiMapper,
514 typ: &TypeDef,
515 cfg: &RustBindingConfig,
516 opaque_types: &AHashSet<String>,
517) -> String {
518 let params = function_params(&method.params, &|ty| mapper.map_type(ty));
519 let return_type = mapper.map_type(&method.return_type);
520 let return_annotation = mapper.wrap_return(&return_type, method.error_type.is_some());
521
522 let js_name = to_node_name(&method.name);
523 let js_name_attr = if js_name != method.name {
524 format!("(js_name = \"{}\")", js_name)
525 } else {
526 String::new()
527 };
528
529 let type_name = &typ.name;
530 let core_type_path = typ.rust_path.replace('-', "_");
531 let call_args = napi_gen_call_args(&method.params, opaque_types);
532 let can_delegate_static = can_auto_delegate(method, opaque_types);
533
534 let async_kw = if method.is_async { "async " } else { "" };
535
536 let body = if !can_delegate_static {
537 generators::gen_unimplemented_body(
538 &method.return_type,
539 &format!("{type_name}::{}", method.name),
540 method.error_type.is_some(),
541 cfg,
542 &method.params,
543 )
544 } else if method.is_async {
545 let core_call = format!("{core_type_path}::{}({call_args})", method.name);
546 let return_wrap = napi_wrap_return(
547 "result",
548 &method.return_type,
549 type_name,
550 opaque_types,
551 typ.is_opaque,
552 method.returns_ref,
553 );
554 generators::gen_async_body(
555 &core_call,
556 cfg,
557 method.error_type.is_some(),
558 &return_wrap,
559 false,
560 "",
561 matches!(method.return_type, TypeRef::Unit),
562 )
563 } else {
564 let core_call = format!("{core_type_path}::{}({call_args})", method.name);
565 if method.error_type.is_some() {
566 let err_conv = ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))";
567 let wrapped = napi_wrap_return(
568 "val",
569 &method.return_type,
570 type_name,
571 opaque_types,
572 typ.is_opaque,
573 method.returns_ref,
574 );
575 if wrapped == "val" {
576 format!("{core_call}{err_conv}")
577 } else {
578 format!("{core_call}.map(|val| {wrapped}){err_conv}")
579 }
580 } else {
581 napi_wrap_return(
582 &core_call,
583 &method.return_type,
584 type_name,
585 opaque_types,
586 typ.is_opaque,
587 method.returns_ref,
588 )
589 }
590 };
591
592 let mut attrs = String::new();
593 if method.params.len() > 7 {
595 attrs.push_str("#[allow(clippy::too_many_arguments)]\n");
596 }
597 if method.error_type.is_some() {
599 attrs.push_str("#[allow(clippy::missing_errors_doc)]\n");
600 }
601 if generators::is_trait_method_name(&method.name) {
603 attrs.push_str("#[allow(clippy::should_implement_trait)]\n");
604 }
605 format!(
606 "{attrs}#[napi{js_name_attr}]\npub {async_kw}fn {}({params}) -> {return_annotation} {{\n \
607 {body}\n}}",
608 method.name
609 )
610}
611
612fn gen_enum(enum_def: &EnumDef) -> String {
614 let mut lines = vec![
615 "#[napi(string_enum)]".to_string(),
616 "#[derive(Clone)]".to_string(),
617 format!("pub enum Js{} {{", enum_def.name),
618 ];
619
620 for variant in &enum_def.variants {
621 lines.push(format!(" {},", variant.name));
622 }
623
624 lines.push("}".to_string());
625
626 if let Some(first) = enum_def.variants.first() {
628 lines.push(String::new());
629 lines.push("#[allow(clippy::derivable_impls)]".to_string());
630 lines.push(format!("impl Default for Js{} {{", enum_def.name));
631 lines.push(format!(" fn default() -> Self {{ Self::{} }}", first.name));
632 lines.push("}".to_string());
633 }
634
635 lines.join("\n")
636}
637
638fn gen_function(
640 func: &FunctionDef,
641 mapper: &NapiMapper,
642 cfg: &RustBindingConfig,
643 opaque_types: &AHashSet<String>,
644) -> String {
645 let params = function_params(&func.params, &|ty| mapper.map_type(ty));
646 let return_type = mapper.map_type(&func.return_type);
647 let return_annotation = mapper.wrap_return(&return_type, func.error_type.is_some());
648
649 let js_name = to_node_name(&func.name);
650 let js_name_attr = if js_name != func.name {
651 format!("(js_name = \"{}\")", js_name)
652 } else {
653 String::new()
654 };
655
656 let core_import = cfg.core_import;
657 let core_fn_path = {
658 let path = func.rust_path.replace('-', "_");
659 if path.starts_with(core_import) {
660 path
661 } else {
662 format!("{core_import}::{}", func.name)
663 }
664 };
665
666 let use_let_bindings = generators::has_named_params(&func.params, opaque_types);
668 let call_args = if use_let_bindings {
669 generators::gen_call_args_with_let_bindings(&func.params, opaque_types)
670 } else {
671 napi_gen_call_args(&func.params, opaque_types)
672 };
673
674 let can_delegate_fn = alef_codegen::shared::can_auto_delegate_function(func, opaque_types);
675
676 let err_conv = ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))";
677
678 let async_kw = if func.is_async { "async " } else { "" };
679
680 let body = if !can_delegate_fn {
681 if cfg.has_serde && use_let_bindings && func.error_type.is_some() {
683 let serde_bindings =
684 generators::gen_serde_let_bindings(&func.params, opaque_types, core_import, err_conv, " ");
685 let core_call = format!("{core_fn_path}({call_args})");
686
687 if matches!(func.return_type, TypeRef::Unit) {
688 format!("{serde_bindings}{core_call}{err_conv}?;\n Ok(())")
689 } else {
690 let wrapped = napi_wrap_return_fn("val", &func.return_type, opaque_types, func.returns_ref);
691 if wrapped == "val" {
692 format!("{serde_bindings}{core_call}{err_conv}")
693 } else {
694 format!("{serde_bindings}{core_call}.map(|val| {wrapped}){err_conv}")
695 }
696 }
697 } else {
698 generators::gen_unimplemented_body(
699 &func.return_type,
700 &func.name,
701 func.error_type.is_some(),
702 cfg,
703 &func.params,
704 )
705 }
706 } else if func.is_async {
707 let core_call = format!("{core_fn_path}({call_args})");
708 let return_wrap = napi_wrap_return_fn("result", &func.return_type, opaque_types, func.returns_ref);
709 generators::gen_async_body(
710 &core_call,
711 cfg,
712 func.error_type.is_some(),
713 &return_wrap,
714 false,
715 "",
716 matches!(func.return_type, TypeRef::Unit),
717 )
718 } else {
719 let core_call = format!("{core_fn_path}({call_args})");
720 let let_bindings = if use_let_bindings {
722 generators::gen_named_let_bindings_pub(&func.params, opaque_types)
723 } else {
724 String::new()
725 };
726
727 if func.error_type.is_some() {
728 let wrapped = napi_wrap_return_fn("val", &func.return_type, opaque_types, func.returns_ref);
729 if wrapped == "val" {
730 format!("{let_bindings}{core_call}{err_conv}")
731 } else {
732 format!("{let_bindings}{core_call}.map(|val| {wrapped}){err_conv}")
733 }
734 } else {
735 format!(
736 "{let_bindings}{}",
737 napi_wrap_return_fn(&core_call, &func.return_type, opaque_types, func.returns_ref)
738 )
739 }
740 };
741
742 let mut attrs = String::new();
743 if func.params.len() > 7 {
745 attrs.push_str("#[allow(clippy::too_many_arguments)]\n");
746 }
747 if func.error_type.is_some() {
749 attrs.push_str("#[allow(clippy::missing_errors_doc)]\n");
750 }
751 format!(
752 "{attrs}#[napi{js_name_attr}]\npub {async_kw}fn {}({params}) -> {return_annotation} {{\n \
753 {body}\n}}",
754 func.name
755 )
756}
757
758fn napi_gen_call_args(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
760 params
761 .iter()
762 .map(|p| match &p.ty {
763 TypeRef::Primitive(prim) if needs_napi_cast(prim) => {
764 let core_ty = core_prim_str(prim);
765 if p.optional {
766 format!("{}.map(|v| v as {})", p.name, core_ty)
767 } else {
768 format!("{} as {}", p.name, core_ty)
769 }
770 }
771 TypeRef::Duration => {
772 if p.optional {
773 format!("{}.map(|v| std::time::Duration::from_secs(v as u64))", p.name)
774 } else {
775 format!("std::time::Duration::from_secs({} as u64)", p.name)
776 }
777 }
778 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
779 if p.optional {
780 format!("{}.as_ref().map(|v| &v.inner)", p.name)
781 } else {
782 format!("&{}.inner", p.name)
783 }
784 }
785 TypeRef::Named(_) => {
786 if p.optional {
787 format!("{}.map(Into::into)", p.name)
788 } else {
789 format!("{}.into()", p.name)
790 }
791 }
792 TypeRef::String | TypeRef::Char => format!("&{}", p.name),
793 TypeRef::Path => format!("std::path::PathBuf::from({})", p.name),
794 TypeRef::Bytes => format!("&{}", p.name),
795 _ => p.name.clone(),
796 })
797 .collect::<Vec<_>>()
798 .join(", ")
799}
800
801fn napi_wrap_return(
804 expr: &str,
805 return_type: &TypeRef,
806 type_name: &str,
807 opaque_types: &AHashSet<String>,
808 self_is_opaque: bool,
809 returns_ref: bool,
810) -> String {
811 match return_type {
812 TypeRef::Primitive(p) if needs_napi_cast(p) => {
813 format!("{expr} as i64")
814 }
815 TypeRef::Duration => format!("{expr}.as_secs() as i64"),
816 TypeRef::Named(n) if n == type_name && self_is_opaque => {
818 if returns_ref {
819 format!("Self {{ inner: Arc::new({expr}.clone()) }}")
820 } else {
821 format!("Self {{ inner: Arc::new({expr}) }}")
822 }
823 }
824 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
825 if returns_ref {
826 format!("Js{n} {{ inner: Arc::new({expr}.clone()) }}")
827 } else {
828 format!("Js{n} {{ inner: Arc::new({expr}) }}")
829 }
830 }
831 TypeRef::Named(_) => {
832 if returns_ref {
833 format!("{expr}.clone().into()")
834 } else {
835 format!("{expr}.into()")
836 }
837 }
838 _ => generators::wrap_return(expr, return_type, type_name, opaque_types, self_is_opaque, returns_ref),
839 }
840}
841
842fn napi_wrap_return_fn(
844 expr: &str,
845 return_type: &TypeRef,
846 opaque_types: &AHashSet<String>,
847 returns_ref: bool,
848) -> String {
849 match return_type {
850 TypeRef::Primitive(p) if needs_napi_cast(p) => {
851 format!("{expr} as i64")
852 }
853 TypeRef::Duration => format!("{expr}.as_secs() as i64"),
854 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
855 if returns_ref {
856 format!("Js{n} {{ inner: Arc::new({expr}.clone()) }}")
857 } else {
858 format!("Js{n} {{ inner: Arc::new({expr}) }}")
859 }
860 }
861 TypeRef::Named(_) => {
862 if returns_ref {
863 format!("{expr}.clone().into()")
864 } else {
865 format!("{expr}.into()")
866 }
867 }
868 TypeRef::String | TypeRef::Char | TypeRef::Bytes => {
869 if returns_ref {
870 format!("{expr}.into()")
871 } else {
872 expr.to_string()
873 }
874 }
875 TypeRef::Path => format!("{expr}.to_string_lossy().to_string()"),
876 TypeRef::Json => format!("{expr}.to_string()"),
877 TypeRef::Optional(inner) => match inner.as_ref() {
878 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
879 if returns_ref {
880 format!("{expr}.map(|v| Js{name} {{ inner: Arc::new(v.clone()) }})")
881 } else {
882 format!("{expr}.map(|v| Js{name} {{ inner: Arc::new(v) }})")
883 }
884 }
885 TypeRef::Named(_) => {
886 if returns_ref {
887 format!("{expr}.map(|v| v.clone().into())")
888 } else {
889 format!("{expr}.map(Into::into)")
890 }
891 }
892 TypeRef::Path => {
893 format!("{expr}.map(Into::into)")
894 }
895 TypeRef::String | TypeRef::Char | TypeRef::Bytes => {
896 if returns_ref {
897 format!("{expr}.map(Into::into)")
898 } else {
899 expr.to_string()
900 }
901 }
902 _ => expr.to_string(),
903 },
904 TypeRef::Vec(inner) => match inner.as_ref() {
905 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
906 if returns_ref {
907 format!("{expr}.into_iter().map(|v| Js{name} {{ inner: Arc::new(v.clone()) }}).collect()")
908 } else {
909 format!("{expr}.into_iter().map(|v| Js{name} {{ inner: Arc::new(v) }}).collect()")
910 }
911 }
912 TypeRef::Named(_) => {
913 if returns_ref {
914 format!("{expr}.into_iter().map(|v| v.clone().into()).collect()")
915 } else {
916 format!("{expr}.into_iter().map(Into::into).collect()")
917 }
918 }
919 TypeRef::Path => {
920 format!("{expr}.into_iter().map(Into::into).collect()")
921 }
922 TypeRef::String | TypeRef::Char | TypeRef::Bytes => {
923 if returns_ref {
924 format!("{expr}.into_iter().map(Into::into).collect()")
925 } else {
926 expr.to_string()
927 }
928 }
929 _ => expr.to_string(),
930 },
931 _ => expr.to_string(),
932 }
933}
934
935fn needs_napi_cast(p: &alef_core::ir::PrimitiveType) -> bool {
936 matches!(
937 p,
938 alef_core::ir::PrimitiveType::U64 | alef_core::ir::PrimitiveType::Usize | alef_core::ir::PrimitiveType::Isize
939 )
940}
941
942fn core_prim_str(p: &alef_core::ir::PrimitiveType) -> &'static str {
943 match p {
944 alef_core::ir::PrimitiveType::U64 => "u64",
945 alef_core::ir::PrimitiveType::Usize => "usize",
946 alef_core::ir::PrimitiveType::Isize => "isize",
947 _ => unreachable!(),
948 }
949}
950
951fn gen_tokio_runtime() -> String {
953 "static WORKER_POOL: std::sync::LazyLock<tokio::runtime::Runtime> = std::sync::LazyLock::new(|| {
954 tokio::runtime::Builder::new_multi_thread()
955 .enable_all()
956 .build()
957 .expect(\"Failed to create Tokio runtime\")
958});"
959 .to_string()
960}