diff --git a/Cargo.lock b/Cargo.lock index d337cf4863..9fb750d237 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3423,6 +3423,7 @@ dependencies = [ "rivet-test-deps", "rivet-types", "rivet-util", + "rivetkit-shared-types", "serde", "serde_bare", "serde_json", @@ -5245,6 +5246,7 @@ dependencies = [ "rivet-util", "rivetkit-client-protocol", "rivetkit-inspector-protocol", + "rivetkit-shared-types", "rivetkit-sqlite", "scc", "serde", @@ -5296,6 +5298,14 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "rivetkit-shared-types" +version = "2.3.0-rc.4" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "rivetkit-sqlite" version = "2.3.0-rc.4" diff --git a/Cargo.toml b/Cargo.toml index 51c07a59ff..831a4aa738 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,7 @@ members = [ "rivetkit-rust/packages/inspector-protocol", "rivetkit-rust/packages/rivetkit", "rivetkit-rust/packages/rivetkit-core", + "rivetkit-rust/packages/shared-types", "rivetkit-rust/packages/rivetkit-sqlite", "rivetkit-typescript/packages/rivetkit-napi" ] @@ -544,6 +545,9 @@ members = [ [workspace.dependencies.rivetkit-core] path = "rivetkit-rust/packages/rivetkit-core" + [workspace.dependencies.rivetkit-shared-types] + path = "rivetkit-rust/packages/shared-types" + [workspace.dependencies.epoxy-protocol] path = "engine/sdks/rust/epoxy-protocol" diff --git a/engine/packages/pegboard/Cargo.toml b/engine/packages/pegboard/Cargo.toml index bd1e350680..94df5ba7d4 100644 --- a/engine/packages/pegboard/Cargo.toml +++ b/engine/packages/pegboard/Cargo.toml @@ -33,6 +33,7 @@ rivet-runner-protocol.workspace = true rivet-runtime.workspace = true rivet-types.workspace = true rivet-util.workspace = true +rivetkit-shared-types.workspace = true serde_bare.workspace = true serde_json.workspace = true serde.workspace = true diff --git a/engine/packages/pegboard/src/ops/serverless_metadata/fetch.rs b/engine/packages/pegboard/src/ops/serverless_metadata/fetch.rs index 281d954764..bd4955c4af 100644 --- a/engine/packages/pegboard/src/ops/serverless_metadata/fetch.rs +++ b/engine/packages/pegboard/src/ops/serverless_metadata/fetch.rs @@ -5,6 +5,7 @@ use anyhow::Result; use gas::prelude::*; use reqwest::header::{HeaderMap as ReqwestHeaderMap, HeaderName, HeaderValue}; use rivet_envoy_protocol::PROTOCOL_VERSION; +use rivetkit_shared_types::serverless_metadata::{ActorName, ServerlessMetadataPayload}; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; @@ -25,8 +26,9 @@ pub enum ServerlessMetadataError { RequestFailed {}, RequestTimedOut {}, NonSuccessStatus { status_code: u16, body: String }, - InvalidResponseJson { body: String }, + InvalidResponseJson { body: String, parse_error: String }, InvalidResponseSchema { runtime: String, version: String }, + InvalidEnvoyProtocolVersion { version: u16 }, } #[derive(Debug, Clone)] @@ -45,33 +47,6 @@ pub struct ActorNameMetadata { pub metadata: serde_json::Map, } -#[derive(Deserialize)] -struct ServerlessMetadataEnvoy { - version: Option, -} - -#[derive(Deserialize)] -struct ServerlessMetadataRunner { - version: Option, -} - -#[derive(Deserialize)] -struct ServerlessMetadataPayload { - runtime: String, - version: String, - #[serde(rename = "envoyProtocolVersion")] - envoy_protocol_version: Option, - #[serde(rename = "actorNames", default)] - actor_names: HashMap, - envoy: Option, - runner: Option, -} - -#[derive(Deserialize)] -struct ActorName { - metadata: Option, -} - fn truncate_response_body(body: &str) -> String { let mut chars = body.chars(); let mut truncated: String = chars.by_ref().take(RESPONSE_BODY_MAX_LEN).collect(); @@ -160,9 +135,10 @@ pub async fn pegboard_serverless_metadata_fetch( let payload = match serde_json::from_str::(&body_raw) { Ok(p) => p, - Err(_) => { + Err(err) => { return Ok(Err(ServerlessMetadataError::InvalidResponseJson { body: body_for_user, + parse_error: err.to_string(), })); } }; @@ -197,8 +173,8 @@ pub async fn pegboard_serverless_metadata_fetch( if let Some(envoy_protocol_version) = envoy_protocol_version { if envoy_protocol_version < 1 || envoy_protocol_version > PROTOCOL_VERSION { - return Ok(Err(ServerlessMetadataError::InvalidResponseJson { - body: body_for_user, + return Ok(Err(ServerlessMetadataError::InvalidEnvoyProtocolVersion { + version: envoy_protocol_version, })); } } diff --git a/examples/kitchen-sink/package.json b/examples/kitchen-sink/package.json index 2bfea303f8..27b2157681 100644 --- a/examples/kitchen-sink/package.json +++ b/examples/kitchen-sink/package.json @@ -5,10 +5,10 @@ "type": "module", "packageManager": "pnpm@10.13.1", "scripts": { - "dev": "concurrently -n server,vite \"node --import ../../rivetkit-typescript/packages/sql-loader/dist/register.js --import tsx src/server.ts\" \"vite\"", + "dev": "concurrently -n server,vite \"node --import @rivetkit/sql-loader --import tsx src/server.ts\" \"vite\"", "check-types": "echo 'skipped - workflow history types broken'", "build": "vite build", - "start": "node --import ../../rivetkit-typescript/packages/sql-loader/dist/register.js --import tsx src/server.ts", + "start": "node --import @rivetkit/sql-loader --import tsx src/server.ts", "benchmark": "tsx scripts/benchmark.ts", "db:generate": "find src/actors -name drizzle.config.ts -exec drizzle-kit generate --config {} \\;" }, @@ -29,6 +29,7 @@ "@hono/node-server": "^1.19.7", "@hono/node-ws": "^1.3.0", "@rivetkit/react": "*", + "@rivetkit/sql-loader": "*", "@rivetkit/rivetkit-napi": "*", "ai": "^4.0.38", "fdb-tuple": "^1.0.0", diff --git a/examples/kitchen-sink/turbo.json b/examples/kitchen-sink/turbo.json index c5e71016d3..87c094648d 100644 --- a/examples/kitchen-sink/turbo.json +++ b/examples/kitchen-sink/turbo.json @@ -3,7 +3,11 @@ "extends": ["//"], "tasks": { "build": { - "dependsOn": ["@rivetkit/react#build", "rivetkit#build"] + "dependsOn": [ + "@rivetkit/react#build", + "@rivetkit/sql-loader#build", + "rivetkit#build" + ] } } } diff --git a/rivetkit-rust/packages/rivetkit-core/Cargo.toml b/rivetkit-rust/packages/rivetkit-core/Cargo.toml index 0b33f6b97e..ac480130a8 100644 --- a/rivetkit-rust/packages/rivetkit-core/Cargo.toml +++ b/rivetkit-rust/packages/rivetkit-core/Cargo.toml @@ -23,6 +23,7 @@ rivet-pools.workspace = true rivet-util.workspace = true rivet-error.workspace = true rivet-envoy-client.workspace = true +rivetkit-shared-types.workspace = true rivetkit-client-protocol.workspace = true rivetkit-inspector-protocol.workspace = true rivetkit-sqlite = { workspace = true, optional = true } diff --git a/rivetkit-rust/packages/rivetkit-core/src/serverless.rs b/rivetkit-rust/packages/rivetkit-core/src/serverless.rs index cd0e0b8997..b48a8909ce 100644 --- a/rivetkit-rust/packages/rivetkit-core/src/serverless.rs +++ b/rivetkit-rust/packages/rivetkit-core/src/serverless.rs @@ -9,6 +9,9 @@ use rivet_envoy_client::config::EnvoyConfig; use rivet_envoy_client::envoy::start_envoy; use rivet_envoy_client::handle::EnvoyHandle; use rivet_envoy_client::protocol; +use rivetkit_shared_types::serverless_metadata::{ + ActorName, ServerlessMetadataEnvoy, ServerlessMetadataPayload, +}; use serde::Serialize; use serde_json::json; use subtle::ConstantTimeEq; @@ -308,22 +311,38 @@ impl CoreServerlessRuntime { ], }), ); - (actor_name.clone(), json!({ "metadata": metadata })) + ( + actor_name.clone(), + ActorName { + metadata: Some(serde_json::Value::Object(metadata)), + }, + ) }) - .collect::>(); - - let mut response = json!({ - "runtime": "rivetkit", - "version": self.settings.package_version, - "envoy": { - "kind": { "serverless": {} }, - "version": self.settings.version, - }, - "envoyProtocolVersion": protocol::PROTOCOL_VERSION, - "actorNames": actor_names, - }); + .collect::>(); + + let payload = ServerlessMetadataPayload { + runtime: "rivetkit".to_owned(), + version: self.settings.package_version.clone(), + envoy_protocol_version: Some(protocol::PROTOCOL_VERSION), + actor_names, + envoy: Some(ServerlessMetadataEnvoy { + version: Some(self.settings.version), + }), + runner: None, + }; + + let mut response = serde_json::to_value(payload).unwrap_or_else(|_| json!({})); if let serde_json::Value::Object(object) = &mut response { + if object.get("runner").is_some_and(serde_json::Value::is_null) { + object.remove("runner"); + } + if let Some(envoy) = object + .get_mut("envoy") + .and_then(|value| value.as_object_mut()) + { + envoy.insert("kind".to_owned(), json!({ "serverless": {} })); + } if let Some(client_endpoint) = &self.settings.client_endpoint { object.insert("clientEndpoint".to_owned(), json!(client_endpoint)); } diff --git a/rivetkit-rust/packages/shared-types/Cargo.toml b/rivetkit-rust/packages/shared-types/Cargo.toml new file mode 100644 index 0000000000..5b896871bf --- /dev/null +++ b/rivetkit-rust/packages/shared-types/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "rivetkit-shared-types" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +workspace = "../../../" + +[dependencies] +serde.workspace = true +serde_json.workspace = true diff --git a/rivetkit-rust/packages/shared-types/src/lib.rs b/rivetkit-rust/packages/shared-types/src/lib.rs new file mode 100644 index 0000000000..f9aad164e5 --- /dev/null +++ b/rivetkit-rust/packages/shared-types/src/lib.rs @@ -0,0 +1 @@ +pub mod serverless_metadata; diff --git a/rivetkit-rust/packages/shared-types/src/serverless_metadata.rs b/rivetkit-rust/packages/shared-types/src/serverless_metadata.rs new file mode 100644 index 0000000000..5935c6ceac --- /dev/null +++ b/rivetkit-rust/packages/shared-types/src/serverless_metadata.rs @@ -0,0 +1,30 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ServerlessMetadataEnvoy { + pub version: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ServerlessMetadataRunner { + pub version: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ServerlessMetadataPayload { + pub runtime: String, + pub version: String, + #[serde(rename = "envoyProtocolVersion")] + pub envoy_protocol_version: Option, + #[serde(rename = "actorNames", default)] + pub actor_names: HashMap, + pub envoy: Option, + pub runner: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ActorName { + pub metadata: Option, +} diff --git a/rivetkit-typescript/packages/rivetkit-napi/scripts/build.mjs b/rivetkit-typescript/packages/rivetkit-napi/scripts/build.mjs index 2be0ffdadc..749b402053 100644 --- a/rivetkit-typescript/packages/rivetkit-napi/scripts/build.mjs +++ b/rivetkit-typescript/packages/rivetkit-napi/scripts/build.mjs @@ -1,66 +1,21 @@ #!/usr/bin/env node /** - * Smart build wrapper for rivetkit-napi. - * - * Skips the napi build if a prebuilt .node file already exists next to - * this package (either a root-level `rivetkit-napi.*.node` or one inside - * a `npm//` directory). This lets CI skip a redundant napi build - * when the cross-compiled artifacts have already been downloaded from the - * platform build jobs. - * - * Pass `--force` to always run the napi build. + * Build wrapper for rivetkit-napi. */ -import { execSync } from "node:child_process"; -import { existsSync, readdirSync, statSync } from "node:fs"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const packageDir = join(__dirname, ".."); +import { execFileSync } from "node:child_process"; const args = process.argv.slice(2); -const force = args.includes("--force"); -const releaseArg = args.find((a) => a === "--release"); -const extraFlags = releaseArg ? ["--release"] : []; +const extraFlags = args.includes("--release") ? ["--release"] : []; // Explicit skip for environments that don't need the native binary (e.g. // Docker engine-frontend build which only consumes TypeScript types). -if (!force && process.env.SKIP_NAPI_BUILD === "1") { +if (process.env.SKIP_NAPI_BUILD === "1") { console.log( "[rivetkit-napi/build] SKIP_NAPI_BUILD=1 — skipping napi build", ); process.exit(0); } -function hasPrebuiltArtifact() { - // Check for root-level .node files. - const rootFiles = readdirSync(packageDir); - if (rootFiles.some((f) => f.endsWith(".node"))) { - return true; - } - // Check for any npm//*.node files. - const npmDir = join(packageDir, "npm"); - if (existsSync(npmDir) && statSync(npmDir).isDirectory()) { - for (const entry of readdirSync(npmDir)) { - const platDir = join(npmDir, entry); - if (!statSync(platDir).isDirectory()) continue; - const files = readdirSync(platDir); - if (files.some((f) => f.endsWith(".node"))) { - return true; - } - } - } - return false; -} - -if (!force && hasPrebuiltArtifact()) { - console.log( - "[rivetkit-napi/build] prebuilt .node artifact found — skipping napi build", - ); - console.log("[rivetkit-napi/build] use --force to rebuild from source"); - process.exit(0); -} - -const cmd = ["napi", "build", "--platform", ...extraFlags].join(" "); -console.log(`[rivetkit-napi/build] running: ${cmd}`); -execSync(cmd, { stdio: "inherit", cwd: packageDir }); +const cmd = ["build", "--platform", ...extraFlags]; +console.log(`[rivetkit-napi/build] running: napi ${cmd.join(" ")}`); +execFileSync("napi", cmd, { stdio: "inherit" }); diff --git a/rivetkit-typescript/packages/rivetkit-napi/turbo.json b/rivetkit-typescript/packages/rivetkit-napi/turbo.json index 1f82e3142b..6fbaf1cd44 100644 --- a/rivetkit-typescript/packages/rivetkit-napi/turbo.json +++ b/rivetkit-typescript/packages/rivetkit-napi/turbo.json @@ -4,13 +4,18 @@ "tasks": { "build": { "inputs": [ - "build.mjs", + "scripts/build.mjs", + "build.rs", "src/**/*.rs", "Cargo.toml", - "../../engine/sdks/rust/envoy-client/src/**/*.rs", - "../../engine/sdks/rust/envoy-client/Cargo.toml", - "../../engine/sdks/rust/envoy-protocol/src/**/*.rs", - "../../engine/sdks/rust/envoy-protocol/Cargo.toml" + "../../../Cargo.toml", + "../../../Cargo.lock", + "../../../rivetkit-rust/packages/**/src/**/*.rs", + "../../../rivetkit-rust/packages/**/Cargo.toml", + "../../../engine/packages/**/src/**/*.rs", + "../../../engine/packages/**/Cargo.toml", + "../../../engine/sdks/rust/**/src/**/*.rs", + "../../../engine/sdks/rust/**/Cargo.toml" ], "outputs": ["*.node"] }