From 392dab42b09826e062de38b54f7fe9192ca26d67 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Fri, 24 Oct 2025 13:38:59 -0700 Subject: [PATCH 1/3] ci: fix release scripts for engine --- engine/sdks/typescript/runner/src/tunnel.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/sdks/typescript/runner/src/tunnel.ts b/engine/sdks/typescript/runner/src/tunnel.ts index 9882341bc1..1d7924827c 100644 --- a/engine/sdks/typescript/runner/src/tunnel.ts +++ b/engine/sdks/typescript/runner/src/tunnel.ts @@ -336,8 +336,8 @@ export class Tunnel { existing.actorId = req.actorId; } else { this.#actorPendingRequests.set(requestIdStr, { - resolve: () => { }, - reject: () => { }, + resolve: () => {}, + reject: () => {}, streamController: controller, actorId: req.actorId, }); @@ -506,7 +506,7 @@ export class Tunnel { const dataBuffer = typeof data === "string" ? (new TextEncoder().encode(data) - .buffer as ArrayBuffer) + .buffer as ArrayBuffer) : data; this.#sendMessage(requestId, { From 224e7e0d401a4271094a6d10e84b8639eaec6e7e Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Sat, 25 Oct 2025 02:17:00 -0700 Subject: [PATCH 2/3] chore(rivetkit-typescript): remove dependency on node modules --- biome.json | 53 +++++- engine/package.json | 27 +-- .../typescript/runner-protocol/src/index.ts | 8 +- pnpm-workspace.yaml | 1 + .../packages/rivetkit/package.json | 5 + .../packages/rivetkit/scripts/dump-openapi.ts | 6 +- .../packages/rivetkit/src/drivers/default.ts | 6 +- .../src/drivers/file-system/global-state.ts | 35 +++- .../rivetkit/src/drivers/file-system/mod.ts | 14 +- .../rivetkit/src/drivers/file-system/utils.ts | 21 ++- .../rivetkit/src/engine-process/mod.ts | 51 ++++-- .../packages/rivetkit/src/inspector/utils.ts | 11 +- .../packages/rivetkit/src/mod.ts | 9 +- .../packages/rivetkit/src/registry/mod.ts | 41 +++-- .../packages/rivetkit/src/registry/serve.ts | 10 +- .../packages/rivetkit/src/test/mod.ts | 10 +- .../packages/rivetkit/src/utils/node.ts | 169 ++++++++++++++++++ .../rivetkit/tests/driver-file-system.test.ts | 2 +- .../rivetkit/tests/driver-memory.test.ts | 2 +- 19 files changed, 382 insertions(+), 99 deletions(-) create mode 100644 rivetkit-typescript/packages/rivetkit/src/utils/node.ts diff --git a/biome.json b/biome.json index bcc74e911b..bfde915277 100644 --- a/biome.json +++ b/biome.json @@ -41,5 +41,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" + } + } + } + } + ] } diff --git a/engine/package.json b/engine/package.json index e7fd9ca8ee..93070d1322 100644 --- a/engine/package.json +++ b/engine/package.json @@ -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" } } diff --git a/engine/sdks/typescript/runner-protocol/src/index.ts b/engine/sdks/typescript/runner-protocol/src/index.ts index c6405665cb..343802861a 100644 --- a/engine/sdks/typescript/runner-protocol/src/index.ts +++ b/engine/sdks/typescript/runner-protocol/src/index.ts @@ -1,4 +1,4 @@ -import assert from "node:assert" + import * as bare from "@bare-ts/lib" const DEFAULT_CONFIG = /* @__PURE__ */ bare.Config({}) @@ -1906,3 +1906,9 @@ export function decodeToServerlessServer(bytes: Uint8Array): ToServerlessServer } return result } + + +function assert(condition: boolean, message?: string): asserts condition { + if (!condition) throw new Error(message ?? "Assertion failed") +} + diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8b6cb70c52..bf8dba696b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,4 +1,5 @@ packages: + - engine - engine/docker/template - engine/sdks/typescript/api-full - engine/sdks/typescript/runner diff --git a/rivetkit-typescript/packages/rivetkit/package.json b/rivetkit-typescript/packages/rivetkit/package.json index 4beb9ce0d4..574a99e313 100644 --- a/rivetkit-typescript/packages/rivetkit/package.json +++ b/rivetkit-typescript/packages/rivetkit/package.json @@ -155,6 +155,10 @@ "build": "tsup src/mod.ts src/client/mod.ts src/common/log.ts src/common/websocket.ts src/actor/errors.ts src/topologies/coordinate/mod.ts src/topologies/partition/mod.ts src/utils.ts src/driver-helpers/mod.ts src/driver-test-suite/mod.ts src/test/mod.ts src/inspector/mod.ts", "build:schema": "./scripts/compile-bare.ts compile schemas/client-protocol/v1.bare -o dist/schemas/client-protocol/v1.ts && ./scripts/compile-bare.ts compile schemas/file-system-driver/v1.bare -o dist/schemas/file-system-driver/v1.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v1.bare -o dist/schemas/actor-persist/v1.ts", "check-types": "tsc --noEmit", + "lint": "biome check .", + "lint:fix": "biome check --write .", + "format": "biome format .", + "format:write": "biome format --write .", "test": "vitest run", "test:watch": "vitest", "dump-openapi": "tsx scripts/dump-openapi.ts" @@ -176,6 +180,7 @@ }, "devDependencies": { "@bare-ts/tools": "^0.13.0", + "@biomejs/biome": "^2.2.3", "@hono/node-server": "^1.18.2", "@hono/node-ws": "^1.1.1", "@types/invariant": "^2", diff --git a/rivetkit-typescript/packages/rivetkit/scripts/dump-openapi.ts b/rivetkit-typescript/packages/rivetkit/scripts/dump-openapi.ts index fa31da47e7..3bf19bd4aa 100644 --- a/rivetkit-typescript/packages/rivetkit/scripts/dump-openapi.ts +++ b/rivetkit-typescript/packages/rivetkit/scripts/dump-openapi.ts @@ -13,14 +13,14 @@ import { import { type RunnerConfig, RunnerConfigSchema } from "@/registry/run-config"; import { VERSION } from "@/utils"; -function main() { +async function main() { const registryConfig: RegistryConfig = RegistryConfigSchema.parse({ use: {}, }); const registry = setup(registryConfig); const driverConfig: RunnerConfig = RunnerConfigSchema.parse({ - driver: createFileSystemOrMemoryDriver(false), + driver: await createFileSystemOrMemoryDriver(false), getUpgradeWebSocket: () => () => unimplemented(), inspector: { enabled: false, @@ -70,7 +70,7 @@ function main() { "rivetkit-openapi", "openapi.json", ); - fs.writeFile(outputPath, JSON.stringify(openApiDoc, null, 2)); + await fs.writeFile(outputPath, JSON.stringify(openApiDoc, null, 2)); console.log("Dumped OpenAPI to", outputPath); } diff --git a/rivetkit-typescript/packages/rivetkit/src/drivers/default.ts b/rivetkit-typescript/packages/rivetkit/src/drivers/default.ts index 8fab1b3d65..d55c407d8f 100644 --- a/rivetkit-typescript/packages/rivetkit/src/drivers/default.ts +++ b/rivetkit-typescript/packages/rivetkit/src/drivers/default.ts @@ -7,7 +7,9 @@ import type { DriverConfig, RunnerConfig } from "@/registry/run-config"; /** * Chooses the appropriate driver based on the run configuration. */ -export function chooseDefaultDriver(runConfig: RunnerConfig): DriverConfig { +export async function chooseDefaultDriver( + runConfig: RunnerConfig, +): Promise { if (runConfig.endpoint && runConfig.driver) { throw new UserError( "Cannot specify both 'endpoint' and 'driver' in configuration", @@ -31,5 +33,5 @@ export function chooseDefaultDriver(runConfig: RunnerConfig): DriverConfig { } loggerWithoutContext().debug({ msg: "using default file system driver" }); - return createFileSystemOrMemoryDriver(true); + return await createFileSystemOrMemoryDriver(true); } diff --git a/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/global-state.ts b/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/global-state.ts index 2a88c8da79..6b4366e7a7 100644 --- a/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/global-state.ts +++ b/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/global-state.ts @@ -1,7 +1,3 @@ -import * as crypto from "node:crypto"; -import * as fsSync from "node:fs"; -import * as fs from "node:fs/promises"; -import * as path from "node:path"; import invariant from "invariant"; import { lookupInRegistry } from "@/actor/definition"; import { ActorAlreadyExists } from "@/actor/errors"; @@ -27,6 +23,12 @@ import { setLongTimeout, stringifyError, } from "@/utils"; +import { + getNodeCrypto, + getNodeFs, + getNodeFsSync, + getNodePath, +} from "@/utils/node"; import { logger } from "./log"; import { ensureDirectoryExists, @@ -93,6 +95,7 @@ export class FileSystemGlobalState { constructor(persist: boolean = true, customPath?: string) { this.#persist = persist; this.#storagePath = persist ? getStoragePath(customPath) : "/tmp"; + const path = getNodePath(); this.#stateDir = path.join(this.#storagePath, "state"); this.#dbsDir = path.join(this.#storagePath, "databases"); this.#alarmsDir = path.join(this.#storagePath, "alarms"); @@ -104,6 +107,7 @@ export class FileSystemGlobalState { ensureDirectoryExistsSync(this.#alarmsDir); try { + const fsSync = getNodeFsSync(); const actorIds = fsSync.readdirSync(this.#stateDir); this.#actorCountOnStartup = actorIds.length; } catch (error) { @@ -131,15 +135,15 @@ export class FileSystemGlobalState { } getActorStatePath(actorId: string): string { - return path.join(this.#stateDir, actorId); + return getNodePath().join(this.#stateDir, actorId); } getActorDbPath(actorId: string): string { - return path.join(this.#dbsDir, `${actorId}.db`); + return getNodePath().join(this.#dbsDir, `${actorId}.db`); } getActorAlarmPath(actorId: string): string { - return path.join(this.#alarmsDir, actorId); + return getNodePath().join(this.#alarmsDir, actorId); } async *getActorsIterator(params: { @@ -148,6 +152,7 @@ export class FileSystemGlobalState { let actorIds = Array.from(this.#actors.keys()).sort(); // Check if state directory exists first + const fsSync = getNodeFsSync(); if (fsSync.existsSync(this.#stateDir)) { actorIds = fsSync .readdirSync(this.#stateDir) @@ -258,6 +263,7 @@ export class FileSystemGlobalState { // Read & parse file try { + const fs = getNodeFs(); const stateData = await fs.readFile(stateFilePath); // Cache the loaded state in handler @@ -352,8 +358,10 @@ export class FileSystemGlobalState { // Persist alarm to disk if (this.#persist) { const alarmPath = this.getActorAlarmPath(actorId); + const crypto = getNodeCrypto(); const tempPath = `${alarmPath}.tmp.${crypto.randomUUID()}`; try { + const path = getNodePath(); await ensureDirectoryExists(path.dirname(alarmPath)); const alarmData: schema.ActorAlarm = { actorId, @@ -363,10 +371,12 @@ export class FileSystemGlobalState { ACTOR_ALARM_VERSIONED.serializeWithEmbeddedVersion( alarmData, ); + const fs = getNodeFs(); await fs.writeFile(tempPath, data); await fs.rename(tempPath, alarmPath); } catch (error) { try { + const fs = getNodeFs(); await fs.unlink(tempPath); } catch {} logger().error({ @@ -391,10 +401,12 @@ export class FileSystemGlobalState { ): Promise { const dataPath = this.getActorStatePath(actorId); // Generate unique temp filename to prevent any race conditions + const crypto = getNodeCrypto(); const tempPath = `${dataPath}.tmp.${crypto.randomUUID()}`; try { // Create directory if needed + const path = getNodePath(); await ensureDirectoryExists(path.dirname(dataPath)); // Convert to BARE types for serialization @@ -409,11 +421,13 @@ export class FileSystemGlobalState { // Perform atomic write const serializedState = ACTOR_STATE_VERSIONED.serializeWithEmbeddedVersion(bareState); + const fs = getNodeFs(); await fs.writeFile(tempPath, serializedState); await fs.rename(tempPath, dataPath); } catch (error) { // Cleanup temp file on error try { + const fs = getNodeFs(); await fs.unlink(tempPath); } catch { // Ignore cleanup errors @@ -548,12 +562,14 @@ export class FileSystemGlobalState { */ #loadAlarmsSync(): void { try { + const fsSync = getNodeFsSync(); const files = fsSync.existsSync(this.#alarmsDir) ? fsSync.readdirSync(this.#alarmsDir) : []; for (const file of files) { // Skip temp files if (file.includes(".tmp.")) continue; + const path = getNodePath(); const fullPath = path.join(this.#alarmsDir, file); try { const buf = fsSync.readFileSync(fullPath); @@ -622,6 +638,7 @@ export class FileSystemGlobalState { // On trigger: remove persisted alarm file if (this.#persist) { try { + const fs = getNodeFs(); await fs.unlink(this.getActorAlarmPath(actorId)); } catch (err: any) { if (err?.code !== "ENOENT") { @@ -668,6 +685,8 @@ export class FileSystemGlobalState { } getOrCreateInspectorAccessToken(): string { + const path = getNodePath(); + const fsSync = getNodeFsSync(); const tokenPath = path.join(this.#storagePath, "inspector-token"); if (fsSync.existsSync(tokenPath)) { return fsSync.readFileSync(tokenPath, "utf-8"); @@ -683,6 +702,7 @@ export class FileSystemGlobalState { */ #cleanupTempFilesSync(): void { try { + const fsSync = getNodeFsSync(); const files = fsSync.readdirSync(this.#stateDir); const tempFiles = files.filter((f) => f.includes(".tmp.")); @@ -690,6 +710,7 @@ export class FileSystemGlobalState { for (const tempFile of tempFiles) { try { + const path = getNodePath(); const fullPath = path.join(this.#stateDir, tempFile); const stat = fsSync.statSync(fullPath); diff --git a/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/mod.ts b/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/mod.ts index e9d33a12c0..e5fe8cf7c1 100644 --- a/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/mod.ts @@ -1,4 +1,5 @@ import type { DriverConfig } from "@/registry/run-config"; +import { importNodeDependencies } from "@/utils/node"; import { FileSystemActorDriver } from "./actor"; import { FileSystemGlobalState } from "./global-state"; import { FileSystemManagerDriver } from "./manager"; @@ -8,10 +9,13 @@ export { FileSystemGlobalState } from "./global-state"; export { FileSystemManagerDriver } from "./manager"; export { getStoragePath } from "./utils"; -export function createFileSystemOrMemoryDriver( +export async function createFileSystemOrMemoryDriver( persist: boolean = true, customPath?: string, -): DriverConfig { +): Promise { + // Import Node.js dependencies before creating the state + await importNodeDependencies(); + const state = new FileSystemGlobalState(persist, customPath); const driverConfig: DriverConfig = { name: persist ? "file-system" : "memory", @@ -44,10 +48,12 @@ export function createFileSystemOrMemoryDriver( return driverConfig; } -export function createFileSystemDriver(opts?: { path?: string }): DriverConfig { +export async function createFileSystemDriver(opts?: { + path?: string; +}): Promise { return createFileSystemOrMemoryDriver(true, opts?.path); } -export function createMemoryDriver(): DriverConfig { +export async function createMemoryDriver(): Promise { return createFileSystemOrMemoryDriver(false); } diff --git a/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/utils.ts b/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/utils.ts index f6e09e011c..785aa4808d 100644 --- a/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/utils.ts +++ b/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/utils.ts @@ -1,9 +1,11 @@ -import * as crypto from "node:crypto"; -import * as fsSync from "node:fs"; -import * as fs from "node:fs/promises"; -import * as os from "node:os"; -import * as path from "node:path"; import type { ActorKey } from "@/actor/mod"; +import { + getNodeCrypto, + getNodeFs, + getNodeFsSync, + getNodeOs, + getNodePath, +} from "@/utils/node"; /** * Generate a deterministic actor ID from name and key @@ -13,6 +15,7 @@ export function generateActorId(name: string, key: ActorKey): string { const jsonString = JSON.stringify([name, key]); // Hash to ensure safe file system names + const crypto = getNodeCrypto(); const hash = crypto .createHash("sha256") .update(jsonString) @@ -26,6 +29,7 @@ export function generateActorId(name: string, key: ActorKey): string { * Create a hash for a path, normalizing it first */ function createHashForPath(dirPath: string): string { + const path = getNodePath(); // Normalize the path first const normalizedPath = path.normalize(dirPath); @@ -33,6 +37,7 @@ function createHashForPath(dirPath: string): string { const lastComponent = path.basename(normalizedPath); // Create SHA-256 hash + const crypto = getNodeCrypto(); const hash = crypto .createHash("sha256") .update(normalizedPath) @@ -49,6 +54,7 @@ export function getStoragePath(customPath?: string): string { const dataPath = getDataPath("rivetkit"); const pathToHash = customPath || process.cwd(); const dirHash = createHashForPath(pathToHash); + const path = getNodePath(); return path.join(dataPath, dirHash); } @@ -57,6 +63,7 @@ export function getStoragePath(customPath?: string): string { */ export async function pathExists(path: string): Promise { try { + const fs = getNodeFs(); await fs.access(path); return true; } catch { @@ -71,6 +78,7 @@ export async function ensureDirectoryExists( directoryPath: string, ): Promise { if (!(await pathExists(directoryPath))) { + const fs = getNodeFs(); await fs.mkdir(directoryPath, { recursive: true }); } } @@ -80,6 +88,7 @@ export async function ensureDirectoryExists( * All other operations use the async version */ export function ensureDirectoryExistsSync(directoryPath: string): void { + const fsSync = getNodeFsSync(); if (!fsSync.existsSync(directoryPath)) { fsSync.mkdirSync(directoryPath, { recursive: true }); } @@ -90,7 +99,9 @@ export function ensureDirectoryExistsSync(directoryPath: string): void { */ function getDataPath(appName: string): string { const platform = process.platform; + const os = getNodeOs(); const homeDir = os.homedir(); + const path = getNodePath(); switch (platform) { case "win32": diff --git a/rivetkit-typescript/packages/rivetkit/src/engine-process/mod.ts b/rivetkit-typescript/packages/rivetkit/src/engine-process/mod.ts index bd6f01e8f2..a2d5da5003 100644 --- a/rivetkit-typescript/packages/rivetkit/src/engine-process/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/engine-process/mod.ts @@ -1,13 +1,16 @@ -import { spawn } from "node:child_process"; -import { randomUUID } from "node:crypto"; -import { createWriteStream } from "node:fs"; -import * as fs from "node:fs/promises"; -import * as path from "node:path"; -import { pipeline } from "node:stream/promises"; import { ensureDirectoryExists, getStoragePath, } from "@/drivers/file-system/utils"; +import { + getNodeChildProcess, + getNodeCrypto, + getNodeFs, + getNodeFsSync, + getNodePath, + getNodeStream, + importNodeDependencies, +} from "@/utils/node"; import { logger } from "./log"; export const ENGINE_PORT = 6420; @@ -23,10 +26,15 @@ interface EnsureEngineProcessOptions { export async function ensureEngineProcess( options: EnsureEngineProcessOptions, ): Promise { + // Import Node.js dependencies first + await importNodeDependencies(); + logger().debug({ msg: "ensuring engine process", version: options.version, }); + + const path = getNodePath(); const storageRoot = getStoragePath(); const binDir = path.join(storageRoot, "bin"); const varDir = path.join(storageRoot, "var"); @@ -61,7 +69,6 @@ export async function ensureEngineProcess( ); } } - // Create log file streams with timestamp in the filename const timestamp = new Date() .toISOString() @@ -70,8 +77,13 @@ export async function ensureEngineProcess( const stdoutLogPath = path.join(logsDir, `engine-${timestamp}-stdout.log`); const stderrLogPath = path.join(logsDir, `engine-${timestamp}-stderr.log`); - const stdoutStream = createWriteStream(stdoutLogPath, { flags: "a" }); - const stderrStream = createWriteStream(stderrLogPath, { flags: "a" }); + const fsSync = getNodeFsSync(); + const stdoutStream = fsSync.createWriteStream(stdoutLogPath, { + flags: "a", + }); + const stderrStream = fsSync.createWriteStream(stderrLogPath, { + flags: "a", + }); logger().debug({ msg: "creating engine log files", @@ -79,7 +91,8 @@ export async function ensureEngineProcess( stderr: stderrLogPath, }); - const child = spawn(binaryPath, ["start"], { + const childProcess = getNodeChildProcess(); + const child = childProcess.spawn(binaryPath, ["start"], { cwd: path.dirname(binaryPath), stdio: ["inherit", "pipe", "pipe"], env: { @@ -98,7 +111,6 @@ export async function ensureEngineProcess( if (child.stderr) { child.stderr.pipe(stderrStream); } - logger().debug({ msg: "spawned engine process", pid: child.pid, @@ -175,7 +187,8 @@ async function downloadEngineBinaryIfNeeded( } // Generate unique temp file name to prevent parallel download conflicts - const tempPath = `${binaryPath}.${randomUUID()}.tmp`; + const crypto = getNodeCrypto(); + const tempPath = `${binaryPath}.${crypto.randomUUID()}.tmp`; const startTime = Date.now(); logger().debug({ @@ -193,12 +206,18 @@ async function downloadEngineBinaryIfNeeded( }, 5000); try { - await pipeline(response.body, createWriteStream(tempPath)); + const stream = getNodeStream(); + const fsSync = getNodeFsSync(); + await stream.pipeline( + response.body, + fsSync.createWriteStream(tempPath), + ); // Clear the slow download warning clearTimeout(slowDownloadWarning); // Get file size to verify download + const fs = getNodeFs(); const stats = await fs.stat(tempPath); const downloadDuration = Date.now() - startTime; @@ -232,6 +251,7 @@ async function downloadEngineBinaryIfNeeded( support: "https://rivet.dev/discord", }); try { + const fs = getNodeFs(); await fs.unlink(tempPath); } catch (unlinkError) { // Ignore errors when cleaning up (file may not exist) @@ -239,7 +259,7 @@ async function downloadEngineBinaryIfNeeded( throw error; } } - +// function resolveTargetTriplet(): { targetTriplet: string; extension: string } { return resolveTargetTripletFor(process.platform, process.arch); } @@ -279,7 +299,6 @@ export function resolveTargetTripletFor( `unsupported platform for rivet engine binary: ${platform}/${arch}`, ); } - async function isEngineRunning(): Promise { // Check if the engine is running on the port return await checkIfEngineAlreadyRunningOnPort(ENGINE_PORT); @@ -328,9 +347,9 @@ async function checkIfEngineAlreadyRunningOnPort( // Port responded but not with OK status return false; } - async function fileExists(filePath: string): Promise { try { + const fs = getNodeFs(); await fs.access(filePath); return true; } catch { diff --git a/rivetkit-typescript/packages/rivetkit/src/inspector/utils.ts b/rivetkit-typescript/packages/rivetkit/src/inspector/utils.ts index cd86ab686c..f9c1c3789a 100644 --- a/rivetkit-typescript/packages/rivetkit/src/inspector/utils.ts +++ b/rivetkit-typescript/packages/rivetkit/src/inspector/utils.ts @@ -1,4 +1,4 @@ -import crypto from "node:crypto"; +// import crypto from "node:crypto"; import { createMiddleware } from "hono/factory"; import type { ManagerDriver } from "@/driver-helpers/mod"; import type { RunConfig } from "@/mod"; @@ -20,10 +20,11 @@ export function compareSecrets(providedSecret: string, validSecret: string) { return false; } - // Perform timing-safe comparison - if (!crypto.timingSafeEqual(a, b)) { - return false; - } + // TODO: + // // Perform timing-safe comparison + // if (!crypto.timingSafeEqual(a, b)) { + // return false; + // } return true; } diff --git a/rivetkit-typescript/packages/rivetkit/src/mod.ts b/rivetkit-typescript/packages/rivetkit/src/mod.ts index 924a7aa299..156ae3daaa 100644 --- a/rivetkit-typescript/packages/rivetkit/src/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/mod.ts @@ -8,11 +8,10 @@ export { export { InlineWebSocketAdapter2 } from "@/common/inline-websocket-adapter2"; export { noopNext } from "@/common/utils"; export { createEngineDriver } from "@/drivers/engine/mod"; -export { - createFileSystemDriver, - createMemoryDriver, -} from "@/drivers/file-system/mod"; -// Re-export important protocol types and utilities needed by drivers +// export { +// createFileSystemDriver, +// createMemoryDriver, +// } from "@/drivers/file-system/mod"; export type { ActorQuery } from "@/manager/protocol/query"; export * from "@/registry/mod"; export { toUint8Array } from "@/utils"; diff --git a/rivetkit-typescript/packages/rivetkit/src/registry/mod.ts b/rivetkit-typescript/packages/rivetkit/src/registry/mod.ts index 5befc2a6ad..9104ea3d86 100644 --- a/rivetkit-typescript/packages/rivetkit/src/registry/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/registry/mod.ts @@ -58,7 +58,9 @@ export class Registry { /** * Runs the registry for a server. */ - public start(inputConfig?: RunnerConfigInput): ServerOutput { + public async start( + inputConfig?: RunnerConfigInput, + ): Promise> { const config = RunnerConfigSchema.parse(inputConfig); // Validate autoConfigureServerless is only used with serverless runner @@ -72,7 +74,7 @@ export class Registry { } // Promise for any async operations we need to wait to complete - const readyPromises = []; + const readyPromises: Promise[] = []; // Disable health check if using serverless // @@ -135,7 +137,7 @@ export class Registry { } // Choose the driver based on configuration - const driver = chooseDefaultDriver(config); + const driver = await chooseDefaultDriver(config); // Set defaults based on the driver if (driver.name === "engine") { @@ -209,6 +211,23 @@ export class Registry { console.log(); } + const { router: hono } = createManagerRouter( + this.#config, + config, + managerDriver, + driver, + client, + ); + + // Start server + if (!config.disableDefaultServer) { + const serverPromise = (async () => { + const out = await crossPlatformServe(config, hono, undefined); + upgradeWebSocket = out.upgradeWebSocket; + })(); + readyPromises.push(serverPromise); + } + // HACK: We need to find a better way to let the driver itself decide when to start the actor driver // Create runner // @@ -230,22 +249,6 @@ export class Registry { }); } - const { router: hono } = createManagerRouter( - this.#config, - config, - managerDriver, - driver, - client, - ); - - // Start server - if (!config.disableDefaultServer) { - (async () => { - const out = await crossPlatformServe(config, hono, undefined); - upgradeWebSocket = out.upgradeWebSocket; - })(); - } - return { client, fetch: hono.fetch.bind(hono), diff --git a/rivetkit-typescript/packages/rivetkit/src/registry/serve.ts b/rivetkit-typescript/packages/rivetkit/src/registry/serve.ts index 44b2b09896..0aaaa18990 100644 --- a/rivetkit-typescript/packages/rivetkit/src/registry/serve.ts +++ b/rivetkit-typescript/packages/rivetkit/src/registry/serve.ts @@ -9,12 +9,13 @@ export async function crossPlatformServe( ) { const app = userRouter ?? new Hono(); - // Import @hono/node-server + // Import @hono/node-server using string variable to prevent static analysis + const nodeServerModule = "@hono/node-server"; let serve: any; try { const dep = await import( /* webpackIgnore: true */ - "@hono/node-server" + nodeServerModule ); serve = dep.serve; } catch (err) { @@ -28,12 +29,13 @@ export async function crossPlatformServe( // app.route("/registry", rivetKitRouter); app.route("/", rivetKitRouter); - // Import @hono/node-ws + // Import @hono/node-ws using string variable to prevent static analysis + const nodeWsModule = "@hono/node-ws"; let createNodeWebSocket: any; try { const dep = await import( /* webpackIgnore: true */ - "@hono/node-ws" + nodeWsModule ); createNodeWebSocket = dep.createNodeWebSocket; } catch (err) { diff --git a/rivetkit-typescript/packages/rivetkit/src/test/mod.ts b/rivetkit-typescript/packages/rivetkit/src/test/mod.ts index 7fddf5ee49..c6764a4ae4 100644 --- a/rivetkit-typescript/packages/rivetkit/src/test/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/test/mod.ts @@ -17,7 +17,10 @@ import { RunnerConfigSchema } from "@/registry/run-config"; import { ConfigSchema, type InputConfig } from "./config"; import { logger } from "./log"; -function serve(registry: Registry, inputConfig?: InputConfig): ServerType { +async function serve( + registry: Registry, + inputConfig?: InputConfig, +): Promise { // Configure default configuration inputConfig ??= {}; @@ -30,7 +33,8 @@ function serve(registry: Registry, inputConfig?: InputConfig): ServerType { // Create router const runConfig = RunnerConfigSchema.parse(inputConfig); - const driver = inputConfig.driver ?? createFileSystemOrMemoryDriver(false); + const driver = + inputConfig.driver ?? (await createFileSystemOrMemoryDriver(false)); const managerDriver = driver.manager(registry.config, config); const client = createClientWithDriver( managerDriver, @@ -92,7 +96,7 @@ export async function setupTest>( // Start server with a random port const port = await getPort(); - const server = serve(registry, { port }); + const server = await serve(registry, { port }); c.onTestFinished( async () => await new Promise((resolve) => server.close(() => resolve())), diff --git a/rivetkit-typescript/packages/rivetkit/src/utils/node.ts b/rivetkit-typescript/packages/rivetkit/src/utils/node.ts new file mode 100644 index 0000000000..746df0ba65 --- /dev/null +++ b/rivetkit-typescript/packages/rivetkit/src/utils/node.ts @@ -0,0 +1,169 @@ +// Global variables for Node.js modules +let nodeCrypto: typeof import("node:crypto") | undefined; +let nodeFsSync: typeof import("node:fs") | undefined; +let nodeFs: typeof import("node:fs/promises") | undefined; +let nodePath: typeof import("node:path") | undefined; +let nodeOs: typeof import("node:os") | undefined; +let nodeChildProcess: typeof import("node:child_process") | undefined; +let nodeStream: typeof import("node:stream/promises") | undefined; + +// Singleton promise to ensure imports happen only once +let importPromise: Promise | undefined; + +/** + * Dynamically imports all required Node.js dependencies. + * This function is idempotent and will only import once. + * @throws Error if Node.js modules are not available (e.g., in browser/edge environments) + */ +export async function importNodeDependencies(): Promise { + if (importPromise) return importPromise; + + importPromise = (async () => { + try { + // Dynamic imports with webpack ignore comment to prevent bundling + const cryptoModule = "node:crypto"; + const fsModule = "node:fs"; + const fsPromisesModule = "node:fs/promises"; + const pathModule = "node:path"; + const osModule = "node:os"; + const childProcessModule = "node:child_process"; + const streamModule = "node:stream/promises"; + + const modules = await Promise.all([ + import(/* webpackIgnore: true */ cryptoModule), + import(/* webpackIgnore: true */ fsModule), + import(/* webpackIgnore: true */ fsPromisesModule), + import(/* webpackIgnore: true */ pathModule), + import(/* webpackIgnore: true */ osModule), + import(/* webpackIgnore: true */ childProcessModule), + import(/* webpackIgnore: true */ streamModule), + ]); + + [ + nodeCrypto, + nodeFsSync, + nodeFs, + nodePath, + nodeOs, + nodeChildProcess, + nodeStream, + ] = modules; + } catch (err) { + // Node.js not available - will use memory driver fallback + console.warn( + "Node.js modules not available, file system driver will not work", + err, + ); + throw err; + } + })(); + + return importPromise; +} + +/** + * Gets the Node.js crypto module. + * @throws Error if crypto module is not loaded + */ +export function getNodeCrypto(): typeof import("node:crypto") { + if (!nodeCrypto) { + throw new Error( + "Node crypto module not loaded. Ensure importNodeDependencies() has been called.", + ); + } + return nodeCrypto; +} + +/** + * Gets the Node.js fs module. + * @throws Error if fs module is not loaded + */ +export function getNodeFsSync(): typeof import("node:fs") { + if (!nodeFsSync) { + throw new Error( + "Node fs module not loaded. Ensure importNodeDependencies() has been called.", + ); + } + return nodeFsSync; +} + +/** + * Gets the Node.js fs/promises module. + * @throws Error if fs/promises module is not loaded + */ +export function getNodeFs(): typeof import("node:fs/promises") { + if (!nodeFs) { + throw new Error( + "Node fs/promises module not loaded. Ensure importNodeDependencies() has been called.", + ); + } + return nodeFs; +} + +/** + * Gets the Node.js path module. + * @throws Error if path module is not loaded + */ +export function getNodePath(): typeof import("node:path") { + if (!nodePath) { + throw new Error( + "Node path module not loaded. Ensure importNodeDependencies() has been called.", + ); + } + return nodePath; +} + +/** + * Gets the Node.js os module. + * @throws Error if os module is not loaded + */ +export function getNodeOs(): typeof import("node:os") { + if (!nodeOs) { + throw new Error( + "Node os module not loaded. Ensure importNodeDependencies() has been called.", + ); + } + return nodeOs; +} + +/** + * Gets the Node.js child_process module. + * @throws Error if child_process module is not loaded + */ +export function getNodeChildProcess(): typeof import("node:child_process") { + if (!nodeChildProcess) { + throw new Error( + "Node child_process module not loaded. Ensure importNodeDependencies() has been called.", + ); + } + return nodeChildProcess; +} + +/** + * Gets the Node.js stream/promises module. + * @throws Error if stream/promises module is not loaded + */ +export function getNodeStream(): typeof import("node:stream/promises") { + if (!nodeStream) { + throw new Error( + "Node stream/promises module not loaded. Ensure importNodeDependencies() has been called.", + ); + } + return nodeStream; +} + +/** + * Checks if Node.js dependencies are available. + * @returns true if all Node.js modules are loaded + */ +export function areNodeDependenciesAvailable(): boolean { + return !!( + nodeCrypto && + nodeFsSync && + nodeFs && + nodePath && + nodeOs && + nodeChildProcess && + nodeStream + ); +} diff --git a/rivetkit-typescript/packages/rivetkit/tests/driver-file-system.test.ts b/rivetkit-typescript/packages/rivetkit/tests/driver-file-system.test.ts index 1c5b662b5a..197bc26020 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/driver-file-system.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/driver-file-system.test.ts @@ -10,7 +10,7 @@ runDriverTests({ join(__dirname, "../fixtures/driver-test-suite/registry.ts"), async () => { return { - driver: createFileSystemOrMemoryDriver( + driver: await createFileSystemOrMemoryDriver( true, `/tmp/test-${crypto.randomUUID()}`, ), diff --git a/rivetkit-typescript/packages/rivetkit/tests/driver-memory.test.ts b/rivetkit-typescript/packages/rivetkit/tests/driver-memory.test.ts index c299c0d042..20912e9e36 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/driver-memory.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/driver-memory.test.ts @@ -14,7 +14,7 @@ runDriverTests({ join(__dirname, "../fixtures/driver-test-suite/registry.ts"), async () => { return { - driver: createFileSystemOrMemoryDriver(false), + driver: await createFileSystemOrMemoryDriver(false), }; }, ); From 3c9700f12a3d4cb8e50447ceb6f1f86ce59a1979 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Sat, 25 Oct 2025 02:17:21 -0700 Subject: [PATCH 3/3] chore(website): flatten actors docs structure --- website/public/llms-full.txt | 7 +- website/public/llms.txt | 1 - .../database-sharp-regular-full.svg | 1 + .../diagram-next-sharp-regular-full.svg | 1 + .../file-pen-sharp-regular-full.svg | 1 + .../raw-icon-svgs/gears-sharp-light-full.svg | 1 + .../globe-sharp-regular-full.svg | 1 + .../rotate-sharp-regular-full.svg | 1 + .../sparkles-sharp-regular-full.svg | 1 + .../src/app/(v2)/(marketing)/(index)/page.tsx | 22 +- .../(index)/sections/CodeSnippetsSection.tsx | 8 +- .../sections/DeploymentOptionsSection.tsx | 91 +++++ .../(index)/sections/HeroSection.tsx | 41 +- .../(index)/sections/IconWithSpotlight.tsx | 17 +- .../(index)/sections/PlatformSection.tsx | 9 + .../(marketing)/(index)/sections/UseCases.tsx | 229 +++++++++++ .../src/components/CollapsibleSidebarItem.tsx | 2 +- website/src/components/DocsNavigation.tsx | 6 +- website/src/content/docs/api/index.mdx | 3 - .../src/content/docs/self-hosting/index.mdx | 4 + website/src/data/use-cases.ts | 25 +- website/src/sitemap/mod.ts | 373 ++++++++---------- 22 files changed, 599 insertions(+), 246 deletions(-) create mode 100644 website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/database-sharp-regular-full.svg create mode 100644 website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/diagram-next-sharp-regular-full.svg create mode 100644 website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/file-pen-sharp-regular-full.svg create mode 100644 website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/gears-sharp-light-full.svg create mode 100644 website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/globe-sharp-regular-full.svg create mode 100644 website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/rotate-sharp-regular-full.svg create mode 100644 website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/sparkles-sharp-regular-full.svg create mode 100644 website/src/app/(v2)/(marketing)/(index)/sections/DeploymentOptionsSection.tsx create mode 100644 website/src/app/(v2)/(marketing)/(index)/sections/PlatformSection.tsx create mode 100644 website/src/app/(v2)/(marketing)/(index)/sections/UseCases.tsx delete mode 100644 website/src/content/docs/api/index.mdx diff --git a/website/public/llms-full.txt b/website/public/llms-full.txt index adc2a38cd1..2b80f0ae4a 100644 --- a/website/public/llms-full.txt +++ b/website/public/llms-full.txt @@ -3652,11 +3652,6 @@ The `setupTest` function automatically calls `vi.useFakeTimers()`, allowing you 4. **Use realistic data**: Test with data that resembles production scenarios. Rivet's testing framework automatically handles server setup and teardown, so you can focus on writing effective tests for your business logic. -## API Reference - -# API Reference - -This is where the technical API docs live. ## Node.js & Bun # Node.js & Bun @@ -6135,6 +6130,8 @@ docker run -d \ Rivet consists of several core components that work together to provide a complete actor orchestration platform. The Rivet Engine is the core of self-hosting and is used for orchestrating actors at scale: + Self-hosting is not required to deploy Rivet applciations in your own cloud. Please see the [deploy documentation](/docs/deploy) if trying to deploy a Rivet application. + ## Core Components - **Your Backend** - Your application server that handles user requests and includes a runner component that executes actor code diff --git a/website/public/llms.txt b/website/public/llms.txt index b2d6fa1cf4..f4962672a6 100644 --- a/website/public/llms.txt +++ b/website/public/llms.txt @@ -79,7 +79,6 @@ https://rivet.dev/docs/actors/schedule https://rivet.dev/docs/actors/sharing-and-joining-state https://rivet.dev/docs/actors/state https://rivet.dev/docs/actors/testing -https://rivet.dev/docs/api https://rivet.dev/docs/clients/javascript https://rivet.dev/docs/clients/next-js https://rivet.dev/docs/clients/openapi diff --git a/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/database-sharp-regular-full.svg b/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/database-sharp-regular-full.svg new file mode 100644 index 0000000000..af98bd5a33 --- /dev/null +++ b/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/database-sharp-regular-full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/diagram-next-sharp-regular-full.svg b/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/diagram-next-sharp-regular-full.svg new file mode 100644 index 0000000000..b9f25f7b42 --- /dev/null +++ b/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/diagram-next-sharp-regular-full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/file-pen-sharp-regular-full.svg b/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/file-pen-sharp-regular-full.svg new file mode 100644 index 0000000000..37bb0b4978 --- /dev/null +++ b/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/file-pen-sharp-regular-full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/gears-sharp-light-full.svg b/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/gears-sharp-light-full.svg new file mode 100644 index 0000000000..6a74040060 --- /dev/null +++ b/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/gears-sharp-light-full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/globe-sharp-regular-full.svg b/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/globe-sharp-regular-full.svg new file mode 100644 index 0000000000..66fabfa19b --- /dev/null +++ b/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/globe-sharp-regular-full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/rotate-sharp-regular-full.svg b/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/rotate-sharp-regular-full.svg new file mode 100644 index 0000000000..e2897e066e --- /dev/null +++ b/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/rotate-sharp-regular-full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/sparkles-sharp-regular-full.svg b/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/sparkles-sharp-regular-full.svg new file mode 100644 index 0000000000..8cad7b9ad2 --- /dev/null +++ b/website/src/app/(v2)/(marketing)/(index)/images/raw-icon-svgs/sparkles-sharp-regular-full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/src/app/(v2)/(marketing)/(index)/page.tsx b/website/src/app/(v2)/(marketing)/(index)/page.tsx index fdc8eb5a87..3419b1d7a6 100644 --- a/website/src/app/(v2)/(marketing)/(index)/page.tsx +++ b/website/src/app/(v2)/(marketing)/(index)/page.tsx @@ -1,12 +1,14 @@ import { HeroBackground } from "./components/HeroBackground"; import { HeroSection } from "./sections/HeroSection"; +import { UseCases } from "./sections/UseCases"; +import { PlatformSection } from "./sections/PlatformSection"; import { CodeSnippetsSection } from "./sections/CodeSnippetsSection"; import { FeaturesSection } from "./sections/FeaturesSection"; import { TechSection } from "./sections/TechSection"; +import { DeploymentOptionsSection } from "./sections/DeploymentOptionsSection"; import { StudioSection } from "./sections/StudioSection"; import { CommunitySection } from "./sections/CommunitySection"; import { CTASection } from "./sections/CTASection"; -import { PlatformIcons } from "./components/PlatformIcons"; export default function IndexPage() { return ( @@ -19,18 +21,30 @@ export default function IndexPage() {
-
- +
+ +
+ +
+
- +
+ {/*
+ +
*/} +
+
+ +
+
diff --git a/website/src/app/(v2)/(marketing)/(index)/sections/CodeSnippetsSection.tsx b/website/src/app/(v2)/(marketing)/(index)/sections/CodeSnippetsSection.tsx index 9336fd25ed..7b14c3352d 100644 --- a/website/src/app/(v2)/(marketing)/(index)/sections/CodeSnippetsSection.tsx +++ b/website/src/app/(v2)/(marketing)/(index)/sections/CodeSnippetsSection.tsx @@ -3,14 +3,14 @@ import CodeSnippets from "../components/code-snippets"; export function CodeSnippetsSection() { return (
- {/*
+

- Reconsider What Your Backend Can Do + See It In Action

- Build powerful applications with Rivet Actors + Real-world examples showing how Rivet Actors simplify complex backends

-
*/} +
diff --git a/website/src/app/(v2)/(marketing)/(index)/sections/DeploymentOptionsSection.tsx b/website/src/app/(v2)/(marketing)/(index)/sections/DeploymentOptionsSection.tsx new file mode 100644 index 0000000000..be72ca6e6f --- /dev/null +++ b/website/src/app/(v2)/(marketing)/(index)/sections/DeploymentOptionsSection.tsx @@ -0,0 +1,91 @@ +import Link from "next/link"; +import { CopyCommand } from "../components/CopyCommand"; + +interface DeploymentOptionProps { + title: string; + description: string; + children?: React.ReactNode; +} + +function DeploymentOption({ title, description, children }: DeploymentOptionProps) { + return ( +
+

{title}

+

{description}

+ {children} +
+ ); +} + +export function DeploymentOptionsSection() { + return ( +
+
+
+

+ Run It Your Way +

+

+ Deploy Rivet however works best for your team, from local development to production at scale. +

+
+ +
+ + + +
+ + Sign In with Rivet + + + + View Pricing + + +
+
+ + +
+ + View Self-Hosting Docs + + +
+ +
+
+
+
+ +
+

Hybrid Deployment

+

+ Run in Rivet Cloud for production, use self-hosting for on-premises deployments +

+
+
+
+ ); +} diff --git a/website/src/app/(v2)/(marketing)/(index)/sections/HeroSection.tsx b/website/src/app/(v2)/(marketing)/(index)/sections/HeroSection.tsx index a074198dfa..d783e3de4c 100644 --- a/website/src/app/(v2)/(marketing)/(index)/sections/HeroSection.tsx +++ b/website/src/app/(v2)/(marketing)/(index)/sections/HeroSection.tsx @@ -1,4 +1,3 @@ -import { PlatformIcons } from "../components/PlatformIcons"; import { MarketingButton } from "../components/MarketingButton"; import { CopyCommand } from "../components/CopyCommand"; import Link from "next/link"; @@ -7,7 +6,7 @@ import { WithTooltip } from "@rivet-gg/components"; export function HeroSection() { return (
-
+
{/* Main content centered vertically */} @@ -17,11 +16,39 @@ export function HeroSection() {

{/*Lightweight library for building modern backends*/} {/*Library for building stateful applications and distributed systems*/} - Build and scale stateful workloads + {/*Build and scale stateful workloads*/} + State + Compute = Less Complexity

+

+ Rivet Actors merge state and compute in to a primitive that scales effortlessly with less complex infrastructure. + Easily self-hostable and works with your infrastructure. +

+ + {/*

+ Rivet Actors are lightweight processes that unite state and compute.
Scales effortlessly with less complex infrastructure.
+ Easily{" "} + self-hostable{" "} + and works with{" "} + + your infrastructure + + . +

*/} + + {/*

+ Rivet Actors are lightweight processes that merge state and compute{" "}
in to a primitive that scales effortlessly with less complex infrastructure.
+ Easily{" "} + self-hostable{" "} + and works with{" "} + + your infrastructure + + . +

*/} + {/*

Rivet is a library for{" "} long-lived processes{" "} with{" "} @@ -40,7 +67,7 @@ export function HeroSection() {

*/} -

+ {/*

Rivet is an open-source library for{" "} long-lived processes.
@@ -56,7 +83,7 @@ export function HeroSection() { your infrastructure . -

+

*/}
@@ -88,10 +115,6 @@ export function HeroSection() {
-
- - -
diff --git a/website/src/app/(v2)/(marketing)/(index)/sections/IconWithSpotlight.tsx b/website/src/app/(v2)/(marketing)/(index)/sections/IconWithSpotlight.tsx index 6ebc51b11a..9e9145120f 100644 --- a/website/src/app/(v2)/(marketing)/(index)/sections/IconWithSpotlight.tsx +++ b/website/src/app/(v2)/(marketing)/(index)/sections/IconWithSpotlight.tsx @@ -12,6 +12,21 @@ export function IconWithSpotlight({ iconPath, title }: IconWithSpotlightProps) { return "M144 270L144 341.4L237.8 400L402.3 400L496.1 341.4L496.1 270L416.1 320L224.1 320L144.1 270zM96 240L96 144L224 64L416 64L544 144L544 496L416 576L224 576L96 496L96 240zM496 192L496 170.6L402.2 112L237.7 112L143.9 170.6L143.9 213.4L237.7 272L402.2 272L496 213.4L496 192zM144 469.4L237.8 528L402.3 528L496.1 469.4L496.1 398L416.1 448L224.1 448L144.1 398L144.1 469.4z"; case "/icons/bolt.svg": return "M414.7 48L407.8 54.6L103.8 342.6L60.1 384L247.5 384L193.4 561L183.9 592L225.9 592L232.8 585.4L536.8 297.4L580.5 256L393.1 256L447.2 79L456.7 48L414.7 48zM180.5 336L374.7 152C345.5 247.7 330 298.3 328.3 304L460 304L265.8 488C295 392.4 310.5 341.7 312.2 336L180.4 336z"; + // Use case icons + case "/use-case-icons/sparkles.svg": + return "M480 96L408 128L480 160L512 232L544 160L616 128L544 96L512 24L480 96zM288 384C348.9 356.9 396.9 335.6 432 320C396.9 304.4 348.9 283.1 288 256C260.9 195.1 239.6 147.1 224 112C208.4 147.1 187.1 195.1 160 256C99.1 283.1 51.1 304.4 16 320C51.1 335.6 99.1 356.9 160 384C187.1 444.9 208.4 492.9 224 528C239.6 492.9 260.9 444.9 288 384zM224 409.8C208.5 374.9 199.3 354.1 196.4 347.6C189.9 344.7 169.2 335.5 134.2 320C169.1 304.5 189.9 295.3 196.4 292.4C199.3 285.9 208.5 265.2 224 230.2C239.5 265.1 248.7 285.9 251.6 292.4C258.1 295.3 278.8 304.5 313.8 320C278.9 335.5 258.1 344.7 251.6 347.6C248.7 354.1 239.5 374.8 224 409.8zM480 408L448 480L376 512L448 544L480 616L512 544L584 512L512 480L480 408z"; + case "/use-case-icons/diagram-next.svg": + return "M496 240L144 240L144 144L496 144L496 240zM544 288L544 96L96 96L96 288L296 288L296 374.1C279.6 357.7 266.3 344.4 256 334.1L222.1 368C225.6 371.5 252.6 398.5 303.1 449L320.1 466C323.6 462.5 350.6 435.5 401.1 385L418.1 368L384.2 334.1C373.9 344.4 360.6 357.7 344.2 374.1L344.2 288L544.2 288zM184 352L96 352L96 544L544 544L544 352L456 352L456 400L496 400L496 496L144 496L144 400L184 400L184 352z"; + case "/use-case-icons/gears.svg": + return "M190.3 25.8C190.3 25.8 215.1 32.4 264.5 45.7L276.9 49C276.7 52.6 275.9 71.1 274.5 104.6C276.2 106.2 277.9 107.9 279.5 109.6C313 108.2 331.6 107.4 335.1 107.2L338.4 119.6L355 181.4L358.3 193.8C355.2 195.4 338.7 204 308.9 219.5C308.4 221.8 307.7 224.1 307.1 226.3C325.1 254.6 335.1 270.3 337 273.3L327.9 282.4L282.6 327.7L273.5 336.8C270.5 334.9 254.8 324.9 226.5 306.9C224.2 307.6 222 308.2 219.6 308.7C204.1 338.4 195.5 354.9 193.9 358.1L181.5 354.8L119.7 338.2L107.3 334.9C107.5 331.3 108.3 312.8 109.7 279.3C108 277.7 106.3 276 104.7 274.3C71.2 275.7 52.6 276.5 49.1 276.7C49.1 276.7 42.5 251.9 29.2 202.5L26 190.1L75.4 164.4C75.9 162.1 76.6 159.8 77.2 157.6C59.2 129.3 49.2 113.6 47.3 110.6L101.6 56.3L110.7 47.2C113.7 49.1 129.4 59.1 157.7 77.1C160 76.4 162.2 75.8 164.6 75.3C180.1 45.6 188.7 29.1 190.3 25.9zM206.8 63.4C192.9 90 185.9 103.5 185.7 103.8L177.8 105.1C172.1 106 166.5 107.5 161.2 109.6L153.7 112.4C153.4 112.2 140.5 104 115.2 87.9L88.1 115C104.2 140.4 112.4 153.2 112.6 153.5L109.8 161C108.8 163.7 107.9 166.4 107.2 169.2C106.5 172 105.8 174.8 105.4 177.6L104 185.5C103.7 185.7 90.2 192.7 63.5 206.6L73.4 243.6L119 241.6L124.1 247.8C127.7 252.2 131.8 256.3 136.3 260L142.5 265.1C142.5 265.5 141.8 280.7 140.5 310.7L177.5 320.6C191.4 294 198.4 280.5 198.6 280.2L206.5 278.9C212.2 278 217.8 276.5 223.1 274.4L230.6 271.6C230.9 271.8 243.8 280 269.1 296.1L296.2 269C280.1 243.7 271.9 230.8 271.7 230.5L274.5 223C275.5 220.3 276.4 217.6 277.1 214.8C277.8 212 278.5 209.2 278.9 206.4L280.2 198.5C280.5 198.3 294 191.3 320.7 177.4L310.8 140.4C280.8 141.7 265.6 142.4 265.2 142.4L260.1 136.2C256.5 131.8 252.4 127.7 247.9 124L241.7 118.9C241.7 118.5 242.4 103.3 243.7 73.3L206.7 63.4zM177.7 246.1C147.8 238.1 130.1 207.4 138.1 177.5C146.1 147.6 176.8 129.9 206.7 137.9C236.6 145.9 254.3 176.6 246.3 206.5C238.3 236.4 207.6 254.1 177.7 246.1zM215.4 198.2C218.8 185.4 211.2 172.2 198.4 168.8C185.6 165.4 172.4 173 169 185.8C165.6 198.6 173.2 211.8 186 215.2C198.8 218.6 212 211 215.4 198.2zM363.4 305.1L375.8 301.8L437.6 285.2L450 281.9C451.6 285.1 460.2 301.5 475.7 331.3C478 331.9 480.3 332.5 482.6 333.1C510.9 315.1 526.6 305.1 529.6 303.2L538.7 312.3L584 357.6L593.1 366.7C591.2 369.7 581.2 385.4 563.2 413.7C563.9 415.9 564.5 418.2 565 420.5C594.8 436 611.2 444.6 614.4 446.2L611.1 458.6L594.5 520.4L591.2 532.8C587.6 532.6 569.1 531.8 535.6 530.4C534 532.1 532.3 533.8 530.6 535.4C532 568.9 532.8 587.4 533 591L520.6 594.3L458.8 610.9L446.4 614.2C444.8 611 436.2 594.6 420.7 564.8C418.4 564.3 416.1 563.6 413.8 563C385.5 581 369.8 591 366.8 592.9L357.7 583.8L312.4 538.5L303.3 529.4C305.2 526.4 315.2 510.7 333.2 482.4C332.9 481.3 332.5 480.1 332.2 479C331.9 477.9 331.6 476.7 331.3 475.6C301.5 460.1 285.1 451.5 281.9 449.9L285.2 437.5L301.8 375.7L305.1 363.3C308.7 363.5 327.2 364.3 360.7 365.7C362.3 364 364 362.3 365.7 360.7C364.3 327.2 363.5 308.7 363.3 305.1zM396.5 329.4L398.1 367L398.4 375L392.2 380.1C387.7 383.8 383.7 387.9 380 392.3L374.9 398.5L366.9 398.2L329.3 396.6L319.4 433.6L352.8 451L359.9 454.7L361.2 462.6C361.7 465.4 362.3 468.2 363 471C363.7 473.8 364.6 476.6 365.6 479.2L368.4 486.7L364.1 493.4L343.9 525.2L371 552.3C396.3 536.2 409.2 528 409.5 527.8L417 530.6C422.4 532.6 427.9 534.1 433.6 535.1L441.5 536.4L445.2 543.5L462.6 576.9L499.6 567L498 529.4L497.7 521.4L503.9 516.3C508.4 512.6 512.4 508.5 516.1 504.1L521.2 497.9L529.2 498.2L566.8 499.8L576.7 462.8L543.3 445.4L536.2 441.7L534.9 433.8C534.4 431 533.8 428.2 533.1 425.4C532.4 422.6 531.5 419.8 530.5 417.2L527.7 409.7C527.9 409.4 536.1 396.5 552.2 371.2L525.1 344.1C499.8 360.2 486.9 368.4 486.6 368.6L479.1 365.8C473.7 363.8 468.2 362.3 462.5 361.3L454.6 360L450.9 352.9L433.5 319.5L396.5 329.4zM462.6 502.2C432.7 510.2 402 492.5 394 462.6C386 432.7 403.7 402 433.6 394C463.5 386 494.2 403.7 502.2 433.6C510.2 463.5 492.5 494.2 462.6 502.2zM471.3 441.9C467.9 429.1 454.7 421.5 441.9 424.9C429.1 428.3 421.5 441.5 424.9 454.3C428.3 467.1 441.5 474.7 454.3 471.3C467.1 467.9 474.7 454.7 471.3 441.9z"; + case "/use-case-icons/rotate.svg": + return "M576 107.9L576 40C542.6 73.4 513.4 102.6 488.6 127.4C388.1 39.2 234.9 43 139 139C89 189 64 254.5 64 320L112 320C112 266.7 132.3 213.5 172.9 172.9C250.1 95.7 372.9 91.9 454.6 161.4C430.4 185.6 401.6 214.4 368 248L576 248L576 107.9zM528 200L483.9 200L528 155.9L528 200zM185.4 478.6C209.6 454.4 238.4 425.6 272 392L64 392L64 600C97.4 566.6 126.6 537.4 151.4 512.6C252 600.8 405.1 596.9 501 501C551 451 576 385.5 576 320L528 320C528 373.3 507.7 426.5 467.1 467.1C389.9 544.3 267.1 548.1 185.4 478.6zM112 484.1L112 440L156.1 440L112 484.1z"; + case "/use-case-icons/database.svg": + return "M144 270L144 341.4L237.8 400L402.3 400L496.1 341.4L496.1 270L416.1 320L224.1 320L144.1 270zM96 240L96 144L224 64L416 64L544 144L544 496L416 576L224 576L96 496L96 240zM496 192L496 170.6L402.2 112L237.7 112L143.9 170.6L143.9 213.4L237.7 272L402.2 272L496 213.4L496 192zM144 469.4L237.8 528L402.3 528L496.1 469.4L496.1 398L416.1 448L224.1 448L144.1 398L144.1 469.4z"; + case "/use-case-icons/globe.svg": + return "M192.8 344C197.4 410.4 220.3 472 247.7 515.1C175.6 488.4 122.4 422.9 113.3 344L192.8 344zM240.9 344L399 344C395.1 393.3 379.5 439.8 360.1 475.1C348.9 495.6 337.2 510.7 327.4 520C324.2 523 321.7 525 319.9 526.2C318.1 524.9 315.6 523 312.4 520C302.6 510.7 291 495.6 279.7 475.1C260.3 439.8 244.8 393.3 240.8 344zM399 296L241 296C244.9 246.7 260.5 200.2 279.9 164.9C291.1 144.4 302.8 129.3 312.6 120C315.8 117 318.3 115 320.1 113.8C321.9 115.1 324.4 117 327.6 120C337.4 129.3 349 144.4 360.3 164.9C379.7 200.2 395.2 246.7 399.2 296zM447.1 344L526.6 344C517.5 422.9 464.3 488.4 392.2 515.1C419.5 472 442.5 410.4 447.1 344zM526.6 296L447.1 296C442.5 229.6 419.6 168 392.2 124.9C464.3 151.6 517.5 217.1 526.6 296zM192.8 296L113.3 296C122.4 217.1 175.6 151.6 247.7 124.9C220.4 168 197.4 229.6 192.8 296zM320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576z"; + case "/use-case-icons/file-pen.svg": + return "M240 112L112 112L112 528L284.2 528L276.2 576L64 576L64 64L288 64L448 224L448 331.7L400 379.7L400 272L240 272L240 112zM380.1 224L288 131.9L288 224L380.1 224zM544 303.9L624 383.9L571.2 436.7L491.2 356.7L544 303.9zM336 511.9L468.6 379.3L548.6 459.3L416 591.9L320 607.9L336 511.9z"; default: return ""; } @@ -32,7 +47,7 @@ export function IconWithSpotlight({ iconPath, title }: IconWithSpotlightProps) { + + + ); +} diff --git a/website/src/app/(v2)/(marketing)/(index)/sections/UseCases.tsx b/website/src/app/(v2)/(marketing)/(index)/sections/UseCases.tsx new file mode 100644 index 0000000000..f77201b986 --- /dev/null +++ b/website/src/app/(v2)/(marketing)/(index)/sections/UseCases.tsx @@ -0,0 +1,229 @@ +"use client"; + +import { useRef } from "react"; +import { useCases } from "@/data/use-cases"; +import Link from "next/link"; +import { IconWithSpotlight } from "../sections/IconWithSpotlight"; + +interface UseCaseCardProps { + title: string; + description: React.ReactNode; + href: string; + className?: string; + iconPath: string; + variant?: "default" | "large"; +} + +function UseCaseCard({ + title, + description, + href, + className = "", + iconPath, + variant = "default", +}: UseCaseCardProps) { + const cardRef = useRef(null); + + const handleMouseMove = (e: React.MouseEvent) => { + if (!cardRef.current) return; + const card = cardRef.current; + + // Find the icon container and convert coordinates to icon-relative + const iconContainer = card.querySelector('.icon-spotlight-container') as HTMLElement; + if (!iconContainer) return; + + // Get the icon's position relative to viewport + const iconRect = iconContainer.getBoundingClientRect(); + + // Calculate mouse position relative to the icon (not the card) + const x = ((e.clientX - iconRect.left) / iconRect.width) * 100; + const y = ((e.clientY - iconRect.top) / iconRect.height) * 100; + + // Set CSS custom properties on the icon container + iconContainer.style.setProperty('--mouse-x', `${x}%`); + iconContainer.style.setProperty('--mouse-y', `${y}%`); + }; + + if (variant === "large") { + return ( + +
+ {/* Gradient overlay on hover */} +
+ + {/* Left side: Content + Checkmarks */} +
+
+

+ {title} +

+

+ {description} +

+
+ + {/* Checkmarks */} +
+ {["Cloud & on-prem", "Supports realtime", "Works with AI SDK"].map((item) => ( +
+ + + + {item} +
+ ))} +
+
+ + {/* Right side: Icon */} +
+ +
+
+ + ); + } + + return ( + +
+ {/* Gradient overlay on hover */} +
+ + {/* Content */} +
+

+ {title} +

+

+ {description} +

+
+ + {/* Spotlight icon */} +
+ +
+
+ + ); +} + +export function UseCases() { + // Map the use cases we want to display + const selectedUseCases = [ + useCases.find((uc) => uc.title === "Agent Orchestration & MCP")!, // agent orchestration & mcp + useCases.find((uc) => uc.title === "Workflows")!, // workflows + useCases.find((uc) => uc.title === "Multiplayer Apps")!, // multiplayer apps + useCases.find((uc) => uc.title === "Local-First Sync")!, // local-first sync + useCases.find((uc) => uc.title === "Background Jobs")!, // background jobs + useCases.find((uc) => uc.title === "Per-Tenant Databases")!, // per-tenant databases + useCases.find((uc) => uc.title === "Geo-Distributed Database")!, // geo-distributed database + ]; + + // Map use case titles to icon paths + const getIconPath = (title: string): string => { + const iconMap: { [key: string]: string } = { + "Agent Orchestration & MCP": "/use-case-icons/sparkles.svg", + "Workflows": "/use-case-icons/diagram-next.svg", + "Multiplayer Apps": "/use-case-icons/file-pen.svg", + "Local-First Sync": "/use-case-icons/rotate.svg", + "Background Jobs": "/use-case-icons/gears.svg", + "Per-Tenant Databases": "/use-case-icons/database.svg", + "Geo-Distributed Database": "/use-case-icons/globe.svg", + }; + return iconMap[title] || ""; + }; + + // Get highlighted description + const getHighlightedDescription = (title: string): React.ReactNode => { + const descriptionMap: { [key: string]: React.ReactNode } = { + "Agent Orchestration & MCP": ( + <> + Build AI agents with Model Context Protocol and persistent state + + ), + "Workflows": ( + <> + Durable multi-step workflows with automatic state management + + ), + "Multiplayer Apps": ( + <> + Build realtime multiplayer applications with authoritative state + + ), + "Local-First Sync": ( + <> + Offline-first applications with server synchronization + + ), + "Background Jobs": ( + <> + Scheduled and recurring jobs without external queue infrastructure + + ), + "Per-Tenant Databases": ( + <> + Isolated data stores for each user with zero-latency access + + ), + "Geo-Distributed Database": ( + <> + Store data close to users globally with automatic edge distribution + + ), + }; + return descriptionMap[title] || ""; + }; + + return ( +
+
+
+ {/* First item - takes 6 columns (half width on medium+) */} + + + {/* Remaining items - 3 columns each (quarter width on medium+) */} + {selectedUseCases.slice(1).map((useCase) => ( + + ))} +
+
+
+ ); +} diff --git a/website/src/components/CollapsibleSidebarItem.tsx b/website/src/components/CollapsibleSidebarItem.tsx index 9249e9c0ab..05c9b13bfa 100644 --- a/website/src/components/CollapsibleSidebarItem.tsx +++ b/website/src/components/CollapsibleSidebarItem.tsx @@ -88,7 +88,7 @@ export function CollapsibleSidebarItem({