sanity-check

bởi rivet-dev

Chạy kiểm tra khói E2E cài đặt các gói rivetkit từ npm trong một dự án tạm thời độc lập, khởi chạy actor hello-world counter, sau đó xác minh cả HTTP…

npx skills add https://github.com/rivet-dev/rivet --skill sanity-check

Sanity Check

Run a quick end-to-end sanity check of a published rivetkit version: copy the hello-world example to a temp directory, install the specified package version from the public npm registry, start the dev server, and run a client test script that verifies both HTTP actions and WebSocket connections with event broadcasting.

When to use

  • User wants to verify a published rivetkit version works (e.g., rivetkit@0.0.0-pr.4701.a818b77, rivetkit@latest, rivetkit@2.5.0)
  • After a preview publish to verify the build is functional
  • After a release to verify the package installs and runs correctly
  • User says "sanity check", "smoke test", "verify the build", or "test this version"

Inputs

  1. Version or tag (required) — explicit pkg-pr-new preview, npm dist-tag, or semver. If not provided in the user's message, ask for it.
  2. Additional test behavior (optional) — e.g., "also verify workflows persist" or "check that KV works." If provided, extend src/index.ts + test.mjs using the menu in "Extending with custom tests" below.

Usage

  • /sanity-check <version> — run in a temp directory on the host
  • /sanity-check docker <version> — run inside a node:22 Docker container
  • /sanity-check <version> <custom instructions> — any extra instructions (e.g. "also hit a KV action", "verify state persists across restart", "use pnpm", "test on node 20")

<version> is any npm-resolvable spec: an explicit pkg-pr-new preview (0.0.0-pr.4701.a818b77), an npm dist-tag (latest, rc, next), or a semver (2.3.0-rc.4). If the user omits it, ask for it.

What it tests

  1. npm install of rivetkit, @rivetkit/react, and the platform-specific @rivetkit/rivetkit-napi-* native binding from the public npm registry
  2. Boot the hello-world counter actor server via registry.start() on port 6420
  3. HTTP path: call counter.increment(5), counter.increment(3), counter.getCount() and assert the values
  4. WebSocket path: open a .connect() conn, subscribe to the newCount event, call increment(10), assert the action response AND the broadcast event value match
  5. Report the resolved versions of rivetkit + @rivetkit/rivetkit-napi

Requirements

  • Node.js 22+ (or Docker with node:22)
  • This repo available locally — only used to copy examples/hello-world/src/ as the seed. Does NOT use any of its node_modules or workspace links.

Steps

1. Set up the test project

REPO_ROOT=$(git rev-parse --show-toplevel)
SANITY_DIR=$(mktemp -d /tmp/rivetkit-sanity-XXXXXX)
cp -r "$REPO_ROOT/examples/hello-world/src" "$REPO_ROOT/examples/hello-world/tsconfig.json" "$SANITY_DIR/"
cd "$SANITY_DIR"

Write package.json with <VERSION> substituted:

{
  "name": "rivetkit-sanity",
  "private": true,
  "type": "module",
  "dependencies": {
    "rivetkit": "<VERSION>",
    "@rivetkit/react": "<VERSION>"
  },
  "devDependencies": {
    "tsx": "^4",
    "typescript": "^5"
  }
}

For pkg-pr-new previews, <VERSION> can be the bare version string (0.0.0-pr.4701.a818b77) — npm resolves it directly. If that fails, fall back to the URL form: "rivetkit": "https://pkg.pr.new/rivet-dev/rivet/rivetkit@<short-sha>".

2. Write test.mjs

import { createClient } from "rivetkit/client";
import { spawn } from "node:child_process";

const ENDPOINT = "http://localhost:6420";

console.log("Starting counter actor server...");
const server = spawn("npx", ["tsx", "src/index.ts"], {
  stdio: ["ignore", "pipe", "pipe"],
});
let log = "";
server.stdout.on("data", (d) => (log += d));
server.stderr.on("data", (d) => (log += d));

