Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a9a9e7c
feat(api-public): expose actor kv api
NathanFlurry Nov 7, 2025
edd8a45
feat(rivetkit): add `GET /actors/names`
NathanFlurry Nov 7, 2025
af583ac
fix(rivetkit): use loggerWithoutContext for websocket error when acto…
NathanFlurry Nov 7, 2025
d2d01ea
refactor(rivetkit): add state manager for conn with symbol-based access
NathanFlurry Nov 9, 2025
b3ff582
chore(rivetkit): rename `onFetch` to `onRequest`
NathanFlurry Nov 9, 2025
7095d1c
chore(rivetkit): add connection for raw http
NathanFlurry Nov 9, 2025
1f30194
chore(rivetkit): add conn type
NathanFlurry Nov 9, 2025
57377f5
chore(rivetkit): add `WebSocketContext` and `RequestContext`
NathanFlurry Nov 9, 2025
714660b
refactor(rivetkit): restructure connection lifecycle contexts
NathanFlurry Nov 10, 2025
e10206c
refactor(rivetkit): extract WebSocket protocol parsing to shared utility
NathanFlurry Nov 10, 2025
a8f3a9c
chore(rivetkit): add listing actors by only name to manager api
NathanFlurry Nov 10, 2025
1784d8f
chore(rivetkit): add required zod validation for json encoding
NathanFlurry Nov 10, 2025
412f83b
feat(rivetkit): add json protocol support
NathanFlurry Nov 10, 2025
0bc4b29
fix(rivetkit): remove incorrect getEndpoint call in metadata handler
NathanFlurry Nov 10, 2025
68402d1
fix(rivetkit): standardize error metadata return type
NathanFlurry Nov 10, 2025
afc9c8e
fix(rivetkit): fix race condition with websocket open events
NathanFlurry Nov 10, 2025
51a32d6
fix(rivetkit): skip sending RivetKit messages to conns that do not su…
NathanFlurry Nov 10, 2025
898bc9a
chore(rivetkit): add actor router to the openapi spec
NathanFlurry Nov 10, 2025
9a434c7
feat(rivetkit): add asyncapi spec
NathanFlurry Nov 10, 2025
7d2f44d
chore(rivetkit): add support for conn params on onRequest and onWebSo…
NathanFlurry Nov 10, 2025
aaf96c9
chore(rivetkit): remove deprecated packages
NathanFlurry Nov 10, 2025
0e6327a
feat(site): add typedoc docs
NathanFlurry Nov 10, 2025
7bda860
chore(rivetkit-typescript): remove dependency on node modules
NathanFlurry Oct 25, 2025
0ceae4a
chore(rivetkit): switch dynamic node imports to use require
NathanFlurry Nov 13, 2025
1a35970
chore(website): flatten actors docs structure
NathanFlurry Oct 25, 2025
858d55d
chore: create branch for v3 website changes on v2
NicholasKissel Nov 1, 2025
cad6172
chore(site): website changes v3
NicholasKissel Oct 31, 2025
afd0d10
Add Quickstart tab to docs page and update all Get Started links
NicholasKissel Oct 31, 2025
0911e7e
chore(site): fix generating examples
NathanFlurry Nov 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ engine/sdks/schema/** linguist-generated=false
website/public/llms.txt linguist-generated=true
website/public/llms-full.txt linguist-generated=true
website/public/docs/**/*.md linguist-generated=true
website/public/typedoc/**/* linguist-generated=true
**/Cargo.lock linguist-generated=true

# I refuse to admit there's more TypeScript than Rust in the codebase
Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

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

