diff --git a/rivetkit-typescript/CLAUDE.md b/rivetkit-typescript/CLAUDE.md index 57a59e4f7a..2f86caa989 100644 --- a/rivetkit-typescript/CLAUDE.md +++ b/rivetkit-typescript/CLAUDE.md @@ -42,6 +42,18 @@ DEBUG perf user: dbMigrateMs durationMs=... The log name matches the key in `ActorMetrics.startup`. Internal phases use `perf internal:`, user-code callbacks use `perf user:`. This convention keeps startup logs greppable and makes it easy to separate framework overhead from user-code time. When adding a new startup phase, always add a corresponding log with the appropriate prefix and update the `#userStartupKeys` set in `ActorInstance` if the phase runs user code. +## Drizzle Compatibility Testing + +To test rivetkit's drizzle integration against multiple drizzle-orm versions: + +```bash +cd rivetkit-typescript/packages/rivetkit +./scripts/test-drizzle-compat.sh # test all default versions +./scripts/test-drizzle-compat.sh 0.44.2 0.45.1 # test specific versions +``` + +The script installs each drizzle-orm version, runs the drizzle driver tests, and reports pass/fail per version. It restores the original package.json and lockfile on exit. Update the `DEFAULT_VERSIONS` array in the script and `SUPPORTED_DRIZZLE_RANGE` in `packages/rivetkit/src/db/drizzle/mod.ts` when adding support for new drizzle releases. + ## Workflow Context Actor Access Guards In `ActorWorkflowContext` (`packages/rivetkit/src/workflow/context.ts`), all side-effectful `#runCtx` access must be guarded by `#ensureActorAccess` so that side effects only run inside workflow steps and are not replayed outside of them. Read-only properties (e.g., `actorId`, `log`) do not need guards. When adding new methods or properties to the workflow context that delegate to `#runCtx`, apply the guard if the operation has side effects. diff --git a/rivetkit-typescript/packages/rivetkit/package.json b/rivetkit-typescript/packages/rivetkit/package.json index b0c312cdc7..b6ff1a23f8 100644 --- a/rivetkit-typescript/packages/rivetkit/package.json +++ b/rivetkit-typescript/packages/rivetkit/package.json @@ -366,7 +366,6 @@ "@e2b/code-interpreter": "^2.3.3", "dockerode": "^4.0.9", "drizzle-kit": "^0.31.2", - "drizzle-orm": "^0.44.2", "eventsource": "^4.0.0", "ws": "^8.0.0" }, @@ -383,9 +382,6 @@ "drizzle-kit": { "optional": true }, - "drizzle-orm": { - "optional": true - }, "eventsource": { "optional": true }, diff --git a/rivetkit-typescript/packages/rivetkit/scripts/test-drizzle-compat.sh b/rivetkit-typescript/packages/rivetkit/scripts/test-drizzle-compat.sh new file mode 100755 index 0000000000..80312ea9f6 --- /dev/null +++ b/rivetkit-typescript/packages/rivetkit/scripts/test-drizzle-compat.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# +# Tests rivetkit's drizzle integration against multiple drizzle-orm versions. +# +# Usage: +# ./scripts/test-drizzle-compat.sh # test all versions +# ./scripts/test-drizzle-compat.sh 0.44.2 0.45.1 # test specific versions +# +# Run from rivetkit-typescript/packages/rivetkit/. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PKG_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +ROOT_DIR="$(cd "$PKG_DIR/../../.." && pwd)" + +# Default versions to test. Uses the latest patch of each minor release. +# Add new minor versions here as drizzle releases them. +DEFAULT_VERSIONS=("0.44" "0.45") + +if [[ $# -gt 0 ]]; then + VERSIONS=("$@") +else + VERSIONS=("${DEFAULT_VERSIONS[@]}") +fi + +cd "$ROOT_DIR" + +# Back up files that pnpm add will modify. +cp "$PKG_DIR/package.json" "$PKG_DIR/package.json.drizzle-compat-bak" +cp "$ROOT_DIR/pnpm-lock.yaml" "$ROOT_DIR/pnpm-lock.yaml.drizzle-compat-bak" + +cleanup() { + echo "" + echo "Restoring original package.json and lockfile..." + mv "$PKG_DIR/package.json.drizzle-compat-bak" "$PKG_DIR/package.json" + mv "$ROOT_DIR/pnpm-lock.yaml.drizzle-compat-bak" "$ROOT_DIR/pnpm-lock.yaml" + cd "$ROOT_DIR" && pnpm install --frozen-lockfile 2>/dev/null || pnpm install +} +trap cleanup EXIT + +declare -A RESULTS + +for version in "${VERSIONS[@]}"; do + echo "" + echo "==========================================" + echo " Testing drizzle-orm@$version" + echo "==========================================" + + # Install the target version into the rivetkit package. + pnpm --filter rivetkit add -D "drizzle-orm@$version" 2>&1 | tail -3 + + # Run only the drizzle variant of the DB tests. + TEST_LOG="/tmp/drizzle-compat-${version}.log" + if cd "$PKG_DIR" && pnpm test driver-file-system -t "Actor Database \(drizzle\)" > "$TEST_LOG" 2>&1; then + RESULTS["$version"]="PASS" + echo " -> PASS" + else + RESULTS["$version"]="FAIL" + echo " -> FAIL (see $TEST_LOG)" + fi + cd "$ROOT_DIR" +done + +echo "" +echo "==========================================" +echo " Drizzle Compatibility Results" +echo "==========================================" +for version in "${VERSIONS[@]}"; do + printf " drizzle-orm@%-10s %s\n" "$version" "${RESULTS[$version]}" +done diff --git a/rivetkit-typescript/packages/rivetkit/src/db/drizzle/mod.ts b/rivetkit-typescript/packages/rivetkit/src/db/drizzle/mod.ts index 7182f05a6b..4912f64cc0 100644 --- a/rivetkit-typescript/packages/rivetkit/src/db/drizzle/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/db/drizzle/mod.ts @@ -1,3 +1,4 @@ +import { createRequire } from "node:module"; import type { IDatabase } from "@rivetkit/sqlite-vfs"; import { drizzle as proxyDrizzle, @@ -10,6 +11,54 @@ export * from "./sqlite-core"; import { type Config, defineConfig as originalDefineConfig } from "drizzle-kit"; +/** + * Supported drizzle-orm version bounds. Update these when testing confirms + * compatibility with new releases. Run scripts/test-drizzle-compat.sh to + * validate. + */ +const DRIZZLE_MIN = [0, 44, 0]; +const DRIZZLE_MAX = [0, 46, 0]; // exclusive + +let drizzleVersionChecked = false; + +function compareVersions(a: number[], b: number[]): number { + for (let i = 0; i < Math.max(a.length, b.length); i++) { + const diff = (a[i] ?? 0) - (b[i] ?? 0); + if (diff !== 0) return diff; + } + return 0; +} + +function isSupported(version: string): boolean { + // Strip prerelease suffix (e.g. "0.45.1-a7a15d0" -> "0.45.1") + const v = version.replace(/-.*$/, "").split(".").map(Number); + return ( + compareVersions(v, DRIZZLE_MIN) >= 0 && + compareVersions(v, DRIZZLE_MAX) < 0 + ); +} + +function checkDrizzleVersion() { + if (drizzleVersionChecked) return; + drizzleVersionChecked = true; + + try { + const require = createRequire(import.meta.url); + const { version } = require("drizzle-orm/package.json") as { + version: string; + }; + if (!isSupported(version)) { + console.warn( + `[rivetkit] drizzle-orm@${version} has not been tested with this version of rivetkit. ` + + `Supported: >= ${DRIZZLE_MIN.join(".")} and < ${DRIZZLE_MAX.join(".")}. ` + + `Things may still work, but please report issues at https://github.com/rivet-dev/rivet/issues`, + ); + } + } catch { + // Cannot determine version, skip check. + } +} + export function defineConfig( config: Partial, ): Config { @@ -141,6 +190,8 @@ export function db< >( config?: DatabaseFactoryConfig, ): DatabaseProvider & RawAccess> { + checkDrizzleVersion(); + // Map from drizzle client to the underlying @rivetkit/sqlite Database. // Uses WeakMap so entries are garbage-collected when the client is. // This replaces the previous shared `waDbInstance` variable which caused @@ -271,10 +322,7 @@ export function db< clientToKvStore.get(client as object)?.clearPreload(); const waDb = clientToRawDb.get(client as object); if (config?.migrations && waDb) { - await runInlineMigrations( - waDb, - config.migrations, - ); + await runInlineMigrations(waDb, config.migrations); } }, onDestroy: async (client) => {