Skip to content
Merged
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
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"
}
}
}
}
]
}
2 changes: 1 addition & 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"
}
}
1 change: 1 addition & 0 deletions engine/packages/engine/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ pub mod db;
pub mod start;
pub mod tracing;
pub mod udb;
pub mod udb_keys;
pub mod wf;
200 changes: 200 additions & 0 deletions engine/packages/engine/src/commands/udb_keys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
use std::{
fs,
io::{BufRead, BufReader},
};

use anyhow::{Context, Result, bail};
use clap::{Parser, Subcommand};

use crate::util::udb::SimpleTuple;

#[derive(Parser)]
pub struct Opts {
#[command(subcommand)]
command: SubCommand,
}

#[derive(Subcommand)]
pub enum SubCommand {
/// Decode a key from a byte array
Decode {
/// JSON array of bytes to decode (e.g. "[20, 21, 1, 21, 2]")
#[arg(long)]
array: String,
},
/// Parse and decode transaction conflicts from a logfmt log file
ParseConflictLogs {
/// Path to the logfmt log file
#[arg(long)]
file: String,
},
}

impl Opts {
pub fn execute(&self) -> Result<()> {
match &self.command {
SubCommand::Decode { array } => {
decode_array(array)?;
Ok(())
}
SubCommand::ParseConflictLogs { file } => {
parse_conflicts(file)?;
Ok(())
}
}
}
}

fn decode_array(array: &str) -> Result<()> {
// Parse the JSON array
let bytes: Vec<u8> = serde_json::from_str(array)
.with_context(|| format!("Failed to parse array as JSON: {}", array))?;

// Decode the tuple using foundationdb tuple unpacking
match universaldb::tuple::unpack::<SimpleTuple>(&bytes) {
Ok(tuple) => {
println!("{}", tuple);
}
Err(err) => {
bail!("Failed to decode key: {:#}", err);
}
}

Ok(())
}

fn parse_conflicts(file_path: &str) -> Result<()> {
let file =
fs::File::open(file_path).with_context(|| format!("Failed to open file: {}", file_path))?;
let reader = BufReader::new(file);

let mut conflict_count = 0;

for line in reader.lines() {
let line = line?;

// Check if this is a transaction conflict log
if !line.contains("transaction conflict detected") {
continue;
}

conflict_count += 1;

// Parse logfmt fields
let mut fields = std::collections::HashMap::new();
let mut in_quotes = false;
let mut current_key = String::new();
let mut current_value = String::new();
let mut in_key = true;

for c in line.chars() {
match c {
'"' => in_quotes = !in_quotes,
'=' if !in_quotes && in_key => {
in_key = false;
}
' ' if !in_quotes => {
if !current_key.is_empty() {
fields.insert(current_key.clone(), current_value.clone());
current_key.clear();
current_value.clear();
in_key = true;
}
}
_ => {
if in_key {
current_key.push(c);
} else {
current_value.push(c);
}
}
}
}

// Don't forget the last field
if !current_key.is_empty() {
fields.insert(current_key, current_value);
}

// Extract and decode keys
println!("\n═══════════════════════════════════════════════════════════");
println!("Conflict #{}", conflict_count);
println!("═══════════════════════════════════════════════════════════");

if let Some(ts) = fields.get("ts") {
println!("Timestamp: {}", ts);
}

if let (Some(cr1_type), Some(cr2_type)) = (fields.get("cr1_type"), fields.get("cr2_type")) {
println!("CR1 Type: {}, CR2 Type: {}", cr1_type, cr2_type);
}

if let (Some(start_v), Some(commit_v)) = (
fields.get("txn1_start_version"),
fields.get("txn1_commit_version"),
) {
println!("TXN1: start={}, commit={}", start_v, commit_v);
}

if let (Some(start_v), Some(commit_v)) = (
fields.get("txn2_start_version"),
fields.get("txn2_commit_version"),
) {
println!("TXN2: start={}, commit={}", start_v, commit_v);
}

println!("\nCR1 Range:");
if let Some(cr1_start) = fields.get("cr1_start") {
print!(" Start: ");
if let Err(e) = decode_from_logfmt(cr1_start) {
println!("Error: {:#}", e);
}
}
if let Some(cr1_end) = fields.get("cr1_end") {
print!(" End: ");
if let Err(e) = decode_from_logfmt(cr1_end) {
println!("Error: {:#}", e);
}
}

println!("\nCR2 Range:");
if let Some(cr2_start) = fields.get("cr2_start") {
print!(" Start: ");
if let Err(e) = decode_from_logfmt(cr2_start) {
println!("Error: {:#}", e);
}
}
if let Some(cr2_end) = fields.get("cr2_end") {
print!(" End: ");
if let Err(e) = decode_from_logfmt(cr2_end) {
println!("Error: {:#}", e);
}
}
}

if conflict_count == 0 {
println!("No transaction conflicts found in the log file.");
} else {
println!("\n═══════════════════════════════════════════════════════════");
println!("Total conflicts found: {}", conflict_count);
}

Ok(())
}

fn decode_from_logfmt(value: &str) -> Result<()> {
// Remove surrounding quotes if present
let value = value.trim_matches('"');

// Parse the JSON array
let bytes: Vec<u8> = serde_json::from_str(value)
.with_context(|| format!("Failed to parse array as JSON: {}", value))?;

// Decode the tuple
let tuple = universaldb::tuple::unpack::<SimpleTuple>(&bytes)
.with_context(|| "Failed to decode key")?;

println!("{}", tuple);

Ok(())
}
3 changes: 3 additions & 0 deletions engine/packages/engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub enum SubCommand {
},
/// Allows inspection of UDB data
Udb(udb::Opts),
/// UDB key utilities
UdbKeys(udb_keys::Opts),
}

impl SubCommand {
Expand All @@ -47,6 +49,7 @@ impl SubCommand {
SubCommand::Config { command } => command.execute(config).await,
SubCommand::Tracing { command } => command.execute(config).await,
SubCommand::Udb(opts) => opts.execute(config).await,
SubCommand::UdbKeys(opts) => opts.execute(),
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,19 @@ impl TransactionConflictTracker {
for (cr2_start, cr2_end, cr2_type) in &txn2.conflict_ranges {
// Check conflict ranges overlap
if cr1_start < cr2_end && cr2_start < cr1_end && cr1_type != cr2_type {
tracing::info!(
?cr1_start,
?cr1_end,
?cr1_type,
?cr2_start,
?cr2_end,
?cr2_type,
txn1_start_version,
txn1_commit_version,
txn2_start_version = txn2.start_version,
txn2_commit_version = txn2.commit_version,
"transaction conflict detected"
);
return true;
}
}
Expand Down
8 changes: 7 additions & 1 deletion engine/sdks/typescript/runner-protocol/src/index.ts

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

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
packages:
- engine
- engine/docker/template
- engine/sdks/typescript/api-full
- engine/sdks/typescript/runner
Expand Down
Loading
Loading