@rejaul/react-native-rust 1.0.2 → 1.1.1
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.
- package/README.md +76 -2
- package/dist/app.js +271 -0
- package/dist/codegen/constants.js +168 -0
- package/dist/codegen/index.js +34 -0
- package/dist/codegen/project.js +201 -0
- package/{bin → dist}/codegen/renderers.js +201 -84
- package/dist/codegen/spec.js +158 -0
- package/dist/codegen/types.js +16 -0
- package/dist/codegen.js +22 -0
- package/dist/react-native-rust-lib.js +388 -0
- package/dist/react-native-rust.js +116 -0
- package/dist/types.js +16 -0
- package/package.json +28 -4
- package/bin/codegen/constants.js +0 -39
- package/bin/codegen/index.js +0 -9
- package/bin/codegen/project.js +0 -168
- package/bin/codegen/spec.js +0 -156
- package/bin/codegen.js +0 -1
- package/bin/react-native-rust-lib.js +0 -302
- package/bin/react-native-rust.js +0 -82
|
@@ -0,0 +1,201 @@
|
|
|
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
|
+
updateCppFile: () => updateCppFile,
|
|
37
|
+
validateExistingHandler: () => validateExistingHandler
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(project_exports);
|
|
40
|
+
var import_node_fs = __toESM(require("node:fs"));
|
|
41
|
+
var import_node_path = __toESM(require("node:path"));
|
|
42
|
+
var import_constants = require("./constants");
|
|
43
|
+
var import_renderers = require("./renderers");
|
|
44
|
+
function replaceMarkedRegion(source, startMarker, endMarker, generated) {
|
|
45
|
+
const start = source.indexOf(startMarker);
|
|
46
|
+
const end = source.indexOf(endMarker);
|
|
47
|
+
if (start < 0 || end < start) return null;
|
|
48
|
+
const block = `${startMarker}
|
|
49
|
+
${generated}
|
|
50
|
+
${endMarker}`;
|
|
51
|
+
return `${source.slice(0, start)}${block}${source.slice(end + endMarker.length)}`;
|
|
52
|
+
}
|
|
53
|
+
function replaceTemplateMethod(source, markerStart, markerEnd, generated, methodNeedle) {
|
|
54
|
+
const functionIndex = source.indexOf(methodNeedle);
|
|
55
|
+
if (functionIndex < 0) throw new Error(`Could not locate C++ template method "${methodNeedle}".`);
|
|
56
|
+
const lineStart = source.lastIndexOf("\n", functionIndex) + 1;
|
|
57
|
+
const bodyStart = source.indexOf("{", functionIndex);
|
|
58
|
+
if (bodyStart < 0) throw new Error(`Could not locate the body for C++ method "${methodNeedle}".`);
|
|
59
|
+
let depth = 0;
|
|
60
|
+
let bodyEnd = -1;
|
|
61
|
+
for (let index = bodyStart; index < source.length; index += 1) {
|
|
62
|
+
if (source[index] === "{") depth += 1;
|
|
63
|
+
if (source[index] === "}") {
|
|
64
|
+
depth -= 1;
|
|
65
|
+
if (depth === 0) {
|
|
66
|
+
bodyEnd = index + 1;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (bodyEnd < 0) throw new Error(`Could not find the end of C++ method "${methodNeedle}".`);
|
|
72
|
+
const block = `${markerStart}
|
|
73
|
+
${generated}
|
|
74
|
+
${markerEnd}`;
|
|
75
|
+
return `${source.slice(0, lineStart)}${block}${source.slice(bodyEnd)}`;
|
|
76
|
+
}
|
|
77
|
+
function hasDefaultMultiplyBody(source, moduleName) {
|
|
78
|
+
const methodIndex = source.indexOf(`${moduleName}Impl::multiply(`);
|
|
79
|
+
if (methodIndex < 0) return false;
|
|
80
|
+
const bodyStart = source.indexOf("{", methodIndex);
|
|
81
|
+
if (bodyStart < 0) return false;
|
|
82
|
+
let depth = 0;
|
|
83
|
+
for (let index = bodyStart; index < source.length; index += 1) {
|
|
84
|
+
if (source[index] === "{") depth += 1;
|
|
85
|
+
if (source[index] === "}") {
|
|
86
|
+
depth -= 1;
|
|
87
|
+
if (depth === 0) {
|
|
88
|
+
return source.slice(bodyStart + 1, index).replace(/\s+/g, "") === "returna*b;";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
function updateCppFile(source, markers, generated, templateMethod, methodNeedle, includeRustHeader = false) {
|
|
95
|
+
let updated = source;
|
|
96
|
+
if (includeRustHeader && !updated.includes("../rust/include/rust_api.h")) {
|
|
97
|
+
if (!updated.includes("#pragma once")) throw new Error("Unsupported C++ module header template.");
|
|
98
|
+
updated = updated.replace("#pragma once", '#pragma once\n\n#include "../rust/include/rust_api.h"');
|
|
99
|
+
}
|
|
100
|
+
const marked = replaceMarkedRegion(updated, markers[0], markers[1], generated);
|
|
101
|
+
if (marked !== null) return marked;
|
|
102
|
+
if (updated.includes(markers[0]) || updated.includes(markers[1])) {
|
|
103
|
+
throw new Error("Incomplete react-native-rust generated markers found in C++ source.");
|
|
104
|
+
}
|
|
105
|
+
if (!templateMethod) throw new Error("C++ generated markers are missing; refusing to overwrite the module.");
|
|
106
|
+
if (methodNeedle.endsWith(";")) {
|
|
107
|
+
const count = updated.split(methodNeedle).length - 1;
|
|
108
|
+
if (count !== 1) throw new Error("Could not uniquely locate the C++ template method declaration.");
|
|
109
|
+
return updated.replace(methodNeedle, `${markers[0]}
|
|
110
|
+
${generated}
|
|
111
|
+
${markers[1]}`);
|
|
112
|
+
}
|
|
113
|
+
return replaceTemplateMethod(updated, markers[0], markers[1], generated, methodNeedle);
|
|
114
|
+
}
|
|
115
|
+
function normalizeSignature(text) {
|
|
116
|
+
return text.replace(/\s+/g, "").replace(/,\)/g, ")");
|
|
117
|
+
}
|
|
118
|
+
function validateExistingHandler(filePath, method) {
|
|
119
|
+
const contents = import_node_fs.default.readFileSync(filePath, "utf8");
|
|
120
|
+
const signaturePattern = new RegExp(`pub\\s+fn\\s+${method.rustName}\\s*\\(([^)]*)\\)\\s*->\\s*([^\\{]+)\\{`);
|
|
121
|
+
const match = contents.match(signaturePattern);
|
|
122
|
+
const args = method.params.map((param) => {
|
|
123
|
+
if (param.kind !== "callback") return `${param.name}: ${param.rust}`;
|
|
124
|
+
const callbackTypes = param.params.map((callbackParam) => callbackParam.rust).join(", ");
|
|
125
|
+
return `${param.name}: &mut dyn FnMut(${callbackTypes})`;
|
|
126
|
+
}).join(", ");
|
|
127
|
+
const returnType = method.returnType.promise ? `Result<${method.returnType.rust}, String>` : method.returnType.rust;
|
|
128
|
+
const expected = `${method.rustName}(${args}) -> ${returnType}`;
|
|
129
|
+
const expectedSignature = `pub fn ${expected} {`;
|
|
130
|
+
if (normalizeSignature(contents).includes(normalizeSignature(expectedSignature))) return;
|
|
131
|
+
if (!match || normalizeSignature(match[0].replace(/^pub\s+fn\s+/, "").replace(/\{\s*$/, "")) !== normalizeSignature(expected)) {
|
|
132
|
+
throw new Error(`Rust handler ${import_node_path.default.relative(process.cwd(), filePath)} does not match the TypeScript Spec. Update its signature before regenerating.`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function renderProjectBindings(root, moduleName, methods, initialize, crateName) {
|
|
136
|
+
const headerPath = import_node_path.default.join(root, "cpp", `${moduleName}Impl.h`);
|
|
137
|
+
const sourcePath = import_node_path.default.join(root, "cpp", `${moduleName}Impl.cpp`);
|
|
138
|
+
const header = import_node_fs.default.readFileSync(headerPath, "utf8");
|
|
139
|
+
const source = import_node_fs.default.readFileSync(sourcePath, "utf8");
|
|
140
|
+
if (initialize && !hasDefaultMultiplyBody(source, moduleName)) {
|
|
141
|
+
throw new Error("The C++ multiply demo was customized; move its logic into Rust before running init. No files were changed.");
|
|
142
|
+
}
|
|
143
|
+
const cpp = (0, import_renderers.renderCppMethods)(methods, moduleName);
|
|
144
|
+
const generatedIncludes = cpp.source.match(/^#include .+$/gm) || [];
|
|
145
|
+
const cppSource = cpp.source.replace(/^#include .+\n/gm, "");
|
|
146
|
+
const headerTemplateMethod = "double multiply(jsi::Runtime& rt, double a, double b);";
|
|
147
|
+
const sourceTemplateMethod = `${moduleName}Impl::multiply(`;
|
|
148
|
+
const updatedHeader = updateCppFile(header, [import_constants.HEADER_START, import_constants.HEADER_END], cpp.header, initialize, headerTemplateMethod, true);
|
|
149
|
+
let updatedSource = updateCppFile(source, [import_constants.SOURCE_START, import_constants.SOURCE_END], cppSource, initialize, sourceTemplateMethod);
|
|
150
|
+
if (generatedIncludes.length > 0) {
|
|
151
|
+
const namespaceIndex = updatedSource.indexOf("\nnamespace facebook::react {");
|
|
152
|
+
if (namespaceIndex < 0) throw new Error("Could not locate the C++ namespace for generated includes.");
|
|
153
|
+
const missingIncludes = generatedIncludes.filter((include) => !updatedSource.includes(include));
|
|
154
|
+
if (missingIncludes.length > 0) {
|
|
155
|
+
const insertionPoint = namespaceIndex + 1;
|
|
156
|
+
updatedSource = `${updatedSource.slice(0, insertionPoint)}${missingIncludes.join("\n")}
|
|
157
|
+
${updatedSource.slice(insertionPoint)}`;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const rustDirectory = import_node_path.default.join(root, "rust", "src");
|
|
161
|
+
const moduleList = (0, import_renderers.renderRustModuleList)(methods);
|
|
162
|
+
const updates = /* @__PURE__ */ new Map([
|
|
163
|
+
[headerPath, updatedHeader],
|
|
164
|
+
[sourcePath, updatedSource],
|
|
165
|
+
[import_node_path.default.join(rustDirectory, "lib.rs"), (0, import_renderers.renderRustExports)(methods)],
|
|
166
|
+
[import_node_path.default.join(rustDirectory, "api", "mod.rs"), moduleList],
|
|
167
|
+
[import_node_path.default.join(rustDirectory, "wasm.rs"), (0, import_renderers.renderRustWasmModule)(methods)]
|
|
168
|
+
]);
|
|
169
|
+
for (const method of methods) {
|
|
170
|
+
const handlerPath = import_node_path.default.join(rustDirectory, "api", `${method.rustName}.rs`);
|
|
171
|
+
if (import_node_fs.default.existsSync(handlerPath)) validateExistingHandler(handlerPath, method);
|
|
172
|
+
else updates.set(handlerPath, (0, import_renderers.renderRustHandler)(method));
|
|
173
|
+
}
|
|
174
|
+
const wrappers = (0, import_renderers.renderWrappers)(methods, moduleName, crateName);
|
|
175
|
+
const generatedModulePath = import_node_path.default.join(root, "src", "rust-generated");
|
|
176
|
+
updates.set(`${generatedModulePath}.tsx`, wrappers.web);
|
|
177
|
+
updates.set(`${generatedModulePath}.native.tsx`, wrappers.native);
|
|
178
|
+
const indexPath = import_node_path.default.join(root, "src", "index.tsx");
|
|
179
|
+
let index = import_node_fs.default.readFileSync(indexPath, "utf8");
|
|
180
|
+
for (const method of methods) {
|
|
181
|
+
const oldExport = new RegExp(`^export\\s*\\{\\s*${method.name}\\s*\\}\\s*from\\s*['"]\\./${method.name}['"];?\\s*$`, "m");
|
|
182
|
+
index = index.replace(oldExport, "");
|
|
183
|
+
}
|
|
184
|
+
if (!index.includes("export * from './rust-generated';")) {
|
|
185
|
+
index = `${index.trimEnd()}
|
|
186
|
+
export * from './rust-generated';
|
|
187
|
+
`;
|
|
188
|
+
}
|
|
189
|
+
updates.set(indexPath, index);
|
|
190
|
+
return updates;
|
|
191
|
+
}
|
|
192
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
193
|
+
0 && (module.exports = {
|
|
194
|
+
hasDefaultMultiplyBody,
|
|
195
|
+
normalizeSignature,
|
|
196
|
+
renderProjectBindings,
|
|
197
|
+
replaceMarkedRegion,
|
|
198
|
+
replaceTemplateMethod,
|
|
199
|
+
updateCppFile,
|
|
200
|
+
validateExistingHandler
|
|
201
|
+
});
|
|
@@ -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 = [
|
|
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 ===
|
|
84
|
-
lines.push('#[
|
|
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(
|
|
87
|
-
|
|
123
|
+
lines.push(" ffi::catch_json(|| {");
|
|
88
124
|
for (const param of method.params) {
|
|
89
|
-
if (param.kind !==
|
|
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 ?
|
|
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 ===
|
|
142
|
+
} else if (method.returnType.kind === "void") {
|
|
108
143
|
lines.push(` ${call};`);
|
|
109
|
-
lines.push(
|
|
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(
|
|
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 ===
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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 ?
|
|
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}(
|
|
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 ===
|
|
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 !==
|
|
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 +
|
|
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 =
|
|
365
|
+
const decoded = "auto rnrsValue = rnrsFromRust(rnrsRuntime, rnrsResult);";
|
|
297
366
|
let resultLine;
|
|
298
367
|
switch (method.returnType.kind) {
|
|
299
|
-
case
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
case
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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:
|
|
382
|
+
default:
|
|
383
|
+
throw new Error(`No C++ return conversion for ${method.returnType.kind}.`);
|
|
310
384
|
}
|
|
311
|
-
return [signature +
|
|
385
|
+
return [signature + " {", ...setup, ` ${call}`, ...callbackErrorChecks, ` ${decoded}`, resultLine, "}"].join("\n");
|
|
312
386
|
});
|
|
313
387
|
return {
|
|
314
|
-
header: declarations.join(
|
|
315
|
-
source: `${renderCppHelpers()}
|
|
388
|
+
header: declarations.join("\n"),
|
|
389
|
+
source: `${renderCppHelpers()}
|
|
390
|
+
|
|
391
|
+
${definitions.join("\n\n")}`
|
|
316
392
|
};
|
|
317
393
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
394
|
+
function renderWrappers(methods, moduleName, crateName) {
|
|
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 '../rust/build/web/pkg/${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 =
|
|
425
|
+
const codegenTypesDeclaration = "declare namespace CodegenTypes { type UnsafeObject = object; }";
|
|
328
426
|
nativeLines.unshift(codegenTypesDeclaration);
|
|
329
|
-
|
|
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 ===
|
|
337
|
-
nativeLines.push(
|
|
338
|
-
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
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(
|
|
345
|
-
|
|
460
|
+
native: nativeLines.join("\n"),
|
|
461
|
+
web: webLines.join("\n")
|
|
346
462
|
};
|
|
347
463
|
}
|
|
348
|
-
|
|
349
|
-
module.exports = {
|
|
350
|
-
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
};
|
|
472
|
+
renderRustWasmModule,
|
|
473
|
+
renderWrappers
|
|
474
|
+
});
|