Skip to content
Open
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
121 changes: 121 additions & 0 deletions .github/workflows/jawn-valkey-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
name: Jawn Valkey Compatibility Tests

on:
workflow_dispatch: # Manual trigger for testing iterations
push:
branches:
- main
paths:
- "valhalla/jawn/**"
- "packages/**"
- ".github/workflows/jawn-valkey-test.yml"
pull_request:
branches:
- main
paths:
- "valhalla/jawn/**"
- "packages/**"
- ".github/workflows/jawn-valkey-test.yml"

permissions:
contents: read

jobs:
jawn-valkey-tests:
name: Jawn Valkey Compatibility Tests
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'

# Generate self-signed TLS certificates for Valkey
- name: Generate TLS certificates
run: |
mkdir -p /tmp/valkey-tls
openssl req -x509 -newkey rsa:4096 -sha256 -days 1 \
-nodes -keyout /tmp/valkey-tls/server.key \
-out /tmp/valkey-tls/server.crt \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
chmod 600 /tmp/valkey-tls/server.key
chmod 644 /tmp/valkey-tls/server.crt

# Start Valkey 7.2 with TLS enabled (minimum supported version)
- name: Start Valkey with TLS
run: |
docker run -d --name valkey \
-p 6379:6379 \
-v /tmp/valkey-tls:/tls:ro \
valkey/valkey:7.2-alpine \
valkey-server \
--tls-port 6379 \
--port 0 \
--tls-cert-file /tls/server.crt \
--tls-key-file /tls/server.key \
--tls-auth-clients no

- name: Wait for Valkey to be ready
run: |
for i in $(seq 1 30); do
if docker exec valkey valkey-cli --tls --cert /tls/server.crt --key /tls/server.key --cacert /tls/server.crt ping | grep -q PONG; then
echo "Valkey is ready with TLS"
exit 0
fi
sleep 1
done
echo "Valkey failed to start"
docker logs valkey
exit 1

