convex-rust 0.0.0-stage → 0.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/LICENSE +201 -0
- package/README.md +137 -2
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +200 -0
- package/dist/cli/build.d.ts +146 -0
- package/dist/cli/build.js +902 -0
- package/dist/cli/demangle.d.ts +7 -0
- package/dist/cli/demangle.js +328 -0
- package/dist/cli/diagnostics.d.ts +34 -0
- package/dist/cli/diagnostics.js +296 -0
- package/dist/cli/generate.d.ts +29 -0
- package/dist/cli/generate.js +82 -0
- package/dist/cli/manifest.d.ts +24 -0
- package/dist/cli/manifest.js +75 -0
- package/dist/cli/rustApi.d.ts +78 -0
- package/dist/cli/rustApi.js +609 -0
- package/dist/cli/scaffold.d.ts +22 -0
- package/dist/cli/scaffold.js +114 -0
- package/dist/cli/shim.d.ts +30 -0
- package/dist/cli/shim.js +140 -0
- package/dist/cli/size.d.ts +17 -0
- package/dist/cli/size.js +73 -0
- package/dist/cli/split.d.ts +20 -0
- package/dist/cli/split.js +140 -0
- package/dist/cli/symbols.d.ts +30 -0
- package/dist/cli/symbols.js +133 -0
- package/dist/cli/toolchain.d.ts +53 -0
- package/dist/cli/toolchain.js +167 -0
- package/dist/cli/validators.d.ts +6 -0
- package/dist/cli/validators.js +66 -0
- package/dist/cli/wasisdk.d.ts +82 -0
- package/dist/cli/wasisdk.js +220 -0
- package/dist/cli/wasmHints.d.ts +29 -0
- package/dist/cli/wasmHints.js +172 -0
- package/dist/runtime/base64.d.ts +4 -0
- package/dist/runtime/base64.js +77 -0
- package/dist/runtime/crypto.d.ts +7 -0
- package/dist/runtime/crypto.js +421 -0
- package/dist/runtime/fastjson.d.ts +3 -0
- package/dist/runtime/fastjson.js +170 -0
- package/dist/runtime/host.d.ts +73 -0
- package/dist/runtime/host.js +1230 -0
- package/dist/runtime/http.d.ts +40 -0
- package/dist/runtime/http.js +157 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/intl.d.ts +5 -0
- package/dist/runtime/intl.js +69 -0
- package/dist/runtime/tmpfs.d.ts +129 -0
- package/dist/runtime/tmpfs.js +624 -0
- package/dist/runtime/wasi.d.ts +33 -0
- package/dist/runtime/wasi.js +216 -0
- package/package.json +66 -4
- package/rust/convex_fn/Cargo.toml +32 -0
- package/rust/convex_fn/LICENSE +201 -0
- package/rust/convex_fn/build.rs +3 -0
- package/rust/convex_fn/src/args.rs +416 -0
- package/rust/convex_fn/src/crypto.rs +237 -0
- package/rust/convex_fn/src/ctx.rs +1044 -0
- package/rust/convex_fn/src/dns.rs +356 -0
- package/rust/convex_fn/src/error.rs +179 -0
- package/rust/convex_fn/src/executor.rs +577 -0
- package/rust/convex_fn/src/fanout.rs +106 -0
- package/rust/convex_fn/src/fs.rs +19 -0
- package/rust/convex_fn/src/function_ref.rs +219 -0
- package/rust/convex_fn/src/http.rs +441 -0
- package/rust/convex_fn/src/intl.rs +75 -0
- package/rust/convex_fn/src/lib.rs +242 -0
- package/rust/convex_fn/src/logging.rs +222 -0
- package/rust/convex_fn/src/native.rs +360 -0
- package/rust/convex_fn/src/net.rs +122 -0
- package/rust/convex_fn/src/sys.rs +178 -0
- package/rust/convex_fn/src/values.rs +121 -0
- package/rust/convex_fn/src/wire.rs +579 -0
- package/rust/convex_fn_macros/Cargo.toml +13 -0
- package/rust/convex_fn_macros/LICENSE +201 -0
- package/rust/convex_fn_macros/src/derive.rs +632 -0
- package/rust/convex_fn_macros/src/lib.rs +338 -0
- package/rust/convex_reqwest/Cargo.toml +69 -0
- package/rust/convex_reqwest/LICENSE +201 -0
- package/rust/convex_reqwest/src/blocking.rs +287 -0
- package/rust/convex_reqwest/src/body.rs +152 -0
- package/rust/convex_reqwest/src/client.rs +359 -0
- package/rust/convex_reqwest/src/config_types.rs +150 -0
- package/rust/convex_reqwest/src/error.rs +241 -0
- package/rust/convex_reqwest/src/into_url.rs +57 -0
- package/rust/convex_reqwest/src/lib.rs +42 -0
- package/rust/convex_reqwest/src/multipart.rs +140 -0
- package/rust/convex_reqwest/src/request.rs +358 -0
- package/rust/convex_reqwest/src/response.rs +310 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wasi-sdk discovery / install, so crates that compile C (zstd-sys, libsqlite3-sys, ...) build for
|
|
3
|
+
* wasm32-wasip1. cc-rs reads `CC_wasm32_wasip1`, `AR_wasm32_wasip1` and `CFLAGS_wasm32_wasip1`
|
|
4
|
+
* (https://docs.rs/cc/latest/cc/#external-configuration-via-environment-variables).
|
|
5
|
+
*/
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { Readable } from "node:stream";
|
|
12
|
+
import { pipeline } from "node:stream/promises";
|
|
13
|
+
import { RUST_TARGET } from "./toolchain.js";
|
|
14
|
+
export const WASI_SDK_VERSION = "34.0";
|
|
15
|
+
const RELEASE_BASE = "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-34";
|
|
16
|
+
/** Pinned release archives (sha256 as published by GitHub for the release assets). */
|
|
17
|
+
export const WASI_SDK_ASSETS = {
|
|
18
|
+
"darwin-arm64": {
|
|
19
|
+
file: "wasi-sdk-34.0-arm64-macos.tar.gz",
|
|
20
|
+
sha256: "9c59398106b417f8f14913380fdf0097a8cc0ff4af9eb3ce0065a859e88d49e9",
|
|
21
|
+
size: 180322002,
|
|
22
|
+
},
|
|
23
|
+
"darwin-x64": {
|
|
24
|
+
file: "wasi-sdk-34.0-x86_64-macos.tar.gz",
|
|
25
|
+
sha256: "87d27fa8adc68dee59bfbf2e22a6d34ef717c34d6bf1d8af2a56fc929d9ce0eb",
|
|
26
|
+
size: 182668326,
|
|
27
|
+
},
|
|
28
|
+
"linux-arm64": {
|
|
29
|
+
file: "wasi-sdk-34.0-arm64-linux.tar.gz",
|
|
30
|
+
sha256: "f7e243dff54d60bcc576e94d6166b69f410f2500ae4a9ceef34315be10e77971",
|
|
31
|
+
size: 192255247,
|
|
32
|
+
},
|
|
33
|
+
"linux-x64": {
|
|
34
|
+
file: "wasi-sdk-34.0-x86_64-linux.tar.gz",
|
|
35
|
+
sha256: "b761e3a0721dbae9c09a0059e5fdb2bf917d1b4a8a7b430fb3b5aafb0984b2c4",
|
|
36
|
+
size: 192383077,
|
|
37
|
+
},
|
|
38
|
+
"win32-x64": {
|
|
39
|
+
file: "wasi-sdk-34.0-x86_64-windows.tar.gz",
|
|
40
|
+
sha256: "cccb5c323a9b34f0349a9b09e8804a0a7632c68c3310f4b5f437ed57d7e71d8f",
|
|
41
|
+
size: 619003408,
|
|
42
|
+
},
|
|
43
|
+
"win32-arm64": {
|
|
44
|
+
file: "wasi-sdk-34.0-arm64-windows.tar.gz",
|
|
45
|
+
sha256: "45e1c71f3e965621e7b98ebe1d37b0e4b1f77f3e8072113ffb4534e67b1a4b7c",
|
|
46
|
+
size: 620307231,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
/** `~/.cache/convex-rust` (override: `CONVEX_RUST_CACHE_DIR`; honours `XDG_CACHE_HOME`). */
|
|
50
|
+
export function cacheRoot(env = process.env) {
|
|
51
|
+
if (env.CONVEX_RUST_CACHE_DIR)
|
|
52
|
+
return env.CONVEX_RUST_CACHE_DIR;
|
|
53
|
+
const base = env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache");
|
|
54
|
+
return path.join(base, "convex-rust");
|
|
55
|
+
}
|
|
56
|
+
export function cachedSdkDir(env = process.env) {
|
|
57
|
+
return path.join(cacheRoot(env), `wasi-sdk-${WASI_SDK_VERSION}`);
|
|
58
|
+
}
|
|
59
|
+
const exe = process.platform === "win32" ? ".exe" : "";
|
|
60
|
+
/** True when `dir` looks like a wasi-sdk (clang + sysroot). */
|
|
61
|
+
export function isWasiSdk(dir) {
|
|
62
|
+
return (fs.existsSync(path.join(dir, "bin", `clang${exe}`)) &&
|
|
63
|
+
fs.existsSync(path.join(dir, "share", "wasi-sysroot")));
|
|
64
|
+
}
|
|
65
|
+
/** `WASI_SDK_PATH` first, then the cache dir. Null when neither holds a usable SDK. */
|
|
66
|
+
export function findWasiSdk(env = process.env) {
|
|
67
|
+
if (env.WASI_SDK_PATH && isWasiSdk(env.WASI_SDK_PATH))
|
|
68
|
+
return { dir: env.WASI_SDK_PATH, source: "env" };
|
|
69
|
+
const c = cachedSdkDir(env);
|
|
70
|
+
if (isWasiSdk(c))
|
|
71
|
+
return { dir: c, source: "cache" };
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Environment that points cc-rs at the SDK. Variables the user already set are left alone.
|
|
76
|
+
* `clang` is used as `ar` fallback only when `llvm-ar` is missing.
|
|
77
|
+
*/
|
|
78
|
+
export function wasiSdkEnv(sdkDir, env = process.env) {
|
|
79
|
+
const t = RUST_TARGET.replace(/-/g, "_");
|
|
80
|
+
const out = {};
|
|
81
|
+
const bin = (n) => path.join(sdkDir, "bin", n + exe);
|
|
82
|
+
const sysroot = path.join(sdkDir, "share", "wasi-sysroot");
|
|
83
|
+
const set = (k, v) => {
|
|
84
|
+
if (!env[k] && !env[k.replace(t, RUST_TARGET)])
|
|
85
|
+
out[k] = v;
|
|
86
|
+
};
|
|
87
|
+
set(`CC_${t}`, bin("clang"));
|
|
88
|
+
set(`CXX_${t}`, bin("clang++"));
|
|
89
|
+
set(`AR_${t}`, bin("llvm-ar"));
|
|
90
|
+
set(`CFLAGS_${t}`, `--sysroot=${sysroot}`);
|
|
91
|
+
set(`CXXFLAGS_${t}`, `--sysroot=${sysroot}`);
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Crates in the resolved (target-filtered) graph whose build script uses a C compiler driver
|
|
96
|
+
* (`cc`, `cmake`, `bindgen` is not counted). Their names, sorted.
|
|
97
|
+
*/
|
|
98
|
+
export function crateNamesWithCBuild(meta) {
|
|
99
|
+
const name = new Map(meta.packages.map((p) => [p.id, p.name]));
|
|
100
|
+
const out = new Set();
|
|
101
|
+
for (const node of meta.resolve?.nodes ?? []) {
|
|
102
|
+
for (const d of node.deps) {
|
|
103
|
+
const dep = name.get(d.pkg);
|
|
104
|
+
if (dep !== "cc" && dep !== "cmake")
|
|
105
|
+
continue;
|
|
106
|
+
if (!(d.dep_kinds ?? []).some((k) => k.kind === "build"))
|
|
107
|
+
continue;
|
|
108
|
+
const n = name.get(node.id);
|
|
109
|
+
if (n)
|
|
110
|
+
out.add(n);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return [...out].sort();
|
|
114
|
+
}
|
|
115
|
+
export function assetForPlatform(platform = process.platform, arch = process.arch) {
|
|
116
|
+
return WASI_SDK_ASSETS[`${platform}-${arch}`] ?? null;
|
|
117
|
+
}
|
|
118
|
+
async function sha256File(file) {
|
|
119
|
+
const h = createHash("sha256");
|
|
120
|
+
await pipeline(fs.createReadStream(file), h);
|
|
121
|
+
return h.digest("hex");
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Downloads the pinned wasi-sdk into the cache dir (sha256 checked) and returns its directory.
|
|
125
|
+
* An archive already present in `<cache>/dl` with the right hash is reused.
|
|
126
|
+
*/
|
|
127
|
+
export async function installWasiSdk(opts = {}) {
|
|
128
|
+
const env = opts.env ?? process.env;
|
|
129
|
+
const log = opts.log ?? ((m) => console.error(m));
|
|
130
|
+
const dest = cachedSdkDir(env);
|
|
131
|
+
if (isWasiSdk(dest))
|
|
132
|
+
return dest;
|
|
133
|
+
const asset = assetForPlatform();
|
|
134
|
+
if (!asset) {
|
|
135
|
+
throw new Error(`No pinned wasi-sdk build for ${process.platform}/${process.arch}. Download one from https://github.com/WebAssembly/wasi-sdk/releases and set WASI_SDK_PATH.`);
|
|
136
|
+
}
|
|
137
|
+
const dlDir = path.join(cacheRoot(env), "dl");
|
|
138
|
+
fs.mkdirSync(dlDir, { recursive: true });
|
|
139
|
+
const archive = path.join(dlDir, asset.file);
|
|
140
|
+
let have = fs.existsSync(archive) && (await sha256File(archive)) === asset.sha256;
|
|
141
|
+
if (!have) {
|
|
142
|
+
const url = `${RELEASE_BASE}/${asset.file}`;
|
|
143
|
+
log(`Downloading wasi-sdk ${WASI_SDK_VERSION} (${(asset.size / 1024 / 1024).toFixed(0)} MB) from ${url} ...`);
|
|
144
|
+
const res = await (opts.fetchImpl ?? fetch)(url);
|
|
145
|
+
if (!res.ok || !res.body)
|
|
146
|
+
throw new Error(`download failed: HTTP ${res.status} for ${url}`);
|
|
147
|
+
const tmp = `${archive}.${process.pid}.part`;
|
|
148
|
+
try {
|
|
149
|
+
await pipeline(Readable.fromWeb(res.body), fs.createWriteStream(tmp));
|
|
150
|
+
const got = await sha256File(tmp);
|
|
151
|
+
if (got !== asset.sha256)
|
|
152
|
+
throw new Error(`checksum mismatch for ${asset.file}: expected ${asset.sha256}, got ${got}`);
|
|
153
|
+
fs.renameSync(tmp, archive);
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
fs.rmSync(tmp, { force: true });
|
|
157
|
+
}
|
|
158
|
+
have = true;
|
|
159
|
+
}
|
|
160
|
+
const stage = fs.mkdtempSync(path.join(cacheRoot(env), ".wasi-sdk-"));
|
|
161
|
+
try {
|
|
162
|
+
log(`Extracting wasi-sdk to ${dest} ...`);
|
|
163
|
+
const r = spawnSync("tar", ["-xzf", archive, "-C", stage], { encoding: "utf8" });
|
|
164
|
+
if (r.status !== 0)
|
|
165
|
+
throw new Error(`tar failed: ${r.stderr || r.error?.message || r.status}`);
|
|
166
|
+
const top = fs.readdirSync(stage).find((n) => n.startsWith("wasi-sdk"));
|
|
167
|
+
if (!top || !isWasiSdk(path.join(stage, top)))
|
|
168
|
+
throw new Error("the archive does not contain a wasi-sdk");
|
|
169
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
170
|
+
fs.renameSync(path.join(stage, top), dest);
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
fs.rmSync(stage, { recursive: true, force: true });
|
|
174
|
+
}
|
|
175
|
+
fs.rmSync(archive, { force: true });
|
|
176
|
+
return dest;
|
|
177
|
+
}
|
|
178
|
+
/** `--wasi-sdk` / `--no-wasi-sdk` / `CONVEX_RUST_WASI_SDK=1|0`. Default: auto (download only if a C build is detected). */
|
|
179
|
+
export function wasiSdkMode(flags, env = process.env) {
|
|
180
|
+
if (flags.noWasiSdk || env.CONVEX_RUST_WASI_SDK === "0")
|
|
181
|
+
return "never";
|
|
182
|
+
if (flags.wasiSdk || env.CONVEX_RUST_WASI_SDK === "1")
|
|
183
|
+
return "always";
|
|
184
|
+
return "auto";
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Decides the cargo environment for C compilation. Uses an SDK that is already there (always, it is free);
|
|
188
|
+
* downloads one only when `mode` is "always", or "auto" and `cNames` (crates with a C build script) is non-empty.
|
|
189
|
+
*/
|
|
190
|
+
export async function prepareWasiSdk(args) {
|
|
191
|
+
const env = args.env ?? process.env;
|
|
192
|
+
if (args.mode === "never")
|
|
193
|
+
return {};
|
|
194
|
+
let sdk = findWasiSdk(env);
|
|
195
|
+
if (!sdk) {
|
|
196
|
+
if (args.mode === "auto") {
|
|
197
|
+
let names = [];
|
|
198
|
+
try {
|
|
199
|
+
names = await args.cNames();
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// detection is best effort
|
|
203
|
+
}
|
|
204
|
+
if (names.length === 0)
|
|
205
|
+
return {};
|
|
206
|
+
args.log(`C build scripts detected (${names.join(", ")}) and no wasi-sdk found; installing wasi-sdk ${WASI_SDK_VERSION} (disable with --no-wasi-sdk).`);
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
sdk = { dir: await (args.install ?? installWasiSdk)({ env, log: args.log }), source: "cache" };
|
|
210
|
+
}
|
|
211
|
+
catch (e) {
|
|
212
|
+
args.log(`warning: could not install wasi-sdk: ${e?.message ?? e}`);
|
|
213
|
+
return {};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const out = wasiSdkEnv(sdk.dir, env);
|
|
217
|
+
if (Object.keys(out).length > 0)
|
|
218
|
+
args.log(`using wasi-sdk at ${sdk.dir} for C dependencies`);
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface WasmImport {
|
|
2
|
+
module: string;
|
|
3
|
+
name: string;
|
|
4
|
+
kind: "func" | "table" | "memory" | "global" | "tag";
|
|
5
|
+
/** memory/table imports: limits flags (bit 1 shared, bit 2 64-bit). */
|
|
6
|
+
flags?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface WasmMemory {
|
|
9
|
+
flags: number;
|
|
10
|
+
imported: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface WasmInfo {
|
|
13
|
+
imports: WasmImport[];
|
|
14
|
+
memories: WasmMemory[];
|
|
15
|
+
exports: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare function scanWasm(wasm: Uint8Array): WasmInfo;
|
|
18
|
+
export interface Hint {
|
|
19
|
+
id: string;
|
|
20
|
+
title: string;
|
|
21
|
+
/** One-line fix. */
|
|
22
|
+
fix: string;
|
|
23
|
+
}
|
|
24
|
+
/** WASI functions the host implements (everything else answers ENOSYS), read from the runtime itself. */
|
|
25
|
+
export declare function hostWasiFunctions(): Set<string>;
|
|
26
|
+
/** Functions of the `convex` import module the host provides (src/runtime/host.ts). */
|
|
27
|
+
export declare const HOST_CONVEX_IMPORTS: Set<string>;
|
|
28
|
+
export declare function wasmHints(wasm: Uint8Array, implemented?: Set<string>): Hint[];
|
|
29
|
+
export declare function formatWasmHints(hints: Hint[], label?: string): string[];
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build-time scan of the compiled wasm's imports and memories for things that fail (or silently
|
|
3
|
+
* answer ENOSYS) on the Convex host. Only reads the binary format; nothing is instantiated.
|
|
4
|
+
*/
|
|
5
|
+
import { readLeb } from "./size.js";
|
|
6
|
+
import { makeWasi, LineBuffer } from "../runtime/wasi.js";
|
|
7
|
+
import { TmpFs } from "../runtime/tmpfs.js";
|
|
8
|
+
const KINDS = ["func", "table", "memory", "global", "tag"];
|
|
9
|
+
const dec = new TextDecoder();
|
|
10
|
+
function readName(b, pos) {
|
|
11
|
+
let len;
|
|
12
|
+
[len, pos] = readLeb(b, pos);
|
|
13
|
+
return [dec.decode(b.subarray(pos, pos + len)), pos + len];
|
|
14
|
+
}
|
|
15
|
+
/** Reads a `limits` entry; returns [flags, newPos]. */
|
|
16
|
+
function readLimits(b, pos) {
|
|
17
|
+
let flags;
|
|
18
|
+
[flags, pos] = readLeb(b, pos);
|
|
19
|
+
[, pos] = readLeb(b, pos);
|
|
20
|
+
if (flags & 1)
|
|
21
|
+
[, pos] = readLeb(b, pos);
|
|
22
|
+
return [flags, pos];
|
|
23
|
+
}
|
|
24
|
+
export function scanWasm(wasm) {
|
|
25
|
+
const info = { imports: [], memories: [], exports: [] };
|
|
26
|
+
let pos = 8;
|
|
27
|
+
while (pos < wasm.length) {
|
|
28
|
+
const id = wasm[pos++];
|
|
29
|
+
let size;
|
|
30
|
+
[size, pos] = readLeb(wasm, pos);
|
|
31
|
+
const end = pos + size;
|
|
32
|
+
let p = pos;
|
|
33
|
+
let count;
|
|
34
|
+
if (id === 2) {
|
|
35
|
+
[count, p] = readLeb(wasm, p);
|
|
36
|
+
for (let i = 0; i < count; i++) {
|
|
37
|
+
let module;
|
|
38
|
+
let name;
|
|
39
|
+
[module, p] = readName(wasm, p);
|
|
40
|
+
[name, p] = readName(wasm, p);
|
|
41
|
+
const kind = KINDS[wasm[p++]] ?? "func";
|
|
42
|
+
const imp = { module, name, kind };
|
|
43
|
+
if (kind === "func")
|
|
44
|
+
[, p] = readLeb(wasm, p);
|
|
45
|
+
else if (kind === "tag") {
|
|
46
|
+
p++;
|
|
47
|
+
[, p] = readLeb(wasm, p);
|
|
48
|
+
}
|
|
49
|
+
else if (kind === "table") {
|
|
50
|
+
p++; // reftype
|
|
51
|
+
[imp.flags, p] = readLimits(wasm, p);
|
|
52
|
+
}
|
|
53
|
+
else if (kind === "memory") {
|
|
54
|
+
[imp.flags, p] = readLimits(wasm, p);
|
|
55
|
+
info.memories.push({ flags: imp.flags, imported: true });
|
|
56
|
+
}
|
|
57
|
+
else
|
|
58
|
+
p += 2; // global: valtype, mutability
|
|
59
|
+
info.imports.push(imp);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else if (id === 5) {
|
|
63
|
+
[count, p] = readLeb(wasm, p);
|
|
64
|
+
for (let i = 0; i < count; i++) {
|
|
65
|
+
let flags;
|
|
66
|
+
[flags, p] = readLimits(wasm, p);
|
|
67
|
+
info.memories.push({ flags, imported: false });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else if (id === 7) {
|
|
71
|
+
[count, p] = readLeb(wasm, p);
|
|
72
|
+
for (let i = 0; i < count; i++) {
|
|
73
|
+
let name;
|
|
74
|
+
[name, p] = readName(wasm, p);
|
|
75
|
+
p++;
|
|
76
|
+
[, p] = readLeb(wasm, p);
|
|
77
|
+
info.exports.push(name);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
pos = end;
|
|
81
|
+
}
|
|
82
|
+
return info;
|
|
83
|
+
}
|
|
84
|
+
/** WASI functions the host implements (everything else answers ENOSYS), read from the runtime itself. */
|
|
85
|
+
export function hostWasiFunctions() {
|
|
86
|
+
const lb = new LineBuffer(() => { });
|
|
87
|
+
// The in-memory /tmp is part of the host's table (path_open, fd_prestat_*, fd_readdir ...).
|
|
88
|
+
const memory = () => undefined;
|
|
89
|
+
const fns = makeWasi({ memory, environ: [], line: () => { }, tmp: new TmpFs({ memory }) }, { 1: lb, 2: lb });
|
|
90
|
+
return new Set(Object.keys(fns));
|
|
91
|
+
}
|
|
92
|
+
/** Functions of the `convex` import module the host provides (src/runtime/host.ts). */
|
|
93
|
+
export const HOST_CONVEX_IMPORTS = new Set(["task_submit", "task_poll", "sync_syscall", "log"]);
|
|
94
|
+
const SOCKETS = /^sock_/;
|
|
95
|
+
// Only calls that open or change paths: wasi-libc/std import fd_close, fd_fdstat_get, fd_seek ... even for stdio.
|
|
96
|
+
const FS = /^(path_|fd_readdir$|fd_prestat_)/;
|
|
97
|
+
export function wasmHints(wasm, implemented = hostWasiFunctions()) {
|
|
98
|
+
const info = scanWasm(wasm);
|
|
99
|
+
const hints = [];
|
|
100
|
+
const wasi = info.imports.filter((i) => i.module === "wasi_snapshot_preview1" && i.kind === "func");
|
|
101
|
+
const missing = (re) => wasi.filter((i) => re.test(i.name) && !implemented.has(i.name)).map((i) => i.name);
|
|
102
|
+
const sock = missing(SOCKETS);
|
|
103
|
+
if (sock.length) {
|
|
104
|
+
hints.push({
|
|
105
|
+
id: "wasi-sockets",
|
|
106
|
+
title: `imports ${sock.join(", ")}: raw sockets (TCP/UDP/DNS) return ENOSYS on the Convex host`,
|
|
107
|
+
fix: 'use HTTP through reqwest/ctx.fetch instead of a socket driver, or run that part in a "use node" action (ctx.run_action).',
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const fs = missing(FS);
|
|
111
|
+
if (fs.length) {
|
|
112
|
+
hints.push({
|
|
113
|
+
id: "wasi-fs",
|
|
114
|
+
title: `imports ${fs.join(", ")}: std::fs and opening paths return ENOSYS (the host has no filesystem or VFS for them)`,
|
|
115
|
+
fix: "keep data in memory or in Convex tables/storage (ctx.storage); check dependencies that read config or temp files at runtime.",
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (wasi.some((i) => i.name === "proc_raise") && !implemented.has("proc_raise")) {
|
|
119
|
+
hints.push({
|
|
120
|
+
id: "wasi-proc-raise",
|
|
121
|
+
title: "imports proc_raise: raise()/abort-by-signal returns ENOSYS and the call keeps running",
|
|
122
|
+
fix: 'build with `panic = "abort"` and avoid C code that calls abort()/raise(); report errors as Result instead.',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const threads = info.imports.filter((i) => i.module === "wasi" && /thread/.test(i.name));
|
|
126
|
+
const shared = info.memories.some((m) => (m.flags & 2) !== 0);
|
|
127
|
+
if (threads.length || shared) {
|
|
128
|
+
hints.push({
|
|
129
|
+
id: "threads",
|
|
130
|
+
title: shared
|
|
131
|
+
? "uses shared memory (threads / atomics build): the host cannot start threads"
|
|
132
|
+
: `imports ${threads.map((t) => `${t.module}.${t.name}`).join(", ")}: the host cannot start threads`,
|
|
133
|
+
fix: 'remove `-C target-feature=+atomics` / wasi-threads settings and replace rayon/std::thread::spawn with sequential code, or use a "use node" action.',
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (info.memories.some((m) => (m.flags & 4) !== 0)) {
|
|
137
|
+
hints.push({
|
|
138
|
+
id: "memory64",
|
|
139
|
+
title: "uses a 64-bit memory (memory64), which the Convex runtime does not support",
|
|
140
|
+
fix: "build for wasm32-wasip1 only (no memory64 target features).",
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
const badHost = info.imports.filter((i) => i.module === "convex" && !HOST_CONVEX_IMPORTS.has(i.name)).map((i) => i.name);
|
|
144
|
+
if (badHost.length) {
|
|
145
|
+
hints.push({
|
|
146
|
+
id: "host-imports",
|
|
147
|
+
title: `needs host import(s) convex.${badHost.join(", convex.")} that the installed convex-rust runtime does not provide`,
|
|
148
|
+
fix: "the convex_fn crate is newer than the npm package: update convex-rust (npm i convex-rust@latest) or pin the crate to the matching version.",
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const foreign = info.imports.filter((i) => i.kind !== "memory" && i.module !== "convex" && i.module !== "wasi_snapshot_preview1" && i.module !== "wasi");
|
|
152
|
+
if (foreign.length) {
|
|
153
|
+
const names = foreign.slice(0, 6).map((i) => `${i.module}.${i.name}`);
|
|
154
|
+
hints.push({
|
|
155
|
+
id: "foreign-imports",
|
|
156
|
+
title: `imports ${names.join(", ")}${foreign.length > names.length ? ` (+${foreign.length - names.length} more)` : ""}, which the host does not provide: the module will fail to instantiate`,
|
|
157
|
+
fix: "these come from wasm-bindgen / emscripten / wasm32-unknown-unknown style crates: build only for wasm32-wasip1 and drop the dependency or its `js`/`wasm-bindgen` feature (`cargo tree -i <crate> --target wasm32-wasip1`).",
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
if (info.imports.some((i) => i.kind === "memory")) {
|
|
161
|
+
hints.push({
|
|
162
|
+
id: "imported-memory",
|
|
163
|
+
title: "imports its linear memory (env.memory), which the host does not provide",
|
|
164
|
+
fix: "do not pass `--import-memory` to the linker (check RUSTFLAGS / .cargo/config.toml).",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return hints;
|
|
168
|
+
}
|
|
169
|
+
export function formatWasmHints(hints, label = "") {
|
|
170
|
+
const l = label.trim().replace(/^\[|\]$/g, "");
|
|
171
|
+
return hints.map((h) => `warning:${l ? ` [${l}]` : ""} the wasm ${h.title}.\n fix: ${h.fix}`);
|
|
172
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Dependency-free base64 (the default Convex runtime has no Buffer).
|
|
2
|
+
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
3
|
+
const LOOKUP = new Int16Array(128).fill(-1);
|
|
4
|
+
for (let i = 0; i < ALPHABET.length; i++)
|
|
5
|
+
LOOKUP[ALPHABET.charCodeAt(i)] = i;
|
|
6
|
+
// V8's built-in Uint8Array base64 (ES2026) when present; the JS code below is the fallback.
|
|
7
|
+
const U8 = typeof Uint8Array !== "undefined" ? Uint8Array : undefined;
|
|
8
|
+
const HAS_TO_BASE64 = typeof U8?.prototype?.toBase64 === "function";
|
|
9
|
+
const HAS_FROM_BASE64 = typeof U8?.fromBase64 === "function";
|
|
10
|
+
export function encodeBase64(bytes) {
|
|
11
|
+
if (HAS_TO_BASE64)
|
|
12
|
+
return bytes.toBase64();
|
|
13
|
+
return encodeBase64Js(bytes);
|
|
14
|
+
}
|
|
15
|
+
export function encodeBase64Js(bytes) {
|
|
16
|
+
const parts = [];
|
|
17
|
+
const CHUNK = 3 * 8192;
|
|
18
|
+
for (let start = 0; start < bytes.length; start += CHUNK) {
|
|
19
|
+
const end = Math.min(start + CHUNK, bytes.length);
|
|
20
|
+
let out = "";
|
|
21
|
+
let i = start;
|
|
22
|
+
for (; i + 2 < end; i += 3) {
|
|
23
|
+
const n = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
|
|
24
|
+
out +=
|
|
25
|
+
ALPHABET[(n >> 18) & 63] +
|
|
26
|
+
ALPHABET[(n >> 12) & 63] +
|
|
27
|
+
ALPHABET[(n >> 6) & 63] +
|
|
28
|
+
ALPHABET[n & 63];
|
|
29
|
+
}
|
|
30
|
+
if (end - i === 1) {
|
|
31
|
+
const n = bytes[i] << 16;
|
|
32
|
+
out += ALPHABET[(n >> 18) & 63] + ALPHABET[(n >> 12) & 63] + "==";
|
|
33
|
+
}
|
|
34
|
+
else if (end - i === 2) {
|
|
35
|
+
const n = (bytes[i] << 16) | (bytes[i + 1] << 8);
|
|
36
|
+
out +=
|
|
37
|
+
ALPHABET[(n >> 18) & 63] +
|
|
38
|
+
ALPHABET[(n >> 12) & 63] +
|
|
39
|
+
ALPHABET[(n >> 6) & 63] +
|
|
40
|
+
"=";
|
|
41
|
+
}
|
|
42
|
+
parts.push(out);
|
|
43
|
+
}
|
|
44
|
+
return parts.join("");
|
|
45
|
+
}
|
|
46
|
+
export function decodeBase64(text) {
|
|
47
|
+
if (HAS_FROM_BASE64) {
|
|
48
|
+
try {
|
|
49
|
+
// Same leniency as the fallback: ASCII whitespace and trailing padding are ignored.
|
|
50
|
+
return U8.fromBase64(text.replace(/[\s=]+$/g, ""), { lastChunkHandling: "loose" });
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// fall through: the JS path reports the offending character
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return decodeBase64Js(text);
|
|
57
|
+
}
|
|
58
|
+
export function decodeBase64Js(text) {
|
|
59
|
+
const s = text.replace(/[\s=]+$/g, "");
|
|
60
|
+
const out = new Uint8Array(Math.floor((s.length * 3) / 4));
|
|
61
|
+
let acc = 0;
|
|
62
|
+
let bits = 0;
|
|
63
|
+
let o = 0;
|
|
64
|
+
for (let i = 0; i < s.length; i++) {
|
|
65
|
+
const c = s.charCodeAt(i);
|
|
66
|
+
const v = c < 128 ? LOOKUP[c] : -1;
|
|
67
|
+
if (v < 0)
|
|
68
|
+
throw new Error(`invalid base64 character at ${i}`);
|
|
69
|
+
acc = (acc << 6) | v;
|
|
70
|
+
bits += 6;
|
|
71
|
+
if (bits >= 8) {
|
|
72
|
+
bits -= 8;
|
|
73
|
+
out[o++] = (acc >> bits) & 0xff;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return out.subarray(0, o);
|
|
77
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type Json = any;
|
|
2
|
+
export declare const CRYPTO_VERSION = 2;
|
|
3
|
+
type Mem = () => WebAssembly.Memory;
|
|
4
|
+
/** Serves one `crypto/*` sync op. `mem` returns the guest's linear memory (for `{ptr, len}` byte arguments). */
|
|
5
|
+
/** Answers synchronously (no suspend) for small SHA-256 digest / HMAC, else with a promise. */
|
|
6
|
+
export declare function cryptoOp(op: string, args: Json, mem: Mem): Json | Promise<Json>;
|
|
7
|
+
export {};
|