Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
Expand Down Expand Up @@ -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"

Expand Down
1 change: 1 addition & 0 deletions engine/packages/pegboard/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 7 additions & 31 deletions engine/packages/pegboard/src/ops/serverless_metadata/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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)]
Expand All @@ -45,33 +47,6 @@ pub struct ActorNameMetadata {
pub metadata: serde_json::Map<String, serde_json::Value>,
}

#[derive(Deserialize)]
struct ServerlessMetadataEnvoy {
version: Option<u32>,
}

#[derive(Deserialize)]
struct ServerlessMetadataRunner {
version: Option<u32>,
}

#[derive(Deserialize)]
struct ServerlessMetadataPayload {
runtime: String,
version: String,
#[serde(rename = "envoyProtocolVersion")]
envoy_protocol_version: Option<u16>,
#[serde(rename = "actorNames", default)]
actor_names: HashMap<String, ActorName>,
envoy: Option<ServerlessMetadataEnvoy>,
runner: Option<ServerlessMetadataRunner>,
}

#[derive(Deserialize)]
struct ActorName {
metadata: Option<serde_json::Value>,
}

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();
Expand Down Expand Up @@ -160,9 +135,10 @@ pub async fn pegboard_serverless_metadata_fetch(

let payload = match serde_json::from_str::<ServerlessMetadataPayload>(&body_raw) {
Ok(p) => p,
Err(_) => {
Err(err) => {
return Ok(Err(ServerlessMetadataError::InvalidResponseJson {
body: body_for_user,
parse_error: err.to_string(),
}));
}
};
Expand Down Expand Up @@ -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,
}));
}
}
Expand Down
5 changes: 3 additions & 2 deletions examples/kitchen-sink/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion examples/kitchen-sink/turbo.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rivetkit-rust/packages/rivetkit-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
45 changes: 32 additions & 13 deletions rivetkit-rust/packages/rivetkit-core/src/serverless.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -308,22 +311,38 @@ impl CoreServerlessRuntime {
],
}),
);
(actor_name.clone(), json!({ "metadata": metadata }))
(
actor_name.clone(),
ActorName {
metadata: Some(serde_json::Value::Object(metadata)),
},
)
})
.collect::<serde_json::Map<_, _>>();

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::<HashMap<_, _>>();

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));
}
Expand Down
11 changes: 11 additions & 0 deletions rivetkit-rust/packages/shared-types/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions rivetkit-rust/packages/shared-types/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod serverless_metadata;
30 changes: 30 additions & 0 deletions rivetkit-rust/packages/shared-types/src/serverless_metadata.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use std::collections::HashMap;

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerlessMetadataEnvoy {
pub version: Option<u32>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerlessMetadataRunner {
pub version: Option<u32>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerlessMetadataPayload {
pub runtime: String,
pub version: String,
#[serde(rename = "envoyProtocolVersion")]
pub envoy_protocol_version: Option<u16>,
#[serde(rename = "actorNames", default)]
pub actor_names: HashMap<String, ActorName>,
pub envoy: Option<ServerlessMetadataEnvoy>,
pub runner: Option<ServerlessMetadataRunner>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActorName {
pub metadata: Option<serde_json::Value>,
}
59 changes: 7 additions & 52 deletions rivetkit-typescript/packages/rivetkit-napi/scripts/build.mjs
Original file line number Diff line number Diff line change
@@ -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/<platform>/` 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/<platform>/*.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" });
15 changes: 10 additions & 5 deletions rivetkit-typescript/packages/rivetkit-napi/turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
Expand Down
Loading