/** * React Hook for Cloudflare KV Operations * * This hook provides a convenient way to interact with Cloudflare KV * from React components with loading states and error handling. */ import { useState, useCallback, useEffect } from 'react' import { cloudflareKV, CloudflareKVClient } from '../utils/cloudflareKV' interface UseCloudflareKVOptions { client?: CloudflareKVClient } interface KVOperationState { data: T | null loading: boolean error: string | null } export function useCloudflareKV(options: UseCloudflareKVOptions = {}) { const client = options.client || cloudflareKV // State for get operations const [getState, setGetState] = useState({ data: null, loading: false, error: null }) // State for put operations const [putState, setPutState] = useState({ data: null, loading: false, error: null }) // State for delete operations const [deleteState, setDeleteState] = useState({ data: null, loading: false, error: null }) // State for list operations const [listState, setListState] = useState({ data: null, loading: false, error: null }) /** * Get a value from KV store */ const get = useCallback(async (key: string, parseJSON: boolean = true): Promise => { setGetState({ data: null, loading: true, error: null }) try { const result = await client.get(key, parseJSON) setGetState({ data: result, loading: false, error: null }) return result } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error' setGetState({ data: null, loading: false, error: errorMessage }) throw error } }, [client]) /** * Put a value to KV store */ const put = useCallback(async (key: string, value: any, metadata?: Record) => { setPutState({ data: null, loading: true, error: null }) try { const result = await client.put(key, value, metadata) setPutState({ data: result, loading: false, error: null }) return result } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error' setPutState({ data: null, loading: false, error: errorMessage }) throw error } }, [client]) /** * Delete a key from KV store */ const deleteKey = useCallback(async (key: string) => { setDeleteState({ data: null, loading: true, error: null }) try { const result = await client.delete(key) setDeleteState({ data: result, loading: false, error: null }) return result } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error' setDeleteState({ data: null, loading: false, error: errorMessage }) throw error } }, [client]) /** * List keys from KV store */ const listKeys = useCallback(async (prefix?: string, limit: number = 1000, cursor?: string) => { setListState({ data: null, loading: true, error: null }) try { const result = await client.listKeys(prefix, limit, cursor) setListState({ data: result, loading: false, error: null }) return result } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error' setListState({ data: null, loading: false, error: errorMessage }) throw error } }, [client]) /** * Check if a key exists */ const exists = useCallback(async (key: string): Promise => { try { return await client.exists(key) } catch (error) { console.error('Error checking key existence:', error) return false } }, [client]) /** * Put multiple values in batch */ const putBatch = useCallback(async (entries: Array<{ key: string; value: any; metadata?: Record }>) => { setPutState({ data: null, loading: true, error: null }) try { const result = await client.putBatch(entries) setPutState({ data: result, loading: false, error: null }) return result } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error' setPutState({ data: null, loading: false, error: errorMessage }) throw error } }, [client]) /** * Clear all operation states */ const clearStates = useCallback(() => { setGetState({ data: null, loading: false, error: null }) setPutState({ data: null, loading: false, error: null }) setDeleteState({ data: null, loading: false, error: null }) setListState({ data: null, loading: false, error: null }) }, []) return { // Operations get, put, delete: deleteKey, listKeys, exists, putBatch, clearStates, // States getState, putState, deleteState, listState, // Convenience getters isLoading: getState.loading || putState.loading || deleteState.loading || listState.loading, hasError: !!(getState.error || putState.error || deleteState.error || listState.error), errors: { get: getState.error, put: putState.error, delete: deleteState.error, list: listState.error } } } /** * Hook for managing a specific KV key with automatic loading and caching */ export function useKVValue(key: string, parseJSON: boolean = true, autoLoad: boolean = true) { const [value, setValue] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [lastUpdated, setLastUpdated] = useState(null) const kv = useCloudflareKV() /** * Load the value from KV */ const load = useCallback(async () => { if (!key) return setLoading(true) setError(null) try { const result = await kv.get(key, parseJSON) setValue(result) setLastUpdated(new Date()) } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to load value' setError(errorMessage) } finally { setLoading(false) } }, [key, parseJSON, kv]) /** * Save a new value to KV */ const save = useCallback(async (newValue: T | string, metadata?: Record) => { if (!key) return setLoading(true) setError(null) try { await kv.put(key, newValue, metadata) setValue(newValue) setLastUpdated(new Date()) } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to save value' setError(errorMessage) throw err } finally { setLoading(false) } }, [key, kv]) /** * Delete the value from KV */ const remove = useCallback(async () => { if (!key) return setLoading(true) setError(null) try { await kv.delete(key) setValue(null) setLastUpdated(new Date()) } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to delete value' setError(errorMessage) throw err } finally { setLoading(false) } }, [key, kv]) /** * Refresh the value from KV */ const refresh = useCallback(() => { return load() }, [load]) // Auto-load on mount if enabled useEffect(() => { if (autoLoad && key) { load() } }, [autoLoad, key, load]) return { value, loading, error, lastUpdated, load, save, remove, refresh, exists: value !== null } } // Export types export type { KVOperationState }