53 changes: 52 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,56 @@
"noExplicitAny": "off"
}
}
}
},
"overrides": [
{
"includes": [
"rivetkit-typescript/packages/rivetkit/src/**/*",
"!rivetkit-typescript/packages/rivetkit/src/test/**/*"
],
"linter": {
"rules": {
"style": {
"noRestrictedImports": {
"level": "error",
"options": {
"paths": {
"node:crypto": "Use '@/utils/node' getNodeCrypto() instead. Direct Node.js imports are only allowed in src/utils/node.ts",
"node:fs": "Use '@/utils/node' getNodeFsSync() instead. Direct Node.js imports are only allowed in src/utils/node.ts",
"node:fs/promises": "Use '@/utils/node' getNodeFs() instead. Direct Node.js imports are only allowed in src/utils/node.ts",
"node:path": "Use '@/utils/node' getNodePath() instead. Direct Node.js imports are only allowed in src/utils/node.ts",
"node:os": "Use '@/utils/node' getNodeOs() instead. Direct Node.js imports are only allowed in src/utils/node.ts",
"node:child_process": "Use '@/utils/node' getNodeChildProcess() instead. Direct Node.js imports are only allowed in src/utils/node.ts",
"node:stream": "Use '@/utils/node' getNodeStream() instead. Direct Node.js imports are only allowed in src/utils/node.ts",
"node:net": "Use '@/utils/node' instead. Direct Node.js imports are only allowed in src/utils/node.ts",
"node:url": "Use '@/utils/node' instead. Direct Node.js imports are only allowed in src/utils/node.ts",
"crypto": "Use '@/utils/node' getNodeCrypto() instead. Direct Node.js imports are only allowed in src/utils/node.ts",
"fs": "Use '@/utils/node' getNodeFsSync() or getNodeFs() instead. Direct Node.js imports are only allowed in src/utils/node.ts",
"fs/promises": "Use '@/utils/node' getNodeFs() instead. Direct Node.js imports are only allowed in src/utils/node.ts",
"path": "Use '@/utils/node' getNodePath() instead. Direct Node.js imports are only allowed in src/utils/node.ts",
"os": "Use '@/utils/node' getNodeOs() instead. Direct Node.js imports are only allowed in src/utils/node.ts",
"child_process": "Use '@/utils/node' getNodeChildProcess() instead. Direct Node.js imports are only allowed in src/utils/node.ts",
"stream": "Use '@/utils/node' getNodeStream() instead. Direct Node.js imports are only allowed in src/utils/node.ts",
"net": "Use '@/utils/node' instead. Direct Node.js imports are only allowed in src/utils/node.ts",
"url": "Use '@/utils/node' instead. Direct Node.js imports are only allowed in src/utils/node.ts"
}
}
}
}
}
}
},
{
"includes": [
"rivetkit-typescript/packages/rivetkit/src/utils/node.ts"
],
"linter": {
"rules": {
"style": {
"noRestrictedImports": "off"
}
}
}
}
]
}
5 changes: 5 additions & 0 deletions engine/artifacts/errors/actor.kv_key_not_found.json

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

61 changes: 60 additions & 1 deletion engine/artifacts/openapi.json

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