async function waitForServer(timeoutMs = 30000) {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    try {
      const r = await fetch(`${ENDPOINT}/health`);
      if (r.ok) return;
    } catch {}
    await new Promise((r) => setTimeout(r, 500));
  }
  console.error("--- server log ---\n" + log);
  throw new Error("server did not become ready");
}

let exitCode = 0;
try {
  await waitForServer();
  const client = createClient(ENDPOINT);

  // --- HTTP actions ---
  console.log("Testing HTTP actions...");
  const counter = client.counter.getOrCreate(["sanity"]);
  const a = await counter.increment(5);
  const b = await counter.increment(3);
  const c = await counter.getCount();
  if (a !== 5) throw new Error(`increment(5) => ${a}, expected 5`);
  if (b !== 8) throw new Error(`increment(3) => ${b}, expected 8`);
  if (c !== 8) throw new Error(`getCount() => ${c}, expected 8`);
  console.log(`  HTTP: increment(5)=${a}, increment(3)=${b}, getCount()=${c}`);

  // --- WebSocket actions + events ---
  console.log("Testing WebSocket + events...");
  const ws = client.counter.getOrCreate(["sanity-ws"]).connect();
  await new Promise((res, rej) => {
    const t = setTimeout(() => rej(new Error("ws open timeout")), 10000);
    ws.onOpen(() => {
      clearTimeout(t);
      res();
    });
    ws.onError(rej);
  });
  const eventPromise = new Promise((res, rej) => {
    const t = setTimeout(() => rej(new Error("newCount event timeout")), 5000);
    ws.on("newCount", (v) => {
      clearTimeout(t);
      res(v);
    });
  });
  const wsCount = await ws.increment(10);
  const eventValue = await eventPromise;
  if (wsCount !== 10) throw new Error(`ws increment(10) => ${wsCount}, expected 10`);
  if (eventValue !== 10) throw new Error(`newCount event => ${eventValue}, expected 10`);
  console.log(`  WS: increment(10)=${wsCount}, event=${eventValue}`);
  await ws.dispose();

  console.log("\n✅ E2E TEST PASSED");
} catch (err) {
  console.error(`\n❌ E2E TEST FAILED: ${err.message || err}`);
  console.error("--- server log (last 2KB) ---\n" + log.slice(-2000));
  exitCode = 1;
} finally {
  server.kill("SIGKILL");
  process.exit(exitCode);
}

3. Install + run

Default (host):

cd "$SANITY_DIR"
npm install
node test.mjs

If you need to inspect a failure after the fact, tee the output:

node test.mjs 2>&1 | tee /tmp/sanity-check.log
echo "exit=$?"

Docker mode:

docker run --rm \
  -v "$SANITY_DIR":/app \
  -w /app \
  node:22 \
  bash -c "npm install && timeout 120 node test.mjs"

4. Report installed versions

Surface the resolved versions (rivetkit's exports doesn't expose ./package.json, so read the file directly):

node -e "
for (const p of ['rivetkit','@rivetkit/react','@rivetkit/rivetkit-napi']) {
  try {
    const v = JSON.parse(require('fs').readFileSync('node_modules/'+p+'/package.json','utf8')).version;
    console.log(p, v);
  } catch (e) { console.log(p, '(not installed:', e.code || e.message, ')'); }
}
"

5. Report results

Tell the user:

  • Resolved versions (rivetkit + @rivetkit/rivetkit-napi)
  • HTTP path results
  • WebSocket path results + event value
  • ✅ or ❌ with the last 2KB of server log on failure

6. Clean up

rm -rf "$SANITY_DIR"

Extending with custom tests

If the user asks for extra behavior, modify src/index.ts and add assertions to test.mjs before running. Common asks and where to slot them:

  • KV round-trip: add kv actions (setKv: (c, key, val) => c.kv.set(key, val), getKv: (c, key) => c.kv.get(key)), then in test.mjs call set → get and assert.
  • Workflow: add a workflow action, await its completion, assert the final state.
  • SQLite + migrations: add a migration and a query action, call it, assert rows.
  • State persistence: increment, kill the server (server.kill("SIGTERM")), await exit, respawn, call getCount(), assert value preserved.
  • Multiple actor instances: use different keys, verify isolation.

