convex-rust 0.0.0-stage → 0.1.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.
Files changed (90) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +137 -2
  3. package/dist/cli/bin.d.ts +2 -0
  4. package/dist/cli/bin.js +200 -0
  5. package/dist/cli/build.d.ts +146 -0
  6. package/dist/cli/build.js +902 -0
  7. package/dist/cli/demangle.d.ts +7 -0
  8. package/dist/cli/demangle.js +328 -0
  9. package/dist/cli/diagnostics.d.ts +34 -0
  10. package/dist/cli/diagnostics.js +296 -0
  11. package/dist/cli/generate.d.ts +29 -0
  12. package/dist/cli/generate.js +82 -0
  13. package/dist/cli/manifest.d.ts +24 -0
  14. package/dist/cli/manifest.js +75 -0
  15. package/dist/cli/rustApi.d.ts +78 -0
  16. package/dist/cli/rustApi.js +609 -0
  17. package/dist/cli/scaffold.d.ts +22 -0
  18. package/dist/cli/scaffold.js +114 -0
  19. package/dist/cli/shim.d.ts +30 -0
  20. package/dist/cli/shim.js +140 -0
  21. package/dist/cli/size.d.ts +17 -0
  22. package/dist/cli/size.js +73 -0
  23. package/dist/cli/split.d.ts +20 -0
  24. package/dist/cli/split.js +140 -0
  25. package/dist/cli/symbols.d.ts +30 -0
  26. package/dist/cli/symbols.js +133 -0
  27. package/dist/cli/toolchain.d.ts +53 -0
  28. package/dist/cli/toolchain.js +167 -0
  29. package/dist/cli/validators.d.ts +6 -0
  30. package/dist/cli/validators.js +66 -0
  31. package/dist/cli/wasisdk.d.ts +82 -0
  32. package/dist/cli/wasisdk.js +220 -0
  33. package/dist/cli/wasmHints.d.ts +29 -0
  34. package/dist/cli/wasmHints.js +172 -0
  35. package/dist/runtime/base64.d.ts +4 -0
  36. package/dist/runtime/base64.js +77 -0
  37. package/dist/runtime/crypto.d.ts +7 -0
  38. package/dist/runtime/crypto.js +421 -0
  39. package/dist/runtime/fastjson.d.ts +3 -0
  40. package/dist/runtime/fastjson.js +170 -0
  41. package/dist/runtime/host.d.ts +73 -0
  42. package/dist/runtime/host.js +1230 -0
  43. package/dist/runtime/http.d.ts +40 -0
  44. package/dist/runtime/http.js +157 -0
  45. package/dist/runtime/index.d.ts +2 -0
  46. package/dist/runtime/index.js +1 -0
  47. package/dist/runtime/intl.d.ts +5 -0
  48. package/dist/runtime/intl.js +69 -0
  49. package/dist/runtime/tmpfs.d.ts +129 -0
  50. package/dist/runtime/tmpfs.js +624 -0
  51. package/dist/runtime/wasi.d.ts +33 -0
  52. package/dist/runtime/wasi.js +216 -0
  53. package/package.json +57 -4
  54. package/rust/convex_fn/Cargo.toml +32 -0
  55. package/rust/convex_fn/LICENSE +201 -0
  56. package/rust/convex_fn/build.rs +3 -0
  57. package/rust/convex_fn/src/args.rs +416 -0
  58. package/rust/convex_fn/src/crypto.rs +237 -0
  59. package/rust/convex_fn/src/ctx.rs +1044 -0
  60. package/rust/convex_fn/src/dns.rs +356 -0
  61. package/rust/convex_fn/src/error.rs +179 -0
  62. package/rust/convex_fn/src/executor.rs +577 -0
  63. package/rust/convex_fn/src/fanout.rs +106 -0
  64. package/rust/convex_fn/src/fs.rs +19 -0
  65. package/rust/convex_fn/src/function_ref.rs +219 -0
  66. package/rust/convex_fn/src/http.rs +441 -0
  67. package/rust/convex_fn/src/intl.rs +75 -0
  68. package/rust/convex_fn/src/lib.rs +242 -0
  69. package/rust/convex_fn/src/logging.rs +222 -0
  70. package/rust/convex_fn/src/native.rs +360 -0
  71. package/rust/convex_fn/src/net.rs +122 -0
  72. package/rust/convex_fn/src/sys.rs +178 -0
  73. package/rust/convex_fn/src/values.rs +121 -0
  74. package/rust/convex_fn/src/wire.rs +579 -0
  75. package/rust/convex_fn_macros/Cargo.toml +13 -0
  76. package/rust/convex_fn_macros/LICENSE +201 -0
  77. package/rust/convex_fn_macros/src/derive.rs +632 -0
  78. package/rust/convex_fn_macros/src/lib.rs +338 -0
  79. package/rust/convex_reqwest/Cargo.toml +69 -0
  80. package/rust/convex_reqwest/LICENSE +201 -0
  81. package/rust/convex_reqwest/src/blocking.rs +287 -0
  82. package/rust/convex_reqwest/src/body.rs +152 -0
  83. package/rust/convex_reqwest/src/client.rs +359 -0
  84. package/rust/convex_reqwest/src/config_types.rs +150 -0
  85. package/rust/convex_reqwest/src/error.rs +241 -0
  86. package/rust/convex_reqwest/src/into_url.rs +57 -0
  87. package/rust/convex_reqwest/src/lib.rs +42 -0
  88. package/rust/convex_reqwest/src/multipart.rs +140 -0
  89. package/rust/convex_reqwest/src/request.rs +358 -0
  90. package/rust/convex_reqwest/src/response.rs +310 -0
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Rust symbol demangler: v0 (`_R...`, spec: rust-lang.github.io/rfcs/2603-rust-symbol-name-mangling-v0.html)
3
+ * and legacy (`_ZN...E`). Best effort for display only: generics are shown, lifetimes and const
4
+ * details are simplified, and anything unparsable is returned unchanged.
5
+ */
6
+ /** Demangled name, or the input when it is not a Rust symbol (or cannot be parsed). */
7
+ export declare function demangle(name: string): string;
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Rust symbol demangler: v0 (`_R...`, spec: rust-lang.github.io/rfcs/2603-rust-symbol-name-mangling-v0.html)
3
+ * and legacy (`_ZN...E`). Best effort for display only: generics are shown, lifetimes and const
4
+ * details are simplified, and anything unparsable is returned unchanged.
5
+ */
6
+ const BASIC = {
7
+ a: "i8", b: "bool", c: "char", d: "f64", e: "str", f: "f32", h: "u8", i: "isize", j: "usize", l: "i32", m: "u32",
8
+ n: "i128", o: "u128", s: "i16", t: "u16", u: "()", v: "...", x: "i64", y: "u64", z: "!", p: "_",
9
+ };
10
+ const B62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
11
+ class Fail extends Error {
12
+ }
13
+ class V0 {
14
+ s;
15
+ i = 0;
16
+ depth = 0;
17
+ constructor(s) {
18
+ this.s = s;
19
+ }
20
+ peek() {
21
+ return this.s[this.i] ?? "";
22
+ }
23
+ next() {
24
+ if (this.i >= this.s.length)
25
+ throw new Fail();
26
+ return this.s[this.i++];
27
+ }
28
+ /** `<base-62-number> = {digit | lower | upper} "_"`: "_" is 0, otherwise the value plus one. */
29
+ base62() {
30
+ if (this.peek() === "_") {
31
+ this.i++;
32
+ return 0;
33
+ }
34
+ let n = 0;
35
+ for (;;) {
36
+ const c = this.next();
37
+ if (c === "_")
38
+ return n + 1;
39
+ const d = B62.indexOf(c);
40
+ if (d < 0)
41
+ throw new Fail();
42
+ n = n * 62 + d;
43
+ }
44
+ }
45
+ /** Optional `s<base62>` disambiguator. */
46
+ disambiguator() {
47
+ if (this.peek() === "s") {
48
+ this.i++;
49
+ this.base62();
50
+ }
51
+ }
52
+ ident() {
53
+ this.disambiguator();
54
+ if (this.peek() === "u")
55
+ this.i++; // punycode: shown undecoded
56
+ let len = 0;
57
+ let digits = 0;
58
+ while (/[0-9]/.test(this.peek())) {
59
+ len = len * 10 + Number(this.next());
60
+ digits++;
61
+ if (len === 0)
62
+ break; // "0" is the empty name (closures)
63
+ }
64
+ if (digits === 0)
65
+ throw new Fail();
66
+ if (this.peek() === "_")
67
+ this.i++;
68
+ if (this.i + len > this.s.length)
69
+ throw new Fail();
70
+ const out = this.s.slice(this.i, this.i + len);
71
+ this.i += len;
72
+ return out;
73
+ }
74
+ backref(f) {
75
+ const off = this.base62();
76
+ if (off >= this.i)
77
+ throw new Fail();
78
+ const saved = this.i;
79
+ this.i = off;
80
+ try {
81
+ return f();
82
+ }
83
+ finally {
84
+ this.i = saved;
85
+ }
86
+ }
87
+ enter(f) {
88
+ if (++this.depth > 120)
89
+ throw new Fail();
90
+ try {
91
+ return f();
92
+ }
93
+ finally {
94
+ this.depth--;
95
+ }
96
+ }
97
+ path() {
98
+ return this.enter(() => {
99
+ const c = this.next();
100
+ switch (c) {
101
+ case "C":
102
+ return this.ident();
103
+ case "M": {
104
+ this.disambiguator();
105
+ this.path();
106
+ return `<${this.type()}>`;
107
+ }
108
+ case "X": {
109
+ this.disambiguator();
110
+ this.path();
111
+ const ty = this.type();
112
+ return `<${ty} as ${this.path()}>`;
113
+ }
114
+ case "Y": {
115
+ const ty = this.type();
116
+ return `<${ty} as ${this.path()}>`;
117
+ }
118
+ case "N": {
119
+ const ns = this.next();
120
+ const parent = this.path();
121
+ const name = this.ident();
122
+ if (ns === "C")
123
+ return `${parent}::{closure${name ? `:${name}` : ""}}`;
124
+ if (ns === "S")
125
+ return `${parent}::{shim${name ? `:${name}` : ""}}`;
126
+ return `${parent}::${name}`;
127
+ }
128
+ case "I": {
129
+ const p = this.path();
130
+ const args = [];
131
+ while (this.peek() !== "E") {
132
+ const a = this.genericArg();
133
+ if (a !== null)
134
+ args.push(a);
135
+ }
136
+ this.i++;
137
+ return args.length ? `${p}::<${args.join(", ")}>` : p;
138
+ }
139
+ case "B":
140
+ return this.backref(() => this.path());
141
+ default:
142
+ throw new Fail();
143
+ }
144
+ });
145
+ }
146
+ genericArg() {
147
+ const c = this.peek();
148
+ if (c === "L") {
149
+ this.i++;
150
+ this.base62();
151
+ return null;
152
+ }
153
+ if (c === "K") {
154
+ this.i++;
155
+ return this.konst();
156
+ }
157
+ return this.type();
158
+ }
159
+ konst() {
160
+ const c = this.peek();
161
+ if (c === "p") {
162
+ this.i++;
163
+ return "_";
164
+ }
165
+ if (c === "B") {
166
+ this.i++;
167
+ return this.backref(() => this.konst());
168
+ }
169
+ const ty = this.type();
170
+ let neg = false;
171
+ if (this.peek() === "n") {
172
+ this.i++;
173
+ neg = true;
174
+ }
175
+ let hex = "";
176
+ while (this.peek() !== "_")
177
+ hex += this.next();
178
+ this.i++;
179
+ if (ty === "bool")
180
+ return hex === "1" ? "true" : "false";
181
+ return (neg ? "-" : "") + (hex === "" ? "0" : BigInt("0x" + hex).toString());
182
+ }
183
+ lifetime() {
184
+ if (this.peek() === "L") {
185
+ this.i++;
186
+ this.base62();
187
+ }
188
+ }
189
+ type() {
190
+ return this.enter(() => {
191
+ const c = this.peek();
192
+ if (c >= "a" && c <= "z" && c in BASIC) {
193
+ this.i++;
194
+ return BASIC[c];
195
+ }
196
+ switch (c) {
197
+ case "A": {
198
+ this.i++;
199
+ const t = this.type();
200
+ return `[${t}; ${this.konst()}]`;
201
+ }
202
+ case "S":
203
+ this.i++;
204
+ return `[${this.type()}]`;
205
+ case "T": {
206
+ this.i++;
207
+ const ts = [];
208
+ while (this.peek() !== "E")
209
+ ts.push(this.type());
210
+ this.i++;
211
+ return `(${ts.join(", ")}${ts.length === 1 ? "," : ""})`;
212
+ }
213
+ case "R":
214
+ case "Q": {
215
+ this.i++;
216
+ this.lifetime();
217
+ return `&${c === "Q" ? "mut " : ""}${this.type()}`;
218
+ }
219
+ case "P":
220
+ case "O":
221
+ this.i++;
222
+ return `*${c === "O" ? "mut" : "const"} ${this.type()}`;
223
+ case "F": {
224
+ this.i++;
225
+ if (this.peek() === "G") {
226
+ this.i++;
227
+ this.base62();
228
+ }
229
+ if (this.peek() === "U")
230
+ this.i++;
231
+ if (this.peek() === "K") {
232
+ this.i++;
233
+ if (this.peek() === "C")
234
+ this.i++;
235
+ else
236
+ this.ident();
237
+ }
238
+ const ps = [];
239
+ while (this.peek() !== "E")
240
+ ps.push(this.type());
241
+ this.i++;
242
+ const ret = this.type();
243
+ return `fn(${ps.join(", ")})${ret === "()" ? "" : ` -> ${ret}`}`;
244
+ }
245
+ case "D": {
246
+ this.i++;
247
+ if (this.peek() === "G") {
248
+ this.i++;
249
+ this.base62();
250
+ }
251
+ const bounds = [];
252
+ while (this.peek() !== "E") {
253
+ let b = this.path();
254
+ const assoc = [];
255
+ while (this.peek() === "p") {
256
+ this.i++;
257
+ const n = this.ident();
258
+ assoc.push(`${n} = ${this.type()}`);
259
+ }
260
+ if (assoc.length)
261
+ b += `<${assoc.join(", ")}>`;
262
+ bounds.push(b);
263
+ }
264
+ this.i++;
265
+ this.lifetime();
266
+ return `dyn ${bounds.join(" + ")}`;
267
+ }
268
+ case "B":
269
+ this.i++;
270
+ return this.backref(() => this.type());
271
+ default:
272
+ return this.path();
273
+ }
274
+ });
275
+ }
276
+ }
277
+ function demangleV0(name) {
278
+ const m = name.match(/^_R(\d*)/);
279
+ if (!m)
280
+ return name;
281
+ // Backref offsets count from the start of the symbol after `_R` (and the optional version digits).
282
+ const body = name.slice(m[0].length).replace(/\.[A-Za-z].*$/, "");
283
+ const p = new V0(body);
284
+ try {
285
+ return p.path();
286
+ }
287
+ catch {
288
+ return name;
289
+ }
290
+ }
291
+ const LEGACY_ESCAPES = {
292
+ SP: "@", BP: "*", RF: "&", LT: "<", GT: ">", LP: "(", RP: ")", C: ",", u20: " ", u27: "'", u5b: "[", u5d: "]", u7b: "{", u7d: "}", u3b: ";", u2b: "+", u22: '"',
293
+ };
294
+ function demangleLegacy(name) {
295
+ const s = name.replace(/\.(?:llvm|cold|part|isra|constprop)[\w.]*$/, "");
296
+ if (!s.startsWith("_ZN") || !s.endsWith("E"))
297
+ return name;
298
+ const parts = [];
299
+ let i = 3;
300
+ const end = s.length - 1;
301
+ while (i < end) {
302
+ let j = i;
303
+ while (j < end && s.charCodeAt(j) >= 48 && s.charCodeAt(j) <= 57)
304
+ j++;
305
+ if (j === i)
306
+ return name;
307
+ const len = Number(s.slice(i, j));
308
+ if (j + len > end)
309
+ return name;
310
+ parts.push(s.slice(j, j + len));
311
+ i = j + len;
312
+ }
313
+ if (parts.length > 1 && /^h[0-9a-f]{16}$/.test(parts[parts.length - 1]))
314
+ parts.pop();
315
+ return parts
316
+ .map((p) => (p.startsWith("_$") ? p.slice(1) : p)
317
+ .replace(/\.\./g, "::")
318
+ .replace(/\$([A-Za-z0-9]+)\$/g, (m, k) => LEGACY_ESCAPES[k] ?? m))
319
+ .join("::");
320
+ }
321
+ /** Demangled name, or the input when it is not a Rust symbol (or cannot be parsed). */
322
+ export function demangle(name) {
323
+ if (name.startsWith("_R"))
324
+ return demangleV0(name);
325
+ if (name.startsWith("_ZN"))
326
+ return demangleLegacy(name);
327
+ return name;
328
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Friendly explanations for cargo failures when building for wasm32-wasip1.
3
+ * Cargo's own output is always kept; the hints are appended after it.
4
+ *
5
+ * Data-driven: each {@link Rule} looks at the captured stderr and returns zero
6
+ * or more {@link Finding}s. A finding may name a crate, in which case the
7
+ * caller can show who pulls it in (`cargo tree -i <crate>`).
8
+ */
9
+ export interface Finding {
10
+ /** Stable rule id. */
11
+ rule: string;
12
+ /** Headline: what went wrong. */
13
+ title: string;
14
+ /** Actionable advice, one bullet per entry. */
15
+ advice: string[];
16
+ /** Crate worth running `cargo tree -i` on. */
17
+ crate?: string;
18
+ }
19
+ export interface Rule {
20
+ id: string;
21
+ match: (stderr: string) => Finding[];
22
+ }
23
+ export declare const RULES: Rule[];
24
+ /** Findings for a failed `cargo build` (stderr as captured). */
25
+ export declare function diagnoseCargoFailure(stderr: string): Finding[];
26
+ /** Arguments of the `cargo tree` call that shows who depends on `crate`. */
27
+ export declare function cargoTreeArgs(crate: string): string[];
28
+ /** Human-readable text for findings; `trees` maps crate -> `cargo tree` output. */
29
+ export declare function formatFindings(findings: Finding[], trees?: Record<string, string>): string;
30
+ /**
31
+ * Hint for a runtime (host) error message from a Rust action, for cases the
32
+ * build cannot catch. Returns null when the text is not recognised.
33
+ */
34
+ export declare function diagnoseRuntimeError(text: string): string | null;
@@ -0,0 +1,296 @@
1
+ /**
2
+ * Friendly explanations for cargo failures when building for wasm32-wasip1.
3
+ * Cargo's own output is always kept; the hints are appended after it.
4
+ *
5
+ * Data-driven: each {@link Rule} looks at the captured stderr and returns zero
6
+ * or more {@link Finding}s. A finding may name a crate, in which case the
7
+ * caller can show who pulls it in (`cargo tree -i <crate>`).
8
+ */
9
+ const TARGET = "wasm32-wasip1";
10
+ const NODE_FALLBACK = 'If you cannot avoid it, move that work to a `"use node"` action and call it with `ctx.run_action`.';
11
+ const WASI_SDK_ADVICE = "It builds with a wasi-sdk C compiler: run `npx convex-rust build --wasi-sdk` (downloads ~180 MB once to ~/.cache/convex-rust, or set WASI_SDK_PATH to your own wasi-sdk). Verified to build and run for this crate.";
12
+ /** Crates with C code that cannot be compiled for wasm32-wasip1 as-is. */
13
+ const C_DEPS = {
14
+ "openssl-sys": {
15
+ what: "OpenSSL",
16
+ advice: [
17
+ 'Switch the crate that pulls it in to a rustls/pure-Rust feature (for example `default-features = false, features = ["rustls-tls"]`), or drop TLS: HTTP goes through the built-in `reqwest` shim (`ctx.fetch`), which needs no TLS stack.',
18
+ NODE_FALLBACK,
19
+ ],
20
+ },
21
+ ring: {
22
+ what: "ring (C and assembly)",
23
+ advice: [
24
+ WASI_SDK_ADVICE,
25
+ "Or disable the feature that needs `ring`, or pick a pure-Rust backend (RustCrypto: `sha2`, `hmac`, `aes-gcm`, ...).",
26
+ "For `jsonwebtoken`, enable the `rust_crypto` feature instead of `aws_lc_rs`.",
27
+ "For AWS services use the `convex_aws` crate (HTTP over `ctx.fetch`) instead of the SDK's default TLS client.",
28
+ NODE_FALLBACK,
29
+ ],
30
+ },
31
+ "aws-lc-sys": {
32
+ what: "AWS-LC (C)",
33
+ advice: [
34
+ "For AWS services use the `convex_aws` crate (HTTP over `ctx.fetch`); do not enable the SDK's default HTTPS connector.",
35
+ "For `jsonwebtoken`, enable the `rust_crypto` feature instead of `aws_lc_rs`.",
36
+ "For `rustls`, avoid the `aws_lc_rs` provider (use `default-features = false`), or avoid rustls: outgoing HTTP does not need it here.",
37
+ NODE_FALLBACK,
38
+ ],
39
+ },
40
+ "libsqlite3-sys": {
41
+ what: "SQLite (C)",
42
+ advice: [
43
+ `${WASI_SDK_ADVICE} (\`rusqlite\` with the \`bundled\` feature; in-memory databases only, the host has no filesystem, and it adds ~1.4 MB to the wasm.)`,
44
+ "Convex is already your database: store the data in Convex tables via `ctx.run_query` / `ctx.run_mutation`.",
45
+ 'If you need SQLite itself, call a `"use node"` action with `ctx.run_action`.',
46
+ ],
47
+ },
48
+ "zstd-sys": {
49
+ what: "zstd (C)",
50
+ advice: [
51
+ WASI_SDK_ADVICE,
52
+ "Or use a pure-Rust codec (`ruzstd`, `lz4_flex`) or disable the dependency's zstd feature.",
53
+ NODE_FALLBACK,
54
+ ],
55
+ },
56
+ "lzma-sys": {
57
+ what: "liblzma (C)",
58
+ advice: [
59
+ "Use a pure-Rust codec (`lzma-rs`) or disable the dependency's lzma feature.",
60
+ ],
61
+ },
62
+ "bzip2-sys": {
63
+ what: "libbz2 (C)",
64
+ advice: [
65
+ "Use `bzip2` with the pure-Rust `libbz2-rs-sys` backend, or disable the feature.",
66
+ ],
67
+ },
68
+ "libz-sys": {
69
+ what: "zlib (C)",
70
+ advice: ["Use `flate2` with the `rust_backend` (miniz_oxide) feature."],
71
+ },
72
+ };
73
+ const GENERIC_C_ADVICE = [
74
+ "If `wasi-sdk` is not installed, try `npx convex-rust build --wasi-sdk` (or set WASI_SDK_PATH): many C crates build with it (verified: zstd-sys, libsqlite3-sys bundled, ring 0.17).",
75
+ "Look for a feature flag on the dependency that selects a pure-Rust implementation, or replace the crate.",
76
+ `Only zstd-sys, libsqlite3-sys and ring are verified to build for ${TARGET}. ${NODE_FALLBACK}`,
77
+ ];
78
+ /** Raw-socket database/network drivers: no sockets in v1. */
79
+ const SOCKET_CRATES = [
80
+ "sqlx",
81
+ "tokio-postgres",
82
+ "postgres",
83
+ "mongodb",
84
+ "redis",
85
+ "mysql_async",
86
+ "mysql",
87
+ "diesel",
88
+ "lettre",
89
+ "rdkafka",
90
+ "lapin",
91
+ "async-nats",
92
+ "tiberius",
93
+ "scylla",
94
+ ];
95
+ const SOCKET_ADVICE = [
96
+ "`use rust` v1 has no raw sockets (no TCP/UDP/DNS), so crates that open their own connections cannot run.",
97
+ "Use an HTTP-based driver or the vendor's HTTP API, which works through `reqwest`/`ctx.fetch` (for example Neon or PlanetScale serverless HTTP drivers, Supabase REST, Upstash Redis REST).",
98
+ 'Or do the socket work in a `"use node"` action and call it with `ctx.run_action`.',
99
+ ];
100
+ const THREAD_ADVICE = [
101
+ "`use rust` runs single-threaded: rayon's thread pool and `std::thread::spawn` compile but cannot start threads at runtime.",
102
+ 'Replace `par_iter()` with `iter()`, or run the parallel work in a `"use node"` action via `ctx.run_action`.',
103
+ ];
104
+ function compilingCrates(stderr) {
105
+ const out = new Set();
106
+ for (const m of stderr.matchAll(/^\s*(?:Compiling|Checking)\s+([\w.-]+)\s+v/gm)) {
107
+ out.add(m[1]);
108
+ }
109
+ for (const m of stderr.matchAll(/could not compile `([\w.-]+)`/g)) {
110
+ out.add(m[1]);
111
+ }
112
+ return out;
113
+ }
114
+ function failedBuildScripts(stderr) {
115
+ return [
116
+ ...stderr.matchAll(/failed to run custom build command for `([\w.-]+) v/g),
117
+ ].map((m) => m[1]);
118
+ }
119
+ export const RULES = [
120
+ {
121
+ id: "missing-target",
122
+ match: (s) => /can't find crate for `(std|core)`|target may not be installed|is not installed for the toolchain|does not support target/.test(s)
123
+ ? [
124
+ {
125
+ rule: "missing-target",
126
+ title: `The ${TARGET} Rust target is not installed`,
127
+ advice: [`Run: rustup target add ${TARGET}`],
128
+ },
129
+ ]
130
+ : [],
131
+ },
132
+ {
133
+ id: "c-dep",
134
+ match: (s) => {
135
+ const out = [];
136
+ for (const name of new Set(failedBuildScripts(s))) {
137
+ if (name === "psm")
138
+ continue;
139
+ const known = C_DEPS[name];
140
+ if (!known && !name.endsWith("-sys"))
141
+ continue;
142
+ out.push({
143
+ rule: "c-dep",
144
+ title: known
145
+ ? `\`${name}\` needs a C library (${known.what}) that is not available for ${TARGET}`
146
+ : `\`${name}\` builds native code that is not available for ${TARGET}`,
147
+ advice: known ? known.advice : GENERIC_C_ADVICE,
148
+ crate: name,
149
+ });
150
+ }
151
+ return out;
152
+ },
153
+ },
154
+ {
155
+ id: "c-toolchain",
156
+ match: (s) => {
157
+ const isC = /failed to find tool "[^"]*"|wasm32-wasi[\w-]*-clang|No available targets are compatible with triple "wasm32|error occurred in cc-rs/.test(s);
158
+ if (!isC)
159
+ return [];
160
+ const failed = failedBuildScripts(s);
161
+ // Already reported with specific advice by the `c-dep` rule.
162
+ if (failed.some((c) => C_DEPS[c] || c.endsWith("-sys")))
163
+ return [];
164
+ const crate = failed.find((c) => c !== "psm");
165
+ return [
166
+ {
167
+ rule: "c-toolchain",
168
+ title: `A dependency compiles C code and no C compiler for ${TARGET} was found`,
169
+ advice: GENERIC_C_ADVICE,
170
+ ...(crate ? { crate } : {}),
171
+ },
172
+ ];
173
+ },
174
+ },
175
+ {
176
+ id: "sockets",
177
+ match: (s) => {
178
+ const socketError = /Socket2 doesn't support the compile target|could not find `(net|unix)` in `(sys|mio)`|unresolved import `(mio|tokio)::net`|mio[^\n]*(not supported|unsupported)[^\n]*(platform|target)|(socket2|mio)[^\n]*not supported on this platform/i.test(s);
179
+ if (!socketError)
180
+ return [];
181
+ const crates = compilingCrates(s);
182
+ const drivers = SOCKET_CRATES.filter((c) => crates.has(c));
183
+ return [
184
+ {
185
+ rule: "sockets",
186
+ title: drivers.length > 0
187
+ ? `${drivers.map((d) => `\`${d}\``).join(", ")} need raw sockets, which ${TARGET} does not have`
188
+ : `A dependency needs raw sockets (socket2 / mio / tokio net), which ${TARGET} does not have`,
189
+ advice: SOCKET_ADVICE,
190
+ crate: /socket2/i.test(s) ? "socket2" : "mio",
191
+ },
192
+ ];
193
+ },
194
+ },
195
+ {
196
+ id: "getrandom",
197
+ match: (s) => /getrandom/.test(s) &&
198
+ /the wasm\d*-unknown-unknown targets are not supported by default|target is not supported|unsupported target|wasm_js/i.test(s)
199
+ ? [
200
+ {
201
+ rule: "getrandom",
202
+ title: "`getrandom` rejects the target it is being built for",
203
+ advice: [
204
+ `On ${TARGET} getrandom needs no \`js\` / \`wasm_js\` feature. This error usually means the build is not for ${TARGET}: check that \`.cargo/config.toml\` still has \`[build] target = "${TARGET}"\` and that no \`--target\` override or \`getrandom_backend\` cfg in RUSTFLAGS is set.`,
205
+ "If it persists, run `cargo update -p getrandom` (very old releases do not know WASI), and make sure no dependency enables the `custom` or `js` backend.",
206
+ ],
207
+ crate: "getrandom",
208
+ },
209
+ ]
210
+ : [],
211
+ },
212
+ {
213
+ id: "psm",
214
+ match: (s) => /section too large/i.test(s) ||
215
+ failedBuildScripts(s).includes("psm") ||
216
+ /could not compile `psm`/.test(s)
217
+ ? [
218
+ {
219
+ rule: "psm",
220
+ title: "`psm` (pulled in by `stacker`) fails to build for wasm",
221
+ advice: [
222
+ "Commit `Cargo.lock` and pin a working version of the dependent crate (`cargo update -p <crate> --precise <version>`), or drop the dependency that uses `stacker`.",
223
+ ],
224
+ crate: "psm",
225
+ },
226
+ ]
227
+ : [],
228
+ },
229
+ {
230
+ id: "rayon",
231
+ match: (s) => /could not compile `(rayon|rayon-core)`/.test(s)
232
+ ? [
233
+ {
234
+ rule: "rayon",
235
+ title: "`rayon` needs OS threads, which are not available in `use rust`",
236
+ advice: THREAD_ADVICE,
237
+ crate: "rayon",
238
+ },
239
+ ]
240
+ : [],
241
+ },
242
+ ];
243
+ /** Findings for a failed `cargo build` (stderr as captured). */
244
+ export function diagnoseCargoFailure(stderr) {
245
+ const seen = new Set();
246
+ const out = [];
247
+ for (const rule of RULES) {
248
+ for (const f of rule.match(stderr)) {
249
+ const key = `${f.rule}:${f.crate ?? ""}`;
250
+ if (seen.has(key))
251
+ continue;
252
+ seen.add(key);
253
+ out.push(f);
254
+ }
255
+ }
256
+ return out;
257
+ }
258
+ /** Arguments of the `cargo tree` call that shows who depends on `crate`. */
259
+ export function cargoTreeArgs(crate) {
260
+ return ["tree", "-i", crate, "--target", TARGET, "-e", "normal"];
261
+ }
262
+ /** Human-readable text for findings; `trees` maps crate -> `cargo tree` output. */
263
+ export function formatFindings(findings, trees = {}) {
264
+ const lines = [];
265
+ for (const f of findings) {
266
+ lines.push("", `Hint: ${f.title}.`);
267
+ for (const a of f.advice)
268
+ lines.push(` - ${a}`);
269
+ if (!f.crate)
270
+ continue;
271
+ const tree = trees[f.crate]?.trimEnd();
272
+ if (tree) {
273
+ lines.push(` Who depends on \`${f.crate}\` (cargo ${cargoTreeArgs(f.crate).join(" ")}):`, ...tree
274
+ .split("\n")
275
+ .slice(0, 25)
276
+ .map((l) => ` ${l}`));
277
+ }
278
+ else {
279
+ lines.push(` To see who pulls it in: cargo ${cargoTreeArgs(f.crate).join(" ")}`);
280
+ }
281
+ }
282
+ return lines.join("\n");
283
+ }
284
+ /**
285
+ * Hint for a runtime (host) error message from a Rust action, for cases the
286
+ * build cannot catch. Returns null when the text is not recognised.
287
+ */
288
+ export function diagnoseRuntimeError(text) {
289
+ if (/failed to spawn thread|ThreadPoolBuildError|global thread pool has not been initialized|operation not supported on this platform.*thread|thread::spawn/i.test(text)) {
290
+ return `Hint: this looks like a thread being started. ${THREAD_ADVICE.join(" ")}`;
291
+ }
292
+ if (/sock_(connect|open|accept)|Unsupported.*(tcp|socket)/i.test(text)) {
293
+ return `Hint: raw sockets are not available in \`use rust\`. ${SOCKET_ADVICE.slice(1).join(" ")}`;
294
+ }
295
+ return null;
296
+ }
@@ -0,0 +1,29 @@
1
+ import { RustManifestEntry } from "./manifest.js";
2
+ export declare const GENERATED_HEADER = "// @generated by convex-rust. Do not edit.";
3
+ export interface GenerateOptions {
4
+ entries: RustManifestEntry[];
5
+ /** File name of the wasm below `<out>/_wasm/`, e.g. `hello_rust.wasm`. */
6
+ wasmFile: string;
7
+ /**
8
+ * Directory of the generated files, relative to the Convex functions
9
+ * directory, with `/` separators (default `rust`).
10
+ */
11
+ outRelToFunctions?: string;
12
+ /**
13
+ * Short content hash of the wasm, written into each module. `convex dev` does not watch
14
+ * `.wasm` imports, so this makes a wasm-only change touch a watched `.ts` file.
15
+ */
16
+ wasmHash?: string;
17
+ /** Environment variable names the guest may read (`build --env`). */
18
+ env?: string[];
19
+ /**
20
+ * Per-module wasm (`build --split`): Convex module name -> wasm file below `<out>/_wasm/` and its
21
+ * content hash. Modules not listed use `wasmFile`.
22
+ */
23
+ moduleWasm?: Map<string, {
24
+ file: string;
25
+ hash: string;
26
+ }>;
27
+ }
28
+ /** Generated files, keyed by path relative to the out dir. */
29
+ export declare function generateFiles(opts: GenerateOptions): Map<string, string>;