Files
mxivideo/src/hooks/useCloudflareKV.ts
root a5381e5305 feat: implement Cloudflare KV utility class and React hooks
- 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
2025-07-10 20:51:09 +08:00

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 }