- Add CloudflareKVClient class with full CRUD operations - Support for batch operations, metadata, and key listing - Implement useCloudflareKV hook for React components - Add useKVValue hook for auto-loading specific keys - Include comprehensive error handling and loading states - Create demo component showing all functionality - Add detailed documentation and usage examples - Support for JSON parsing and custom configurations - Based on jm_video_ui.md specifications
292 lines
7.5 KiB
TypeScript
292 lines
7.5 KiB
TypeScript
/**
|
|
* 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<T = any> {
|
|
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<KVOperationState>({
|
|
data: null,
|
|
loading: false,
|
|
error: null
|
|
})
|
|
|
|
// State for put operations
|
|
const [putState, setPutState] = useState<KVOperationState>({
|
|
data: null,
|
|
loading: false,
|
|
error: null
|
|
})
|
|
|
|
// State for delete operations
|
|
const [deleteState, setDeleteState] = useState<KVOperationState>({
|
|
data: null,
|
|
loading: false,
|
|
error: null
|
|
})
|
|
|
|
// State for list operations
|
|
const [listState, setListState] = useState<KVOperationState>({
|
|
data: null,
|
|
loading: false,
|
|
error: null
|
|
})
|
|
|
|
/**
|
|
* Get a value from KV store
|
|
*/
|
|
const get = useCallback(async <T = any>(key: string, parseJSON: boolean = true): Promise<T | string | null> => {
|
|
setGetState({ data: null, loading: true, error: null })
|
|
|
|
try {
|
|
const result = await client.get<T>(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<string, any>) => {
|
|
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<boolean> => {
|
|
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<string, any> }>) => {
|
|
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<T = any>(key: string, parseJSON: boolean = true, autoLoad: boolean = true) {
|
|
const [value, setValue] = useState<T | string | null>(null)
|
|
const [loading, setLoading] = useState(false)
|
|
const [error, setError] = useState<string | null>(null)
|
|
const [lastUpdated, setLastUpdated] = useState<Date | null>(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<T>(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<string, any>) => {
|
|
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 }
|