From cc78f839a58edfbc3415e5cf290d481d29868445 Mon Sep 17 00:00:00 2001 From: Vladimir Vujosevic Date: Fri, 28 Nov 2025 15:55:52 +0100 Subject: [PATCH 1/2] feat: add local KV support with --local flag - Add support for switching between local and remote KV instances - Local KV defaults to http://localhost:8787 (wrangler dev default) - Remote KV uses https://api.cloudflare.com/client/v4 (production API) - Add -l/--local flag to CLI for easy switching - Update ClientConfig with is_local field and base_url() method - Add tests for local/remote endpoint switching - All existing tests pass --- crates/cfkv/src/cli.rs | 4 +++ crates/cfkv/src/main.rs | 8 +++++- crates/cloudflare-kv/src/client.rs | 45 +++++++++++++++++++++++++++++- crates/cloudflare-kv/src/types.rs | 31 +++++++++++++++++--- 4 files changed, 82 insertions(+), 6 deletions(-) diff --git a/crates/cfkv/src/cli.rs b/crates/cfkv/src/cli.rs index 745a12d..a95b309 100644 --- a/crates/cfkv/src/cli.rs +++ b/crates/cfkv/src/cli.rs @@ -33,6 +33,10 @@ pub struct Cli { #[arg(short, long)] pub debug: bool, + /// Use local KV instance (wrangler dev) + #[arg(short, long)] + pub local: bool, + #[command(subcommand)] pub command: Commands, } diff --git a/crates/cfkv/src/main.rs b/crates/cfkv/src/main.rs index 107eb4d..7561007 100644 --- a/crates/cfkv/src/main.rs +++ b/crates/cfkv/src/main.rs @@ -85,11 +85,17 @@ async fn main() -> Result<(), Box> { return Err("No storage configured. Add one with: cfkv storage add --account-id --namespace-id --api-token ".into()); }; - let client_config = ClientConfig::new( + let mut client_config = ClientConfig::new( &account_id, &namespace_id, cloudflare_kv::AuthCredentials::token(api_token), ); + + // Apply local flag if specified + if cli.local { + client_config = client_config.with_local(true); + } + let client = KvClient::new(client_config); match cli.command { diff --git a/crates/cloudflare-kv/src/client.rs b/crates/cloudflare-kv/src/client.rs index ccc7a46..68cb37d 100644 --- a/crates/cloudflare-kv/src/client.rs +++ b/crates/cloudflare-kv/src/client.rs @@ -259,7 +259,7 @@ mod tests { } #[test] - fn test_endpoint_urls() { + fn test_endpoint_urls_remote() { let config = test_config(); let kv_endpoint = config.kv_endpoint(); let list_endpoint = config.kv_list_endpoint(); @@ -267,9 +267,52 @@ mod tests { assert!( kv_endpoint.contains("accounts/account-id/storage/kv/namespaces/namespace-id/values") ); + assert!(kv_endpoint.contains("https://api.cloudflare.com/client/v4")); assert!( list_endpoint.contains("accounts/account-id/storage/kv/namespaces/namespace-id/keys") ); + assert!(list_endpoint.contains("https://api.cloudflare.com/client/v4")); + } + + #[test] + fn test_endpoint_urls_local() { + let creds = AuthCredentials::token("test-token"); + let config = ClientConfig::new("account-id", "namespace-id", creds).with_local(true); + let kv_endpoint = config.kv_endpoint(); + let list_endpoint = config.kv_list_endpoint(); + + assert!( + kv_endpoint.contains("accounts/account-id/storage/kv/namespaces/namespace-id/values") + ); + assert!(kv_endpoint.contains("http://localhost:8787")); + assert!( + list_endpoint.contains("accounts/account-id/storage/kv/namespaces/namespace-id/keys") + ); + assert!(list_endpoint.contains("http://localhost:8787")); + } + + #[test] + fn test_local_remote_switching() { + let creds = AuthCredentials::token("test-token"); + let mut config = ClientConfig::new("account-id", "namespace-id", creds); + + // Start with remote + assert!(!config.is_local); + assert!(config + .base_url() + .contains("https://api.cloudflare.com/client/v4")); + + // Switch to local + config = config.with_local(true); + assert!(config.is_local); + assert!(config.base_url().contains("http://localhost:8787")); + + // Switch back to remote + config = config.with_local(false); + assert!(!config.is_local); + assert!(config + .base_url() + .contains("https://api.cloudflare.com/client/v4")); } #[test] diff --git a/crates/cloudflare-kv/src/types.rs b/crates/cloudflare-kv/src/types.rs index 3cd52fe..7f89e8d 100644 --- a/crates/cloudflare-kv/src/types.rs +++ b/crates/cloudflare-kv/src/types.rs @@ -35,7 +35,9 @@ pub struct ClientConfig { pub account_id: String, pub namespace_id: String, pub credentials: AuthCredentials, - pub base_url: String, + pub remote_base_url: String, + pub local_base_url: String, + pub is_local: bool, } impl ClientConfig { @@ -49,15 +51,34 @@ impl ClientConfig { account_id: account_id.into(), namespace_id: namespace_id.into(), credentials, - base_url: "https://api.cloudflare.com/client/v4".to_string(), + remote_base_url: "https://api.cloudflare.com/client/v4".to_string(), + local_base_url: "http://localhost:8787".to_string(), + is_local: false, } } + /// Get the current base URL based on local/remote setting + pub fn base_url(&self) -> &str { + if self.is_local { + &self.local_base_url + } else { + &self.remote_base_url + } + } + + /// Set local mode + pub fn with_local(mut self, is_local: bool) -> Self { + self.is_local = is_local; + self + } + /// Get KV API endpoint URL pub fn kv_endpoint(&self) -> String { format!( "{}/accounts/{}/storage/kv/namespaces/{}/values", - self.base_url, self.account_id, self.namespace_id + self.base_url(), + self.account_id, + self.namespace_id ) } @@ -65,7 +86,9 @@ impl ClientConfig { pub fn kv_list_endpoint(&self) -> String { format!( "{}/accounts/{}/storage/kv/namespaces/{}/keys", - self.base_url, self.account_id, self.namespace_id + self.base_url(), + self.account_id, + self.namespace_id ) } } From 21167c172eefefe6013840b608a3d7e8c4e36550 Mon Sep 17 00:00:00 2001 From: Vladimir Vujosevic Date: Fri, 28 Nov 2025 15:56:36 +0100 Subject: [PATCH 2/2] docs: add comprehensive local KV development guide --- docs/LOCAL_KV_DEVELOPMENT.md | 277 +++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 docs/LOCAL_KV_DEVELOPMENT.md diff --git a/docs/LOCAL_KV_DEVELOPMENT.md b/docs/LOCAL_KV_DEVELOPMENT.md new file mode 100644 index 0000000..663db4a --- /dev/null +++ b/docs/LOCAL_KV_DEVELOPMENT.md @@ -0,0 +1,277 @@ +# Local KV Development with cfkv + +This guide explains how to use cfkv with Wrangler's local KV instance during development. + +## Overview + +When you run `wrangler dev`, it starts a local development server that includes a local KV instance. This allows you to test your Cloudflare Workers code locally without hitting the production API. + +The new `--local` flag in cfkv makes it easy to switch between your local development KV and remote production KV. + +## Prerequisites + +- **Wrangler** installed and configured +- **cfkv** installed and configured with your Cloudflare credentials +- A `wrangler.toml` file with KV namespace bindings + +## Getting Started + +### Step 1: Start Wrangler Dev Server + +First, start the local Wrangler development server: + +```bash +wrangler dev +``` + +This will start a local server (typically on `http://localhost:8787`) with a local KV instance running. + +**Important:** You must keep this terminal window open. The local KV instance only runs while `wrangler dev` is active. + +### Step 2: Configure cfkv + +Configure cfkv with your Cloudflare credentials (same as you'd use for production): + +```bash +cfkv storage add local \ + --account-id \ + --namespace-id \ + --api-token + +# Set this storage as active +cfkv storage switch local +``` + +### Step 3: Use the --local Flag + +Now you can use the `-l` or `--local` flag to operate on your local KV instance: + +```bash +# Put a value to LOCAL KV +cfkv put mykey --value "test value" --local + +# Get a value from LOCAL KV +cfkv get mykey --local + +# List keys in LOCAL KV +cfkv list --local + +# Delete a key from LOCAL KV +cfkv delete mykey --local +``` + +## Usage Patterns + +### Pattern 1: Always Use Local During Development + +When developing locally with `wrangler dev` running, always add the `--local` flag: + +```bash +# Development (local KV) +cfkv put user:123 --value '{"name": "Alice"}' --local +cfkv get user:123 --local + +# Production (remote KV) - don't forget to remove --local +cfkv put user:123 --value '{"name": "Alice"}' +cfkv get user:123 +``` + +### Pattern 2: Script with Environment Variable + +For scripting, you can use an environment variable to control the flag: + +```bash +#!/bin/bash + +# Set environment for local or remote +ENV=${1:-local} + +if [ "$ENV" = "local" ]; then + LOCAL_FLAG="--local" +else + LOCAL_FLAG="" +fi + +# Use in commands +cfkv put testkey --value "test" $LOCAL_FLAG +cfkv get testkey $LOCAL_FLAG +``` + +### Pattern 3: Batch Operations + +You can mix local and remote operations in a script: + +```bash +#!/bin/bash + +# Populate local KV for testing +cfkv put test:user1 --value '{"id": 1}' --local +cfkv put test:user2 --value '{"id": 2}' --local + +# After testing, copy to production +cfkv get test:user1 --local # Get from local +cfkv put user1 --value '{"id": 1}' # Put to remote (no --local flag) +``` + +## How It Works + +### Endpoint Configuration + +The `--local` flag switches between two endpoints: + +- **Local** (with `--local` flag): `http://localhost:8787` + - Used during development with `wrangler dev` + - No authentication required + - Fast iteration and testing + +- **Remote** (without `--local` flag): `https://api.cloudflare.com/client/v4` + - Used for production + - Requires API token authentication + - Real Cloudflare KV storage + +### Local Endpoint Details + +The default local endpoint is `http://localhost:8787`, which matches Wrangler's default development server port. This can be customized if needed. + +The local instance: +- Persists data for the duration of the `wrangler dev` session +- Resets when you stop and restart `wrangler dev` +- Does not require valid Cloudflare credentials +- Provides the same API interface as production KV + +## Common Scenarios + +### Scenario 1: Testing Data Persistence + +```bash +# Terminal 1: Start wrangler dev +wrangler dev + +# Terminal 2: Add test data to local KV +cfkv put counter --value "0" --local +cfkv put config:debug --value "true" --local + +# Verify it's there +cfkv get counter --local +cfkv list --local + +# Stop wrangler dev and restart it +# The data will be gone (fresh local instance) +``` + +### Scenario 2: Preparing Fixture Data + +```bash +# Create a script to setup fixture data for testing +cat > setup_fixtures.sh << 'EOF' +#!/bin/bash + +# Setup fixture data in local KV +cfkv put user:1 --value '{"name":"Alice","role":"admin"}' --local +cfkv put user:2 --value '{"name":"Bob","role":"user"}' --local +cfkv put settings:theme --value "dark" --local +cfkv put settings:language --value "en" --local + +echo "Fixtures loaded into local KV" +EOF + +chmod +x setup_fixtures.sh + +# Run it whenever you need fresh test data +./setup_fixtures.sh +``` + +### Scenario 3: Development vs Production Workflows + +```bash +# Development workflow +wrangler dev & +sleep 2 +cfkv put dev:test --value "test" --local +cfkv get dev:test --local + +# Production workflow +cfkv put prod:data --value "real data" +cfkv get prod:data +``` + +## Troubleshooting + +### "Connection refused" Error + +``` +Failed to get key mykey: 404 - ... +``` + +**Solution:** Make sure `wrangler dev` is running in another terminal. The local KV server must be active. + +### Data Not Persisting + +``` +# Put data +cfkv put mykey --value "test" --local + +# Stop wrangler dev and restart it + +# Data is gone +cfkv get mykey --local # Not found +``` + +**Explanation:** This is expected! Local data is temporary and resets when you restart `wrangler dev`. + +### Wrong Endpoint Error + +If you see errors like "Invalid credentials" when using `--local`, you might have forgotten the flag: + +```bash +# Wrong: This tries to hit production without valid credentials +cfkv put mykey --value "test" + +# Right: This hits local development server +cfkv put mykey --value "test" --local +``` + +### Authentication Issues with Local + +If you get authentication errors on local operations, remember that the local endpoint doesn't require credentials. If you're having issues: + +1. Verify `wrangler dev` is running +2. Check that it's on `http://localhost:8787` (or your configured port) +3. Try the full URL in debug mode: + +```bash +cfkv --debug get mykey --local +``` + +## Advanced Usage + +### Custom Local Port + +If your Wrangler dev server is running on a different port, you can currently work around this by modifying your configuration or using environment variables (future enhancement to support this directly). + +### Batch Import/Export with Local + +```bash +# Export from remote +cfkv batch export backup.json + +# Import to local for testing +cfkv batch import backup.json --local + +# Import to remote after verification +cfkv batch import backup.json +``` + +## Best Practices + +1. **Always use `--local` during development** - This prevents accidental modifications to production data +2. **Keep `wrangler dev` running** - The local KV server needs to be active +3. **Test with production data** - Export production data and import it locally for realistic testing +4. **Reset frequently** - Restart `wrangler dev` to get a fresh local instance +5. **Separate credentials** - Use different storage profiles for local and production + +## See Also + +- [Storage Management Guide](./STORAGE_MANAGEMENT.md) - Managing multiple KV configurations +- [Wrangler Documentation](https://developers.cloudflare.com/workers/wrangler/) - Wrangler setup and usage +- [Cloudflare KV Documentation](https://developers.cloudflare.com/workers/runtime-apis/kv/) - KV API reference \ No newline at end of file