# Cache node_modules using root yarn.lock (the real lockfile)
- name: Cache dependencies
id: cache
uses: actions/cache@v4
with:
path: |
node_modules
valhalla/jawn/node_modules
packages/*/node_modules
key: deps-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
deps-${{ runner.os }}-

# Install from root using yarn workspaces (only if cache miss)
- name: Install dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: |
echo "Cache miss - installing dependencies from root workspace"
yarn install --frozen-lockfile --network-timeout 100000

# Show cache status
- name: Cache status
run: |
if [ "${{ steps.cache.outputs.cache-hit }}" = "true" ]; then
echo "Using cached dependencies - skipped yarn install!"
else
echo "Cache miss - installed dependencies"
fi

- name: Run Valkey integration tests
working-directory: valhalla/jawn
env:
REDIS_HOST: localhost
REDIS_PORT: 6379
NODE_EXTRA_CA_CERTS: /tmp/valkey-tls/server.crt
# --forceExit is required: ioredis keeps handles open that prevent Jest from
# exiting cleanly even after quit() in afterAll. Without it the CI job hangs.
run: npx jest --detectOpenHandles --forceExit src/lib/clients/__tests__/redisClient.integration.test.ts

# Optional: Include test summary in PR
- name: Test Summary
if: always()
run: |
echo "Jawn Valkey compatibility tests completed (TLS enabled, Valkey 7.2)"

- name: Cleanup
if: always()
run: docker rm -f valkey || true
3 changes: 2 additions & 1 deletion docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,8 @@
"pages": [
"getting-started/self-host/overview",
"getting-started/self-host/docker",
"getting-started/self-host/kubernetes"
"getting-started/self-host/kubernetes",
"getting-started/self-host/valkey"
]
},
"references/provider-integration",
Expand Down
97 changes: 97 additions & 0 deletions docs/getting-started/self-host/valkey.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
title: "Valkey"
sidebarTitle: "Valkey"
description: "Use Valkey as the Redis-compatible backend for Helicone's Jawn API server. Drop-in replacement requiring only a host configuration change."
"twitter:title": "Valkey Backend Support - Helicone OSS LLM Observability"
---

## Overview

Valkey is an officially supported, Redis-compatible backend for Helicone's Jawn API server. The `ioredis` client connects to Valkey with zero code changes. All five Redis subsystems' commands are verified compatible on both backends — the three core subsystems (KV Cache, Encrypted Key Cache, Proxy Rate Limiter) are imported and exercised directly in CI, while the remaining two (HTTP API Rate Limiter, Usage Limit Cache) have their command patterns verified. CI tests against Valkey 7.2 (the minimum supported version).

## Configuration

Set the `REDIS_HOST` environment variable to your Valkey endpoint:

```bash
REDIS_HOST=your-valkey-host.example.com
```

Jawn always connects with TLS enabled. Your Valkey instance must accept TLS connections. Most managed Valkey/Redis services (AWS ElastiCache, GCP Memorystore, etc.) enable TLS by default. For self-hosted deployments, configure Valkey with `--tls-port` and provide server certificates.

<Note>
For local development without TLS, you can run Jawn without setting `REDIS_HOST` — it will
fall back to in-process memory caching (see Graceful Degradation below).
</Note>

## What's Verified

The following table shows Valkey compatibility status for each Redis subsystem in Jawn:

| Subsystem | File | Commands | Status |
|-----------|------|----------|--------|
| KV Cache | `src/lib/cache/kvCache.ts` | `GET`, `SET PX` | Verified in CI (subsystem imported) |
| Encrypted Key Cache | `src/lib/cache/staticMemCache.ts` | `GET`, `SET EX` | Verified in CI (subsystem imported) |
| Proxy Rate Limiter | `src/lib/proxy/RateLimiter.ts` | `GET`, `SET EX` | Verified in CI (subsystem imported) |
| HTTP API Rate Limiter | `src/middleware/ratelimitter.ts` | `SCRIPT LOAD`, `EVALSHA`, `INCR`, `PTTL`, `PEXPIRE` | Commands verified in CI |
| Usage Limit Cache | `src/managers/UsageLimitManager.ts` | `GET`, `SET EX` | Commands verified in CI |

## Docker Compose Example

```yaml
services:
valkey:
image: valkey/valkey:7.2-alpine
command: >
valkey-server
--tls-port 6379
--port 0
--tls-cert-file /tls/server.crt
--tls-key-file /tls/server.key
--tls-auth-clients no
volumes:
- ./certs:/tls:ro
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "valkey-cli", "--tls", "--cert", "/tls/server.crt", "--key", "/tls/server.key", "--cacert", "/tls/server.crt", "ping"]
interval: 10s
timeout: 5s
retries: 5
jawn:
image: helicone/jawn:latest
environment:
REDIS_HOST: valkey
depends_on:
valkey:
condition: service_healthy
```

<Warning>
This example uses `--tls-auth-clients no` for simplicity. For production deployments,
evaluate enabling mutual TLS (`--tls-auth-clients yes`) or rely on network-level isolation
(private subnets, security groups) to restrict access.
</Warning>

Generate self-signed certificates for local testing:

```bash
mkdir -p certs
openssl req -x509 -newkey rsa:4096 -sha256 -days 365 \
-nodes -keyout certs/server.key -out certs/server.crt \
-subj "/CN=valkey" -addext "subjectAltName=DNS:valkey,DNS:localhost,IP:127.0.0.1"
```

## Migrating from Redis

1. Update `REDIS_HOST` to point to your Valkey instance (must support TLS)
2. Restart Jawn
3. Existing cached data in Redis will naturally expire (all values have TTLs)

## Graceful Degradation

When `REDIS_HOST` is not set, Jawn operates without a cache backend using in-process memory caching only. This is useful for development or minimal deployments.

## Requirements

Valkey 7.2 or later (CI tests against 7.2). No modules or extensions required. TLS is required.
Loading