@rejaul/react-native-rust 1.0.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var project_exports = {};
30
+ __export(project_exports, {
31
+ hasDefaultMultiplyBody: () => hasDefaultMultiplyBody,
32
+ normalizeSignature: () => normalizeSignature,
33
+ renderProjectBindings: () => renderProjectBindings,
34
+ replaceMarkedRegion: () => replaceMarkedRegion,
35
+ replaceTemplateMethod: () => replaceTemplateMethod,
36
+ resolveOutsideReference: () => resolveOutsideReference,
37
+ toPosixRelative: () => toPosixRelative,
38
+ updateCppFile: () => updateCppFile,
39
+ validateExistingHandler: () => validateExistingHandler
40
+ });
41
+ module.exports = __toCommonJS(project_exports);
42
+ var import_node_fs = __toESM(require("node:fs"));
43
+ var import_node_path = __toESM(require("node:path"));
44
+ var import_constants = require("./constants");
45
+ var import_renderers = require("./renderers");
46
+ function toPosixRelative(from, to) {
47
+ return import_node_path.default.relative(from, to).split(import_node_path.default.sep).join("/");
48
+ }
49
+ function resolveOutsideReference(root, targetDir, linkName) {
50
+ if (!toPosixRelative(root, targetDir).startsWith("..")) return targetDir;
51
+ const linkPath = import_node_path.default.join(root, linkName);
52
+ import_node_fs.default.rmSync(linkPath, { force: true });
53
+ import_node_fs.default.symlinkSync(toPosixRelative(root, targetDir), linkPath);
54
+ return linkPath;
55
+ }
56
+ function replaceMarkedRegion(source, startMarker, endMarker, generated) {
57
+ const start = source.indexOf(startMarker);
58
+ const end = source.indexOf(endMarker);
59
+ if (start < 0 || end < start) return null;
60
+ const block = `${startMarker}
61
+ ${generated}
62
+ ${endMarker}`;
63
+ return `${source.slice(0, start)}${block}${source.slice(end + endMarker.length)}`;
64
+ }
65
+ function replaceTemplateMethod(source, markerStart, markerEnd, generated, methodNeedle) {
66
+ const functionIndex = source.indexOf(methodNeedle);
67
+ if (functionIndex < 0) throw new Error(`Could not locate C++ template method "${methodNeedle}".`);
68
+ const lineStart = source.lastIndexOf("\n", functionIndex) + 1;
69
+ const bodyStart = source.indexOf("{", functionIndex);
70
+ if (bodyStart < 0) throw new Error(`Could not locate the body for C++ method "${methodNeedle}".`);
71
+ let depth = 0;
72
+ let bodyEnd = -1;
73
+ for (let index = bodyStart; index < source.length; index += 1) {
74
+ if (source[index] === "{") depth += 1;
75
+ if (source[index] === "}") {
76
+ depth -= 1;
77
+ if (depth === 0) {
78
+ bodyEnd = index + 1;
79
+ break;
80
+ }
81
+ }
82
+ }
83
+ if (bodyEnd < 0) throw new Error(`Could not find the end of C++ method "${methodNeedle}".`);
84
+ const block = `${markerStart}
85
+ ${generated}
86
+ ${markerEnd}`;
87
+ return `${source.slice(0, lineStart)}${block}${source.slice(bodyEnd)}`;
88
+ }
89
+ function hasDefaultMultiplyBody(source, moduleName) {
90
+ const methodIndex = source.indexOf(`${moduleName}Impl::multiply(`);
91
+ if (methodIndex < 0) return false;
92
+ const bodyStart = source.indexOf("{", methodIndex);
93
+ if (bodyStart < 0) return false;
94
+ let depth = 0;
95
+ for (let index = bodyStart; index < source.length; index += 1) {
96
+ if (source[index] === "{") depth += 1;
97
+ if (source[index] === "}") {
98
+ depth -= 1;
99
+ if (depth === 0) {
100
+ return source.slice(bodyStart + 1, index).replace(/\s+/g, "") === "returna*b;";
101
+ }
102
+ }
103
+ }
104
+ return false;
105
+ }
106
+ function updateCppFile(source, markers, generated, templateMethod, methodNeedle, rustHeaderPath) {
107
+ let updated = source;
108
+ if (rustHeaderPath && !updated.includes(`#include "${rustHeaderPath}"`)) {
109
+ if (!updated.includes("#pragma once")) throw new Error("Unsupported C++ module header template.");
110
+ updated = updated.replace("#pragma once", `#pragma once
111
+
112
+ #include "${rustHeaderPath}"`);
113
+ }
114
+ const marked = replaceMarkedRegion(updated, markers[0], markers[1], generated);
115
+ if (marked !== null) return marked;
116
+ if (updated.includes(markers[0]) || updated.includes(markers[1])) {
117
+ throw new Error("Incomplete react-native-rust generated markers found in C++ source.");
118
+ }
119
+ if (!templateMethod) throw new Error("C++ generated markers are missing; refusing to overwrite the module.");
120
+ if (methodNeedle.endsWith(";")) {
121
+ const count = updated.split(methodNeedle).length - 1;
122
+ if (count !== 1) throw new Error("Could not uniquely locate the C++ template method declaration.");
123
+ return updated.replace(methodNeedle, `${markers[0]}
124
+ ${generated}
125
+ ${markers[1]}`);
126
+ }
127
+ return replaceTemplateMethod(updated, markers[0], markers[1], generated, methodNeedle);
128
+ }
129
+ function normalizeSignature(text) {
130
+ return text.replace(/\s+/g, "").replace(/,\)/g, ")");
131
+ }
132
+ function validateExistingHandler(filePath, method) {
133
+ const contents = import_node_fs.default.readFileSync(filePath, "utf8");
134
+ const signaturePattern = new RegExp(`pub\\s+fn\\s+${method.rustName}\\s*\\(([^)]*)\\)\\s*->\\s*([^\\{]+)\\{`);
135
+ const match = contents.match(signaturePattern);
136
+ const args = method.params.map((param) => {
137
+ if (param.kind !== "callback") return `${param.name}: ${param.rust}`;
138
+ const callbackTypes = param.params.map((callbackParam) => callbackParam.rust).join(", ");
139
+ return `${param.name}: &mut dyn FnMut(${callbackTypes})`;
140
+ }).join(", ");
141
+ const returnType = method.returnType.promise ? `Result<${method.returnType.rust}, String>` : method.returnType.rust;
142
+ const expected = `${method.rustName}(${args}) -> ${returnType}`;
143
+ const expectedSignature = `pub fn ${expected} {`;
144
+ if (normalizeSignature(contents).includes(normalizeSignature(expectedSignature))) return;
145
+ if (!match || normalizeSignature(match[0].replace(/^pub\s+fn\s+/, "").replace(/\{\s*$/, "")) !== normalizeSignature(expected)) {
146
+ throw new Error(`Rust handler ${import_node_path.default.relative(process.cwd(), filePath)} does not match the TypeScript Spec. Update its signature before regenerating.`);
147
+ }
148
+ }
149
+ function renderProjectBindings(root, moduleName, methods, initialize, crateName, rustRoot = import_node_path.default.join(root, "rust")) {
150
+ const headerPath = import_node_path.default.join(root, "cpp", `${moduleName}Impl.h`);
151
+ const sourcePath = import_node_path.default.join(root, "cpp", `${moduleName}Impl.cpp`);
152
+ const header = import_node_fs.default.readFileSync(headerPath, "utf8");
153
+ const source = import_node_fs.default.readFileSync(sourcePath, "utf8");
154
+ if (initialize && !hasDefaultMultiplyBody(source, moduleName)) {
155
+ throw new Error("The C++ multiply demo was customized; move its logic into Rust before running init. No files were changed.");
156
+ }
157
+ const cpp = (0, import_renderers.renderCppMethods)(methods, moduleName);
158
+ const generatedIncludes = cpp.source.match(/^#include .+$/gm) || [];
159
+ const cppSource = cpp.source.replace(/^#include .+\n/gm, "");
160
+ const headerTemplateMethod = "double multiply(jsi::Runtime& rt, double a, double b);";
161
+ const sourceTemplateMethod = `${moduleName}Impl::multiply(`;
162
+ const rustIncludeTarget = resolveOutsideReference(root, import_node_path.default.join(rustRoot, "include"), "rust-include");
163
+ const rustHeaderPath = `${toPosixRelative(import_node_path.default.join(root, "cpp"), rustIncludeTarget)}/rust_api.h`;
164
+ const updatedHeader = updateCppFile(header, [import_constants.HEADER_START, import_constants.HEADER_END], cpp.header, initialize, headerTemplateMethod, rustHeaderPath);
165
+ let updatedSource = updateCppFile(source, [import_constants.SOURCE_START, import_constants.SOURCE_END], cppSource, initialize, sourceTemplateMethod);
166
+ if (generatedIncludes.length > 0) {
167
+ const namespaceIndex = updatedSource.indexOf("\nnamespace facebook::react {");
168
+ if (namespaceIndex < 0) throw new Error("Could not locate the C++ namespace for generated includes.");
169
+ const missingIncludes = generatedIncludes.filter((include) => !updatedSource.includes(include));
170
+ if (missingIncludes.length > 0) {
171
+ const insertionPoint = namespaceIndex + 1;
172
+ updatedSource = `${updatedSource.slice(0, insertionPoint)}${missingIncludes.join("\n")}
173
+ ${updatedSource.slice(insertionPoint)}`;
174
+ }
175
+ }
176
+ const rustDirectory = import_node_path.default.join(rustRoot, "src");
177
+ const moduleList = (0, import_renderers.renderRustModuleList)(methods);
178
+ const updates = /* @__PURE__ */ new Map([
179
+ [headerPath, updatedHeader],
180
+ [sourcePath, updatedSource],
181
+ [import_node_path.default.join(rustDirectory, "lib.rs"), (0, import_renderers.renderRustExports)(methods)],
182
+ [import_node_path.default.join(rustDirectory, "api", "mod.rs"), moduleList],
183
+ [import_node_path.default.join(rustDirectory, "wasm.rs"), (0, import_renderers.renderRustWasmModule)(methods)]
184
+ ]);
185
+ for (const method of methods) {
186
+ const handlerPath = import_node_path.default.join(rustDirectory, "api", `${method.rustName}.rs`);
187
+ if (import_node_fs.default.existsSync(handlerPath)) validateExistingHandler(handlerPath, method);
188
+ else updates.set(handlerPath, (0, import_renderers.renderRustHandler)(method));
189
+ }
190
+ const wasmPkgTarget = resolveOutsideReference(root, import_node_path.default.join(rustRoot, "build", "web", "pkg"), "rust-build-web-pkg");
191
+ const wasmPkgPath = toPosixRelative(import_node_path.default.join(root, "src"), wasmPkgTarget);
192
+ const wrappers = (0, import_renderers.renderWrappers)(methods, moduleName, crateName, wasmPkgPath);
193
+ const generatedModulePath = import_node_path.default.join(root, "src", "rust-generated");
194
+ updates.set(`${generatedModulePath}.tsx`, wrappers.web);
195
+ updates.set(`${generatedModulePath}.native.tsx`, wrappers.native);
196
+ const indexPath = import_node_path.default.join(root, "src", "index.tsx");
197
+ let index = import_node_fs.default.readFileSync(indexPath, "utf8");
198
+ for (const method of methods) {
199
+ const oldExport = new RegExp(`^export\\s*\\{\\s*${method.name}\\s*\\}\\s*from\\s*['"]\\./${method.name}['"];?\\s*$`, "m");
200
+ index = index.replace(oldExport, "");
201
+ }
202
+ if (!index.includes("export * from './rust-generated';")) {
203
+ index = `${index.trimEnd()}
204
+ export * from './rust-generated';
205
+ `;
206
+ }
207
+ updates.set(indexPath, index);
208
+ return updates;
209
+ }
210
+ // Annotate the CommonJS export names for ESM import in node:
211
+ 0 && (module.exports = {
212
+ hasDefaultMultiplyBody,
213
+ normalizeSignature,
214
+ renderProjectBindings,
215
+ replaceMarkedRegion,
216
+ replaceTemplateMethod,
217
+ resolveOutsideReference,
218
+ toPosixRelative,
219
+ updateCppFile,
220
+ validateExistingHandler
221
+ });
@@ -1,3 +1,33 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var renderers_exports = {};
20
+ __export(renderers_exports, {
21
+ renderCppHelpers: () => renderCppHelpers,
22
+ renderCppMethods: () => renderCppMethods,
23
+ renderRustExports: () => renderRustExports,
24
+ renderRustFfiModule: () => renderRustFfiModule,
25
+ renderRustHandler: () => renderRustHandler,
26
+ renderRustModuleList: () => renderRustModuleList,
27
+ renderRustWasmModule: () => renderRustWasmModule,
28
+ renderWrappers: () => renderWrappers
29
+ });
30
+ module.exports = __toCommonJS(renderers_exports);
1
31
  function renderRustFfiModule() {
2
32
  return `use serde::de::DeserializeOwned;
3
33
  use serde::Serialize;
@@ -76,69 +106,111 @@ pub extern "C" fn rnrs_buffer_free(buffer: RustBuffer) {
76
106
  }
77
107
  `;
78
108
  }
79
-
80
109
  function renderRustExports(methods) {
81
- const lines = ['mod api;', 'mod ffi;', 'pub use ffi::{rnrs_buffer_free, RustBuffer, RustCallback, RustSlice};', ''];
110
+ const lines = [
111
+ "mod api;",
112
+ "mod ffi;",
113
+ '#[cfg(target_arch = "wasm32")]',
114
+ "mod wasm;",
115
+ "pub use ffi::{rnrs_buffer_free, RustBuffer, RustCallback, RustSlice};",
116
+ ""
117
+ ];
82
118
  for (const method of methods) {
83
- const abiParams = method.params.map((param) => `${param.name}: ffi::${param.kind === 'callback' ? 'RustCallback' : 'RustSlice'}`).join(', ');
84
- lines.push('#[no_mangle]');
119
+ const abiParams = method.params.map((param) => `${param.name}: ffi::${param.kind === "callback" ? "RustCallback" : "RustSlice"}`).join(", ");
120
+ lines.push('#[cfg(not(target_arch = "wasm32"))]');
121
+ lines.push("#[no_mangle]");
85
122
  lines.push(`pub extern "C" fn ${method.symbol}(${abiParams}) -> ffi::RustBuffer {`);
86
- lines.push(' ffi::catch_json(|| {');
87
-
123
+ lines.push(" ffi::catch_json(|| {");
88
124
  for (const param of method.params) {
89
- if (param.kind !== 'callback') {
125
+ if (param.kind !== "callback") {
90
126
  lines.push(` let ${param.name}: ${param.rust} = unsafe { ffi::decode_json(${param.name}) }?;`);
91
127
  continue;
92
128
  }
93
- const callbackParams = param.params.map((callbackParam) => `${callbackParam.name}: ${callbackParam.rust}`).join(', ');
94
- const callbackValues = param.params.map((callbackParam) => callbackParam.name).join(', ');
95
- const payload = callbackValues.length === 0 ? 'Vec::<u8>::new()' : `serde_json::to_vec(&(${callbackValues}${param.params.length === 1 ? ',' : ''})).unwrap_or_default()`;
129
+ const callbackParams = param.params.map((callbackParam) => `${callbackParam.name}: ${callbackParam.rust}`).join(", ");
130
+ const callbackValues = param.params.map((callbackParam) => callbackParam.name).join(", ");
131
+ const payload = callbackValues.length === 0 ? "Vec::<u8>::new()" : `serde_json::to_vec(&(${callbackValues}${param.params.length === 1 ? "," : ""})).unwrap_or_default()`;
96
132
  lines.push(` let mut ${param.name}_callback = |${callbackParams}| {`);
97
133
  lines.push(` let payload = ${payload};`);
98
134
  lines.push(` let slice = ffi::RustSlice { data: payload.as_ptr(), len: payload.len() };`);
99
135
  lines.push(` unsafe { (${param.name}.invoke)(${param.name}.context, slice); }`);
100
- lines.push(' };');
136
+ lines.push(" };");
101
137
  }
102
-
103
- const callArgs = method.params.map((param) => param.kind === 'callback' ? `&mut ${param.name}_callback` : param.name).join(', ');
138
+ const callArgs = method.params.map((param) => param.kind === "callback" ? `&mut ${param.name}_callback` : param.name).join(", ");
104
139
  const call = `api::${method.rustName}::${method.rustName}(${callArgs})`;
105
140
  if (method.returnType.promise) {
106
141
  lines.push(` ${call}`);
107
- } else if (method.returnType.kind === 'void') {
142
+ } else if (method.returnType.kind === "void") {
108
143
  lines.push(` ${call};`);
109
- lines.push(' Ok(())');
144
+ lines.push(" Ok(())");
110
145
  } else {
111
146
  lines.push(` Ok(${call})`);
112
147
  }
113
- lines.push(' })', '}', '');
148
+ lines.push(" })", "}", "");
114
149
  }
115
- return lines.join('\n');
150
+ return lines.join("\n");
116
151
  }
117
-
118
152
  function renderRustHandler(method) {
119
153
  const params = method.params.map((param) => {
120
- if (param.kind === 'callback') {
121
- const callbackTypes = param.params.map((callbackParam) => callbackParam.rust).join(', ');
154
+ if (param.kind === "callback") {
155
+ const callbackTypes = param.params.map((callbackParam) => callbackParam.rust).join(", ");
122
156
  return `${param.name}: &mut dyn FnMut(${callbackTypes})`;
123
157
  }
124
158
  return `${param.name}: ${param.rust}`;
125
- }).join(', ');
159
+ }).join(", ");
126
160
  const returnType = method.returnType.promise ? `Result<${method.returnType.rust}, String>` : method.returnType.rust;
127
161
  const lines = [`// TODO: Replace the generated placeholder with the method implementation.`, `pub fn ${method.rustName}(${params}) -> ${returnType} {`];
128
162
  for (const param of method.params) {
129
- if (param.kind === 'callback') lines.push(` let _ = &mut ${param.name};`);
130
- else lines.push(` let _ = ${param.name};`);
163
+ lines.push(` let _ = ${param.name};`);
131
164
  }
132
165
  if (method.returnType.promise) lines.push(` Ok(${method.returnType.defaultValue})`);
133
166
  else if (method.returnType.defaultValue !== null) lines.push(` ${method.returnType.defaultValue}`);
134
- lines.push('}', '');
135
- return lines.join('\n');
167
+ lines.push("}", "");
168
+ return lines.join("\n");
136
169
  }
137
-
138
170
  function renderRustModuleList(methods) {
139
- return `${methods.map((method) => `pub(crate) mod ${method.rustName};`).join('\n')}\n`;
171
+ return `${methods.map((method) => `pub(crate) mod ${method.rustName};`).join("\n")}
172
+ `;
173
+ }
174
+ function renderRustWasmModule(methods) {
175
+ const lines = ["use wasm_bindgen::prelude::*;", ""];
176
+ for (const method of methods) {
177
+ const wasmParams = method.params.map((param) => param.kind === "callback" ? `${param.name}: &js_sys::Function` : `${param.name}_json: &str`).join(", ");
178
+ const isVoidSync = method.returnType.kind === "void" && !method.returnType.promise;
179
+ const wasmReturn = isVoidSync ? "Result<(), JsValue>" : "Result<String, JsValue>";
180
+ lines.push("#[wasm_bindgen]");
181
+ lines.push(`pub fn ${method.symbol}(${wasmParams}) -> ${wasmReturn} {`);
182
+ for (const param of method.params) {
183
+ if (param.kind === "callback") continue;
184
+ lines.push(` let ${param.name}: ${param.rust} = serde_json::from_str(${param.name}_json).map_err(|error| JsValue::from_str(&error.to_string()))?;`);
185
+ }
186
+ for (const param of method.params) {
187
+ if (param.kind !== "callback") continue;
188
+ const callbackParams = param.params.map((callbackParam) => `${callbackParam.name}: ${callbackParam.rust}`).join(", ");
189
+ const callbackValues = param.params.map((callbackParam) => callbackParam.name).join(", ");
190
+ const encode = callbackValues.length === 0 ? 'String::from("[]")' : `serde_json::to_string(&(${callbackValues}${param.params.length === 1 ? "," : ""})).unwrap_or_default()`;
191
+ lines.push(` let mut ${param.name}_callback = |${callbackParams}| {`);
192
+ lines.push(` let payload = ${encode};`);
193
+ lines.push(` let _ = ${param.name}.call1(&JsValue::NULL, &JsValue::from_str(&payload));`);
194
+ lines.push(" };");
195
+ }
196
+ const callArgs = method.params.map((param) => param.kind === "callback" ? `&mut ${param.name}_callback` : param.name).join(", ");
197
+ const call = `crate::api::${method.rustName}::${method.rustName}(${callArgs})`;
198
+ if (method.returnType.promise) {
199
+ lines.push(` let result = ${call};`);
200
+ lines.push(" result");
201
+ lines.push(" .map_err(|error| JsValue::from_str(&error))");
202
+ lines.push(" .and_then(|value| serde_json::to_string(&value).map_err(|error| JsValue::from_str(&error.to_string())))");
203
+ } else if (isVoidSync) {
204
+ lines.push(` ${call};`);
205
+ lines.push(" Ok(())");
206
+ } else {
207
+ lines.push(` let value = ${call};`);
208
+ lines.push(" serde_json::to_string(&value).map_err(|error| JsValue::from_str(&error.to_string()))");
209
+ }
210
+ lines.push("}", "");
211
+ }
212
+ return lines.join("\n");
140
213
  }
141
-
142
214
  function renderCppHelpers() {
143
215
  return `#include <exception>
144
216
  #include <string>
@@ -243,24 +315,25 @@ static jsi::Value rnrsMakePromise(
243
315
  return promiseConstructor.callAsConstructor(runtime, std::move(executor));
244
316
  }`;
245
317
  }
246
-
247
318
  function renderCppMethods(methods, moduleName) {
248
319
  const cppArgName = (index) => `rnrsArg${index}`;
249
- const cppType = (method) => method.returnType.promise ? 'jsi::Value' : method.returnType.cpp;
320
+ const cppType = (method) => method.returnType.promise ? "jsi::Value" : method.returnType.cpp;
250
321
  const declarations = methods.map((method) => {
251
322
  const params = method.params.map((param, index) => `${param.cpp} ${cppArgName(index)}`);
252
- return ` ${cppType(method)} ${method.name}(jsi::Runtime& rnrsRuntime${params.length ? `, ${params.join(', ')}` : ''});`;
323
+ return ` ${cppType(method)} ${method.name}(jsi::Runtime& rnrsRuntime${params.length ? `, ${params.join(", ")}` : ""});`;
253
324
  });
254
-
255
325
  const definitions = methods.map((method) => {
256
326
  const params = method.params.map((param, index) => `${param.cpp} ${cppArgName(index)}`);
257
- const signature = `${cppType(method)} ${moduleName}Impl::${method.name}(\n jsi::Runtime& rnrsRuntime${params.length ? `,\n ${params.join(',\n ')}` : ''}\n)`;
327
+ const signature = `${cppType(method)} ${moduleName}Impl::${method.name}(
328
+ jsi::Runtime& rnrsRuntime${params.length ? `,
329
+ ${params.join(",\n ")}` : ""}
330
+ )`;
258
331
  const jsonArgs = [];
259
332
  const callbackArgs = [];
260
333
  const setup = [];
261
334
  method.params.forEach((param, index) => {
262
335
  const name = cppArgName(index);
263
- if (param.kind === 'callback') {
336
+ if (param.kind === "callback") {
264
337
  const context = `rnrsCallbackContext${index}`;
265
338
  const bridge = `rnrsCallbackBridge${index}`;
266
339
  setup.push(` RustCallbackContext ${context}{&rnrsRuntime, &${name}, nullptr};`);
@@ -274,84 +347,128 @@ function renderCppMethods(methods, moduleName) {
274
347
  setup.push(` RustSlice ${slice}{reinterpret_cast<const uint8_t*>(${json}.data()), ${json}.size()};`);
275
348
  jsonArgs.push({ json, slice });
276
349
  });
277
-
278
- const ffiArgs = method.params.map((param, index) => (
279
- param.kind === 'callback' ? `rnrsCallbackBridge${index}` : `rnrsSlice${index}`
280
- ));
350
+ const ffiArgs = method.params.map((param, index) => param.kind === "callback" ? `rnrsCallbackBridge${index}` : `rnrsSlice${index}`);
281
351
  if (method.returnType.promise) {
282
352
  const captures = jsonArgs.map(({ json }) => `${json} = std::move(${json})`);
283
353
  const workerLines = [];
284
354
  method.params.forEach((param, index) => {
285
- if (param.kind !== 'callback') {
355
+ if (param.kind !== "callback") {
286
356
  const json = `rnrsJson${index}`;
287
357
  workerLines.push(` RustSlice rnrsSlice${index}{reinterpret_cast<const uint8_t*>(${json}.data()), ${json}.size()};`);
288
358
  }
289
359
  });
290
- workerLines.push(` return ${method.symbol}(${ffiArgs.join(', ')});`);
291
- return [signature + ' {', ...setup, ` return rnrsMakePromise(rnrsRuntime, jsInvoker_, [${captures.join(', ')}]() mutable {`, ...workerLines, ' });', '}'].join('\n');
360
+ workerLines.push(` return ${method.symbol}(${ffiArgs.join(", ")});`);
361
+ return [signature + " {", ...setup, ` return rnrsMakePromise(rnrsRuntime, jsInvoker_, [${captures.join(", ")}]() mutable {`, ...workerLines, " });", "}"].join("\n");
292
362
  }
293
-
294
- const call = `auto rnrsResult = ${method.symbol}(${ffiArgs.join(', ')});`;
363
+ const call = `auto rnrsResult = ${method.symbol}(${ffiArgs.join(", ")});`;
295
364
  const callbackErrorChecks = callbackArgs.map(({ context }) => ` if (${context}.exception) { rnrs_buffer_free(rnrsResult); std::rethrow_exception(${context}.exception); }`);
296
- const decoded = 'auto rnrsValue = rnrsFromRust(rnrsRuntime, rnrsResult);';
365
+ const decoded = "auto rnrsValue = rnrsFromRust(rnrsRuntime, rnrsResult);";
297
366
  let resultLine;
298
367
  switch (method.returnType.kind) {
299
- case 'void': resultLine = ' (void)rnrsValue;\n return;'; break;
300
- case 'number': resultLine = ' return rnrsValue.asNumber();'; break;
301
- case 'boolean': resultLine = ' return rnrsValue.asBool();'; break;
302
- case 'string': resultLine = ' return rnrsValue.asString(rnrsRuntime);'; break;
303
- case 'array': resultLine = ' return rnrsValue.asObject(rnrsRuntime).asArray(rnrsRuntime);'; break;
304
- case 'json':
305
- if (method.returnType.cpp === 'jsi::String') resultLine = ' return rnrsValue.asString(rnrsRuntime);';
306
- else if (method.returnType.cpp === 'jsi::Array') resultLine = ' return rnrsValue.asObject(rnrsRuntime).asArray(rnrsRuntime);';
307
- else resultLine = ' return rnrsValue.asObject(rnrsRuntime);';
368
+ case "void":
369
+ resultLine = " (void)rnrsValue;\n return;";
370
+ break;
371
+ case "number":
372
+ resultLine = " return rnrsValue.asNumber();";
373
+ break;
374
+ case "boolean":
375
+ resultLine = " return rnrsValue.asBool();";
376
+ break;
377
+ case "json":
378
+ if (method.returnType.cpp === "jsi::String") resultLine = " return rnrsValue.asString(rnrsRuntime);";
379
+ else if (method.returnType.cpp === "jsi::Array") resultLine = " return rnrsValue.asObject(rnrsRuntime).asArray(rnrsRuntime);";
380
+ else resultLine = " return rnrsValue.asObject(rnrsRuntime);";
308
381
  break;
309
- default: throw new Error(`No C++ return conversion for ${method.returnType.kind}.`);
382
+ default:
383
+ throw new Error(`No C++ return conversion for ${method.returnType.kind}.`);
310
384
  }
311
- return [signature + ' {', ...setup, ` ${call}`, ...callbackErrorChecks, ` ${decoded}`, resultLine, '}'].join('\n');
385
+ return [signature + " {", ...setup, ` ${call}`, ...callbackErrorChecks, ` ${decoded}`, resultLine, "}"].join("\n");
312
386
  });
313
387
  return {
314
- header: declarations.join('\n'),
315
- source: `${renderCppHelpers()}\n\n${definitions.join('\n\n')}`,
388
+ header: declarations.join("\n"),
389
+ source: `${renderCppHelpers()}
390
+
391
+ ${definitions.join("\n\n")}`
316
392
  };
317
393
  }
318
-
319
- function renderWrappers(methods, moduleName) {
320
- const nativeLines = [`import ${moduleName} from './Native${moduleName}';`, ''];
321
- const fallbackLines = [];
322
- const usesUnsafeObject = methods.some((method) => (
323
- method.returnType.typescript.includes('UnsafeObject')
324
- || method.params.some((param) => param.typescript.includes('UnsafeObject'))
325
- ));
394
+ function renderWrappers(methods, moduleName, crateName, wasmPkgPath = "../rust/build/web/pkg") {
395
+ const nativeLines = [
396
+ `import ${moduleName} from './Native${moduleName}';`,
397
+ "",
398
+ "/** No-op on native, where methods are always ready to call; matches the web module's async init so callers don't need to branch on platform. */",
399
+ "export function initRustWeb(): Promise<void> {",
400
+ " return Promise.resolve();",
401
+ "}",
402
+ ""
403
+ ];
404
+ const webLines = [
405
+ `import wasmInit, * as rnrsWasm from '${wasmPkgPath}/${crateName}.js';`,
406
+ "",
407
+ "let rnrsWasmReady = false;",
408
+ "let rnrsWasmInit: Promise<void> | null = null;",
409
+ "",
410
+ "/** Loads the compiled Rust WebAssembly module. Call and await this once before using this module on web. */",
411
+ "export function initRustWeb(wasmUrl?: string | URL): Promise<void> {",
412
+ " if (!rnrsWasmInit) {",
413
+ " rnrsWasmInit = wasmInit(wasmUrl).then(() => { rnrsWasmReady = true; });",
414
+ " }",
415
+ " return rnrsWasmInit;",
416
+ "}",
417
+ "",
418
+ "function rnrsRequireWasm(): void {",
419
+ ` if (!rnrsWasmReady) throw new Error('Call and await initRustWeb() before using ${moduleName} on web.');`,
420
+ "}",
421
+ ""
422
+ ];
423
+ const usesUnsafeObject = methods.some((method) => method.returnType.typescript.includes("UnsafeObject") || method.params.some((param) => param.typescript.includes("UnsafeObject")));
326
424
  if (usesUnsafeObject) {
327
- const codegenTypesDeclaration = 'declare namespace CodegenTypes { type UnsafeObject = object; }';
425
+ const codegenTypesDeclaration = "declare namespace CodegenTypes { type UnsafeObject = object; }";
328
426
  nativeLines.unshift(codegenTypesDeclaration);
329
- fallbackLines.push(codegenTypesDeclaration, '');
427
+ webLines.unshift(codegenTypesDeclaration, "");
330
428
  }
331
429
  for (const method of methods) {
332
- const tsParams = method.params.map((param) => `${param.name}: ${param.typescript}`).join(', ');
333
- const args = method.params.map((param) => param.name).join(', ');
430
+ const tsParams = method.params.map((param) => `${param.name}: ${param.typescript}`).join(", ");
431
+ const args = method.params.map((param) => param.name).join(", ");
334
432
  const nativeCall = `${moduleName}.${method.name}(${args})`;
335
433
  nativeLines.push(`export function ${method.name}(${tsParams}): ${method.returnType.typescript} {`);
336
- nativeLines.push(method.returnType.typescript === 'void' ? ` ${nativeCall};` : ` return ${nativeCall};`);
337
- nativeLines.push('}', '');
338
-
339
- const fallbackParams = method.params.map((param) => `_${param.name}: ${param.typescript}`).join(', ');
340
- fallbackLines.push(`export function ${method.name}(${fallbackParams}): ${method.returnType.typescript} {`);
341
- fallbackLines.push(" throw new Error('This method is only supported on native platforms.');", '}', '');
434
+ nativeLines.push(method.returnType.typescript === "void" ? ` ${nativeCall};` : ` return ${nativeCall};`);
435
+ nativeLines.push("}", "");
436
+ const wasmArgs = method.params.map((param) => param.kind === "callback" ? `(rnrsPayload: string) => { const rnrsArgs = JSON.parse(rnrsPayload); (${param.name} as (...rnrsCallbackArgs: any[]) => void)(...rnrsArgs); }` : `JSON.stringify(${param.name})`).join(", ");
437
+ const wasmCall = `rnrsWasm.${method.symbol}(${wasmArgs})`;
438
+ webLines.push(`export function ${method.name}(${tsParams}): ${method.returnType.typescript} {`);
439
+ webLines.push(" rnrsRequireWasm();");
440
+ if (method.returnType.promise) {
441
+ webLines.push(" try {");
442
+ if (method.returnType.kind === "void") {
443
+ webLines.push(` ${wasmCall};`);
444
+ webLines.push(" return Promise.resolve();");
445
+ } else {
446
+ webLines.push(` const rnrsResult = ${wasmCall};`);
447
+ webLines.push(" return Promise.resolve(JSON.parse(rnrsResult));");
448
+ }
449
+ webLines.push(" } catch (error) {");
450
+ webLines.push(" return Promise.reject(error instanceof Error ? error.message : String(error));");
451
+ webLines.push(" }");
452
+ } else if (method.returnType.kind === "void") {
453
+ webLines.push(` ${wasmCall};`);
454
+ } else {
455
+ webLines.push(` return JSON.parse(${wasmCall});`);
456
+ }
457
+ webLines.push("}", "");
342
458
  }
343
459
  return {
344
- native: nativeLines.join('\n'),
345
- fallback: fallbackLines.join('\n'),
460
+ native: nativeLines.join("\n"),
461
+ web: webLines.join("\n")
346
462
  };
347
463
  }
348
-
349
- module.exports = {
350
- renderRustFfiModule,
464
+ // Annotate the CommonJS export names for ESM import in node:
465
+ 0 && (module.exports = {
466
+ renderCppHelpers,
467
+ renderCppMethods,
351
468
  renderRustExports,
469
+ renderRustFfiModule,
352
470
  renderRustHandler,
353
471
  renderRustModuleList,
354
- renderCppHelpers,
355
- renderCppMethods,
356
- renderWrappers,
357
- };
472
+ renderRustWasmModule,
473
+ renderWrappers
474
+ });