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,82 @@
1
+ // Generator: manifest entries -> TypeScript wrappers (contracts/V8HOST.md).
2
+ import path from "node:path";
3
+ import { groupByModule } from "./manifest.js";
4
+ import { objectFields, validatorToTs } from "./validators.js";
5
+ export const GENERATED_HEADER = "// @generated by convex-rust. Do not edit.";
6
+ /** Relative import specifier with a leading `./` or `../`, `/` separators. */
7
+ function rel(fromDir, to) {
8
+ const r = path.posix.relative(fromDir, to) || ".";
9
+ return r.startsWith(".") ? r : `./${r}`;
10
+ }
11
+ function actionExport(e, env = []) {
12
+ const builder = e.visibility === "internal" ? "internalAction" : "action";
13
+ const lines = [`export const ${e.name} = ${builder}({`];
14
+ const args = e.args;
15
+ if (args !== null && args !== undefined) {
16
+ if (args.type !== "object") {
17
+ throw new Error(`${e.modulePath}::${e.name}: args validator must be an object`);
18
+ }
19
+ lines.push(` args: ${objectFields(args.value)},`);
20
+ }
21
+ if (e.returns !== null && e.returns !== undefined) {
22
+ lines.push(` returns: ${validatorToTs(e.returns)},`);
23
+ }
24
+ const argType = args === null || args === undefined ? "args: any" : "args";
25
+ lines.push(` handler: (ctx, ${argType}) => runRust(ctx, wasm, ${JSON.stringify(`convex_run:${e.modulePath}:${e.name}`)}, args${env.length ? `, { env: ${JSON.stringify(env)} }` : ""}),`);
26
+ lines.push("});");
27
+ return lines.join("\n");
28
+ }
29
+ const isHttp = (e) => e.kind === "http";
30
+ function httpExport(e, env = []) {
31
+ return `export const ${e.name} = httpAction((ctx, request) => runRustHttp(ctx, wasm, ${JSON.stringify(`convex_run:${e.modulePath}:${e.name}`)}, request${env.length ? `, { env: ${JSON.stringify(env)} }` : ""}));`;
32
+ }
33
+ function usesValidators(entries) {
34
+ return entries.some((e) => !isHttp(e) && (e.args != null || e.returns != null));
35
+ }
36
+ // Names the generated file itself uses, plus every ECMAScript reserved word (`export const delete` is a syntax error).
37
+ const RESERVED = new Set([
38
+ "default", "runRust", "runRustHttp", "wasm", "action", "internalAction", "httpAction", "v",
39
+ "await", "break", "case", "catch", "class", "const", "continue", "debugger", "delete", "do", "else",
40
+ "enum", "export", "extends", "false", "finally", "for", "function", "if", "import", "in", "instanceof",
41
+ "new", "null", "return", "super", "switch", "this", "throw", "true", "try", "typeof", "var", "void",
42
+ "while", "with", "yield", "let", "static", "implements", "interface", "package", "private", "protected",
43
+ "public", "arguments", "eval", "undefined", "NaN", "Infinity",
44
+ ]);
45
+ /** Generated files, keyed by path relative to the out dir. */
46
+ export function generateFiles(opts) {
47
+ const out = (opts.outRelToFunctions ?? "rust").replace(/\\/g, "/").replace(/\/+$/, "");
48
+ const files = new Map();
49
+ for (const [module, entries] of [...groupByModule(opts.entries)].sort(([a], [b]) => a < b ? -1 : 1)) {
50
+ for (const e of entries) {
51
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e.name) || RESERVED.has(e.name)) {
52
+ throw new Error(`Rust function ${e.modulePath}::${e.name} cannot be exported as a Convex function name.`);
53
+ }
54
+ }
55
+ // File directory relative to the functions dir, e.g. `rust/a`.
56
+ const fileDir = path.posix.join(out, path.posix.dirname(module));
57
+ const toOut = (p) => rel(fileDir, path.posix.join(out, p));
58
+ const toFunctions = (p) => rel(fileDir, p);
59
+ const builders = [
60
+ ...(entries.some((e) => !isHttp(e) && e.visibility !== "internal") ? ["action"] : []),
61
+ ...(entries.some((e) => !isHttp(e) && e.visibility === "internal") ? ["internalAction"] : []),
62
+ ...(entries.some(isHttp) ? ["httpAction"] : []),
63
+ ];
64
+ const mw = opts.moduleWasm?.get(module);
65
+ const wasmFile = mw?.file ?? opts.wasmFile;
66
+ const wasmHash = mw?.hash ?? opts.wasmHash;
67
+ const head = [
68
+ GENERATED_HEADER,
69
+ ...(wasmHash ? [`// wasm: ${wasmHash}`] : []),
70
+ `import { ${builders.join(", ")} } from "${toFunctions("_generated/server")}";`,
71
+ ];
72
+ if (usesValidators(entries))
73
+ head.push('import { v } from "convex/values";');
74
+ const runtime = [...(entries.some((e) => !isHttp(e)) ? ["runRust"] : []), ...(entries.some(isHttp) ? ["runRustHttp"] : [])];
75
+ head.push(`import { ${runtime.join(", ")} } from "convex-rust/runtime";`);
76
+ head.push(`import wasm from "${toOut(`_wasm/${wasmFile}`)}";`);
77
+ const body = [...entries].sort((a, b) => (a.name < b.name ? -1 : 1)).map((e) => (isHttp(e) ? httpExport(e, opts.env) : actionExport(e, opts.env)));
78
+ files.set(`${module}.ts`, `${head.join("\n")}\n\n${body.join("\n\n")}\n`);
79
+ }
80
+ files.set("_wasm.d.ts", `${GENERATED_HEADER}\ndeclare module "*.wasm" {\n const m: WebAssembly.Module;\n export default m;\n}\n`);
81
+ return files;
82
+ }
@@ -0,0 +1,24 @@
1
+ export declare const MANIFEST_SECTION = "convex:manifest";
2
+ export interface RustManifestEntry {
3
+ abiVersion: number;
4
+ /** Rust `module_path!()`, e.g. `bench_rust::cpu`. */
5
+ modulePath: string;
6
+ name: string;
7
+ visibility: "public" | "internal";
8
+ /** `"http"` for `#[http_action]` (an `httpAction`); absent or `"action"` for `#[action]`. */
9
+ kind?: "action" | "http";
10
+ args: unknown | null;
11
+ returns: unknown | null;
12
+ }
13
+ /**
14
+ * Convex module name for a Rust `module_path!()`: the segments after the crate
15
+ * name joined by `/` (`bench_rust::cpu` -> `cpu`, `app::a::b` -> `a/b`). The
16
+ * crate root maps to `lib` (V8HOST.md).
17
+ */
18
+ export declare function convexModuleName(modulePath: string): string;
19
+ /** Parse manifest text (one JSON entry per line) and validate it. */
20
+ export declare function parseManifestText(text: string): RustManifestEntry[];
21
+ /** Parse the `convex:manifest` custom section(s) out of wasm bytes. */
22
+ export declare function parseManifest(wasm: Uint8Array): RustManifestEntry[];
23
+ /** Group manifest entries by Convex module name (e.g. `cpu`, `a/b`). */
24
+ export declare function groupByModule(entries: RustManifestEntry[]): Map<string, RustManifestEntry[]>;
@@ -0,0 +1,75 @@
1
+ // Manifest v1 (contracts/MANIFEST.md): read from the `convex:manifest` custom section.
2
+ export const MANIFEST_SECTION = "convex:manifest";
3
+ /**
4
+ * Convex module name for a Rust `module_path!()`: the segments after the crate
5
+ * name joined by `/` (`bench_rust::cpu` -> `cpu`, `app::a::b` -> `a/b`). The
6
+ * crate root maps to `lib` (V8HOST.md).
7
+ */
8
+ export function convexModuleName(modulePath) {
9
+ const segments = modulePath.split("::");
10
+ if (segments.length < 2)
11
+ return "lib";
12
+ return segments.slice(1).join("/");
13
+ }
14
+ /** Parse manifest text (one JSON entry per line) and validate it. */
15
+ export function parseManifestText(text) {
16
+ const entries = [];
17
+ for (const line of text.split("\n")) {
18
+ const trimmed = line.trim();
19
+ if (trimmed === "")
20
+ continue;
21
+ entries.push(JSON.parse(trimmed));
22
+ }
23
+ const seen = new Set();
24
+ for (const e of entries) {
25
+ if (e.abiVersion !== 1) {
26
+ throw new Error(`Rust manifest has unsupported abiVersion ${e.abiVersion} (this convex-rust supports 1).`);
27
+ }
28
+ if (e.abiVersion !== entries[0].abiVersion) {
29
+ throw new Error(`Rust manifest has mixed abiVersion values (${entries[0].abiVersion} and ${e.abiVersion}).`);
30
+ }
31
+ const key = `${e.modulePath}:${e.name}`;
32
+ if (seen.has(key))
33
+ throw new Error(`Rust manifest has duplicate function ${key}.`);
34
+ seen.add(key);
35
+ }
36
+ // Crate-root functions are served as module `lib` on this path (V8HOST.md); a real `crate::lib`
37
+ // module would be the same Convex module, so the two cannot coexist.
38
+ const root = entries.find((e) => !e.modulePath.includes("::"));
39
+ const lib = entries.find((e) => e.modulePath.split("::").length === 2 && e.modulePath.split("::")[1] === "lib");
40
+ if (root && lib) {
41
+ throw new Error(`Rust functions in the crate root (${root.modulePath}::${root.name}) and in the module \`lib\` (${lib.modulePath}::${lib.name}) both map to the Convex module \`lib\`; move one of them.`);
42
+ }
43
+ return entries;
44
+ }
45
+ /** Parse the `convex:manifest` custom section(s) out of wasm bytes. */
46
+ export function parseManifest(wasm) {
47
+ let module;
48
+ try {
49
+ module = new WebAssembly.Module(wasm);
50
+ }
51
+ catch (e) {
52
+ throw new Error(`Invalid WebAssembly module: ${e?.message ?? e}`);
53
+ }
54
+ const decoder = new TextDecoder("utf-8");
55
+ const texts = [];
56
+ for (const section of WebAssembly.Module.customSections(module, MANIFEST_SECTION)) {
57
+ texts.push(decoder.decode(section));
58
+ }
59
+ const entries = parseManifestText(texts.join("\n"));
60
+ if (entries.length === 0) {
61
+ throw new Error("The wasm has no `convex:manifest` entries: does the crate define any #[action] functions?");
62
+ }
63
+ return entries;
64
+ }
65
+ /** Group manifest entries by Convex module name (e.g. `cpu`, `a/b`). */
66
+ export function groupByModule(entries) {
67
+ const grouped = new Map();
68
+ for (const e of entries) {
69
+ const name = convexModuleName(e.modulePath);
70
+ const list = grouped.get(name) ?? [];
71
+ list.push(e);
72
+ grouped.set(name, list);
73
+ }
74
+ return grouped;
75
+ }
@@ -0,0 +1,78 @@
1
+ import type { RustManifestEntry } from "./manifest.js";
2
+ type ConvexValidator = any;
3
+ export interface AnalyzedFunction {
4
+ name: string;
5
+ udfType: string;
6
+ visibility: {
7
+ kind: "public" | "internal";
8
+ };
9
+ args: string | null;
10
+ returns: string | null;
11
+ }
12
+ export interface AnalyzedModule {
13
+ functions: AnalyzedFunction[];
14
+ }
15
+ export declare const RUST_API_HEADER = "// @generated by `convex-rust`. Do not edit; changes are overwritten.\n//\n// Typed references to your Convex functions for `ctx.run_query`,\n// `ctx.run_mutation` and `ctx.run_action`. Include it from `lib.rs` with\n//\n// #[path = \"_generated/api.rs\"]\n// pub mod api;\n#![allow(\n non_upper_case_globals,\n non_snake_case,\n non_camel_case_types,\n dead_code,\n unused_imports,\n clippy::all\n)]\n";
16
+ /** Placeholder `api.rs` until the first codegen fills it in. */
17
+ export declare const RUST_API_STUB = "// @generated by `convex-rust`. Do not edit; changes are overwritten.\n//\n// Typed references to your Convex functions for `ctx.run_query`,\n// `ctx.run_mutation` and `ctx.run_action`. Include it from `lib.rs` with\n//\n// #[path = \"_generated/api.rs\"]\n// pub mod api;\n#![allow(\n non_upper_case_globals,\n non_snake_case,\n non_camel_case_types,\n dead_code,\n unused_imports,\n clippy::all\n)]\n\n// No functions yet: this file is filled in by the next `convex-rust build` or `convex-rust codegen`.\n";
18
+ /** A Rust identifier for `name` (a Convex module, function or field name). */
19
+ export declare function rustIdent(name: string): string;
20
+ /** `list_messages` / `listMessages` -> `ListMessages`. */
21
+ export declare function pascalCase(name: string): string;
22
+ interface Generator {
23
+ /** Struct definitions emitted next to the function reference. */
24
+ structs: string[];
25
+ }
26
+ /**
27
+ * Rust type for a validator. Objects become structs named after `hint`
28
+ * (pushed to `gen.structs`); anything the Rust types can't express exactly
29
+ * (general unions, mixed literals) becomes `serde_json::Value`.
30
+ */
31
+ export declare function rustType(v: ConvexValidator, hint: string, gen: Generator): string;
32
+ /** Contents of `api.rs` for the analyzed modules (`messages.js`, ...). */
33
+ export declare function rustApiCodegen(modules: Record<string, AnalyzedModule>): string;
34
+ /** Runs in the subprocess: prints one JSON line `{ [module]: fn[] }`. */
35
+ export declare const ANALYZE_SCRIPT = "\nimport { pathToFileURL } from \"node:url\";\nconst [dir, ...mods] = process.argv.slice(1);\nconst out = {};\nfor (const m of mods) {\n let ns;\n try {\n ns = await import(pathToFileURL(dir + \"/\" + m).href);\n } catch (e) {\n process.stderr.write(\"convex-rust: could not load \" + m + \": \" + (e && e.message || e) + \"\\n\");\n continue;\n }\n const fns = [];\n for (const [name, f] of Object.entries(ns)) {\n if (typeof f !== \"function\") continue;\n const udfType = f.isQuery ? \"Query\" : f.isMutation ? \"Mutation\" : f.isAction ? \"Action\" : null;\n if (udfType === null) continue;\n const kind = f.isPublic ? \"public\" : f.isInternal ? \"internal\" : null;\n if (kind === null) continue;\n let args = null, returns = null;\n try { args = f.exportArgs(); returns = f.exportReturns(); } catch {}\n fns.push({ name, udfType, visibility: { kind }, args, returns });\n }\n out[m] = fns;\n}\nprocess.stdout.write(\"\\n@@convex-analysis@@\" + JSON.stringify(out) + \"\\n\");\n";
36
+ export declare function parseAnalysisOutput(stdout: string): Record<string, AnalyzedModule> | null;
37
+ /**
38
+ * TS/JS entry points of the functions directory, following the stock bundler's rules
39
+ * (skips `_generated`, dotfiles, `schema.*`, files with several dots, component directories).
40
+ * `skipDirs` are absolute directories to leave out (e.g. the Rust output directory).
41
+ */
42
+ export declare function findEntryPoints(functionsDir: string, skipDirs?: string[]): string[];
43
+ export interface AnalyzeResult {
44
+ modules: Record<string, AnalyzedModule>;
45
+ warnings: string[];
46
+ }
47
+ /**
48
+ * Analyzes the TS functions under `functionsDir` without a backend: bundles each module with
49
+ * esbuild (npm packages external, so `convex/server` is the project's), imports the bundles in a
50
+ * Node subprocess and reads each registered function's visibility and validators. Returns null
51
+ * when analysis is not possible (callers keep the existing api.rs).
52
+ */
53
+ export declare function analyzeTsFunctions(projectRoot: string, functionsDir: string, skipDirs?: string[], timeoutMs?: number, log?: (m: string) => void): Promise<AnalyzeResult | null>;
54
+ /** Rust functions (from the wasm manifest) as analysed modules under `<outRel>/<module>.js`. */
55
+ export declare function rustEntriesAsModules(entries: RustManifestEntry[], outRel: string): Record<string, AnalyzedModule>;
56
+ export declare function apiRsPath(crateDir: string): string;
57
+ /** Function paths (`messages:list`) present in an `api.rs`. */
58
+ export declare function functionPathsInApiRs(contents: string): Set<string>;
59
+ export declare function rustSourcesReferenceApi(crateDir: string): boolean;
60
+ export interface ApiRsOptions {
61
+ projectRoot: string;
62
+ functionsDir: string;
63
+ crateDir: string;
64
+ outDir: string;
65
+ /** Rust functions from a previous build (cache) or the fresh manifest. */
66
+ rustEntries?: RustManifestEntry[];
67
+ log?: (m: string) => void;
68
+ }
69
+ /**
70
+ * Regenerates `<crate>/src/_generated/api.rs` from the TS functions plus the Rust ones. Only
71
+ * runs when the file exists or the Rust sources use `api::`. A failed analysis leaves the file
72
+ * alone. Returns true when the file changed.
73
+ */
74
+ export declare function updateApiRs(o: ApiRsOptions): Promise<boolean>;
75
+ /** Rust functions of the last build, kept in the cargo target dir so the first compile sees them. */
76
+ export declare function readRustCache(cacheFile: string): RustManifestEntry[];
77
+ export declare function writeRustCache(cacheFile: string, entries: RustManifestEntry[]): void;
78
+ export {};