27 changes: 5 additions & 22 deletions engine/package.json
Original file line number Diff line number Diff line change
@@ -1,28 +1,11 @@
{
"name": "@rivetkit/engine",
"private": true,
"version": "1.0.0",
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.13.1",
"scripts": {
"start": "npx turbo watch build",
"build": "npx turbo build",
"test": "npx turbo test",
"test:watch": "npx turbo watch test",
"check-types": "npx turbo check-types",
"fmt": "pnpm biome check --write --diagnostic-level=error ."
},
"devDependencies": {
"@bare-ts/tools": "0.15.0",
"@biomejs/biome": "^2.2.3",
"lefthook": "^1.12.4",
"tsup": "^8.5.0",
"turbo": "^2.5.6",
"typescript": "^5.9.2"
},
"dependencies": {
"@sentry/vite-plugin": "^2.23.1"
},
"resolutions": {
"rivetkit": "workspace:*",
"@clerk/shared": "3.27.1"
"@vbare/compiler": "^0.0.3"
}
}
2 changes: 2 additions & 0 deletions engine/packages/api-peer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ license.workspace = true
[dependencies]
anyhow.workspace = true
axum.workspace = true
base64.workspace = true
gas.workspace = true
epoxy.workspace = true
futures-util.workspace = true
Expand All @@ -27,6 +28,7 @@ tokio.workspace = true
tracing.workspace = true
namespace.workspace = true
pegboard.workspace = true
pegboard-actor-kv.workspace = true
universalpubsub.workspace = true
uuid.workspace = true
utoipa.workspace = true
67 changes: 67 additions & 0 deletions engine/packages/api-peer/src/actors/kv_get.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use anyhow::*;
use base64::prelude::BASE64_STANDARD;
use base64::Engine;
use pegboard_actor_kv as actor_kv;
use rivet_api_builder::ApiCtx;
use rivet_util::Id;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct KvGetPath {
pub actor_id: Id,
pub key: String,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct KvGetQuery {}

#[derive(Serialize, ToSchema)]
#[schema(as = ActorsKvGetResponse)]
pub struct KvGetResponse {
/// Value encoded in base 64.
pub value: String,
pub update_ts: i64,
}

#[utoipa::path(
get,
operation_id = "actors_kv_get",
path = "/actors/{actor_id}/kv/keys/{key}",
params(
("actor_id" = Id, Path),
("key" = String, Path),
),
responses(
(status = 200, body = KvGetResponse),
),
)]
#[tracing::instrument(skip_all)]
pub async fn kv_get(ctx: ApiCtx, path: KvGetPath, _query: KvGetQuery) -> Result<KvGetResponse> {
// Decode base64 key
let key_bytes = BASE64_STANDARD
.decode(&path.key)
.context("failed to decode base64 key")?;

// Get the KV value
let udb = ctx.pools().udb()?;
let (keys, values, metadata) =
actor_kv::get(&*udb, path.actor_id, vec![key_bytes.clone()]).await?;

// Check if key was found
if keys.is_empty() {
return Err(pegboard::errors::Actor::KvKeyNotFound.build());
}

// Encode value as base64
let value_base64 = BASE64_STANDARD.encode(&values[0]);

Ok(KvGetResponse {
value: value_base64,
// NOTE: Intentionally uses different name in public API. `create_ts` is actually
// `update_ts`.
update_ts: metadata[0].create_ts,
})
}
1 change: 1 addition & 0 deletions engine/packages/api-peer/src/actors/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod create;
pub mod delete;
pub mod kv_get;
pub mod list;
pub mod list_names;
4 changes: 4 additions & 0 deletions engine/packages/api-peer/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ pub async fn router(
.route("/actors", post(actors::create::create))
.route("/actors/{actor_id}", delete(actors::delete::delete))
.route("/actors/names", get(actors::list_names::list_names))
.route(
"/actors/{actor_id}/kv/keys/{key}",
get(actors::kv_get::kv_get),
)
// MARK: Runners
.route("/runners", get(runners::list))
.route("/runners/names", get(runners::list_names))
Expand Down
1 change: 1 addition & 0 deletions engine/packages/api-public/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ serde.workspace = true
tokio.workspace = true
tower-http.workspace = true
tracing.workspace = true
urlencoding.workspace = true
utoipa.workspace = true

[build-dependencies]
Expand Down
75 changes: 75 additions & 0 deletions engine/packages/api-public/src/actors/kv_get.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use anyhow::Result;
use axum::response::{IntoResponse, Response};
use rivet_api_builder::{
ApiError,
extract::{Extension, Path},
};
use rivet_api_util::request_remote_datacenter_raw;
use rivet_util::Id;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;

use crate::ctx::ApiCtx;

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct KvGetPath {
pub actor_id: Id,
pub key: String,
}

#[derive(Serialize, ToSchema)]
#[schema(as = ActorsKvGetResponse)]
pub struct KvGetResponse {
pub value: String,
pub update_ts: i64,
}

#[utoipa::path(
get,
operation_id = "actors_kv_get",
path = "/actors/{actor_id}/kv/keys/{key}",
params(
("actor_id" = Id, Path),
("key" = String, Path),
),
responses(
(status = 200, body = KvGetResponse),
),
security(("bearer_auth" = [])),
)]
#[tracing::instrument(skip_all)]
pub async fn kv_get(Extension(ctx): Extension<ApiCtx>, Path(path): Path<KvGetPath>) -> Response {
match kv_get_inner(ctx, path).await {
Ok(response) => response,
Err(err) => ApiError::from(err).into_response(),
}
}

#[tracing::instrument(skip_all)]
async fn kv_get_inner(ctx: ApiCtx, path: KvGetPath) -> Result<Response> {
use axum::Json;

ctx.auth().await?;

if path.actor_id.label() == ctx.config().dc_label() {
let peer_path = rivet_api_peer::actors::kv_get::KvGetPath {
actor_id: path.actor_id,
key: path.key,
};
let peer_query = rivet_api_peer::actors::kv_get::KvGetQuery {};
let res = rivet_api_peer::actors::kv_get::kv_get(ctx.into(), peer_path, peer_query).await?;

Ok(Json(res).into_response())
} else {
request_remote_datacenter_raw(
&ctx,
path.actor_id.label(),
&format!("/actors/{}/kv/keys/{}", path.actor_id, urlencoding::encode(&path.key)),
axum::http::Method::GET,
Option::<&()>::None,
Option::<&()>::None,
)
.await
}
}
Loading
Loading