Start from the base test above; layer additions rather than rewriting it.

Rules

  • Always use a fresh temp directory — never run in the repo itself.
  • Always install from the public npm registry or pkg-pr-new — never use local workspace links / file: deps.
  • Pin rivetkit + @rivetkit/react to the exact user-specified version; let npm's optionalDependencies resolve the right @rivetkit/rivetkit-napi-<platform> binary automatically.
  • If npm install fails to resolve a bare pkg-pr-new version string, retry using the https://pkg.pr.new/rivet-dev/rivet/<pkg>@<short-sha> URL form.
  • If the server doesn't reach /health in 30s, dump the last 2KB of server stderr/stdout before failing — most install/runtime issues show up there (missing native binary, wrong Node version, port collision).
  • On Docker mode, run rm -rf $SANITY_DIR only after docker run --rm exits so container-created node_modules get cleaned by the --rm flag.

Thêm skills từ rivet-dev

multiplayer-game
rivet-dev
Các mẫu thiết kế thực tế để xây dựng trò chơi nhiều người chơi: ghép trận, vòng lặp tick, trạng thái thời gian thực, quản lý tương tác và xác thực. Bao gồm mã khởi đầu và các mẫu kiến trúc cho 10 thể loại trò chơi (battle royale, đấu trường, phong cách IO, thế giới mở, tiệc, vật lý 2D/3D, xếp hạng, theo lượt, nhàn rỗi) với cấu trúc tác nhân, sơ đồ vòng đời và chiến lược mạng. Đề cập đến các nguyên tắc cơ bản của mô phỏng máy chủ: vòng lặp thời gian thực cố định so với cập nhật theo
rivetkit
rivet-dev
Tính toán có trạng thái, tồn tại lâu dài cho các tác nhân AI, ứng dụng đa người chơi và tự động hóa quy trình làm việc. Các tác nhân là các tiến trình trong bộ nhớ liên tục duy trì trạng thái qua các yêu cầu mà không cần truy vấn cơ sở dữ liệu, với khả năng tự động mở rộng từ 0 đến hàng triệu phiên bản đồng thời. Tích hợp sẵn giao tiếp thời gian thực qua WebSockets và sự kiện, hàng đợi bền vững để xử lý tin nhắn theo thứ tự, và SQLite cho các truy vấn dữ liệu có cấu trúc. Trạng thái tồn tại qua các lần khởi động lại và sự cố th
ai-agent
rivet-dev
Xây dựng backend cho AI agent với bộ nhớ liên tục: một Rivet Actor cho mỗi cuộc hội thoại, xử lý tin nhắn theo hàng đợi, và phản hồi LLM dạng stream dưới dạng sự kiện thời gian thực.
ai-agent-workspace
rivet-dev
Cung cấp cho mỗi tác nhân AI máy tính riêng của nó: một không gian làm việc liên tục với hệ thống tệp, tiến trình, shell, mạng và các phiên tác nhân trên một quy trình nhẹ…
chat-room
rivet-dev
Xây dựng backend phòng chat thời gian thực với Rivet Actors: một actor cho mỗi phòng, lịch sử tin nhắn dùng SQLite, và phát sóng WebSocket đến mọi client đã kết nối.
collaborative-text-editor
rivet-dev
Xây dựng backend cho trình soạn thảo văn bản cộng tác với Yjs CRDTs và Rivet Actors: các actor theo từng tài liệu chuyển tiếp đồng bộ hóa và cập nhật nhận thức, đồng thời lưu trữ ảnh chụp nhanh.
cron-jobs
rivet-dev
Các công việc cron bền bỉ với Rivet Actors: bộ hẹn giờ schedule.after và schedule.at tồn tại sau khi khởi động lại và sự cố, cùng với việc kích hoạt lại các công việc định kỳ và trình xử lý idempotent.
live-cursors
rivet-dev
Con trỏ trực tiếp và sự hiện diện đa người chơi với Rivet Actors: trạng thái con trỏ theo từng kết nối, cập nhật thời gian thực qua sự kiện hoặc WebSockets thô, và điều chỉnh tần suất.