put search provider global state in store

This commit is contained in:
bymyself
2025-06-15 20:38:05 -07:00
parent 6eed618a94
commit 85f0aca045
3 changed files with 50 additions and 34 deletions

View File

@@ -1,33 +1,19 @@
import { ref } from 'vue'
import { useAlgoliaSearchProvider } from '@/services/providers/algoliaSearchProvider' import { useAlgoliaSearchProvider } from '@/services/providers/algoliaSearchProvider'
import { useComfyRegistrySearchProvider } from '@/services/providers/registrySearchProvider' import { useComfyRegistrySearchProvider } from '@/services/providers/registrySearchProvider'
import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'
import type { SearchNodePacksParams } from '@/types/algoliaTypes' import type { SearchNodePacksParams } from '@/types/algoliaTypes'
import type { components } from '@/types/comfyRegistryTypes' import type { components } from '@/types/comfyRegistryTypes'
import type { import type {
NodePackSearchProvider, NodePackSearchProvider,
ProviderState,
SearchPacksResult SearchPacksResult
} from '@/types/searchServiceTypes' } from '@/types/searchServiceTypes'
type RegistryNodePack = components['schemas']['Node'] type RegistryNodePack = components['schemas']['Node']
interface ProviderState {
provider: NodePackSearchProvider
name: string
isHealthy: boolean
lastError?: Error
lastAttempt?: Date
consecutiveFailures: number
}
const CIRCUIT_BREAKER_THRESHOLD = 3 // Number of failures before circuit opens const CIRCUIT_BREAKER_THRESHOLD = 3 // Number of failures before circuit opens
const CIRCUIT_BREAKER_TIMEOUT = 60000 // 1 minute before retry const CIRCUIT_BREAKER_TIMEOUT = 60000 // 1 minute before retry
// Global state shared across all uses of the gateway
const providers: ProviderState[] = []
const activeProviderIndex = ref(0)
let isInitialized = false
/** /**
* API Gateway for registry search providers with circuit breaker pattern. * API Gateway for registry search providers with circuit breaker pattern.
* Acts as a single entry point that routes search requests to appropriate providers * Acts as a single entry point that routes search requests to appropriate providers
@@ -39,11 +25,13 @@ let isInitialized = false
* - Automatic failover: Cascades through providers on failure * - Automatic failover: Cascades through providers on failure
*/ */
export const useRegistrySearchGateway = (): NodePackSearchProvider => { export const useRegistrySearchGateway = (): NodePackSearchProvider => {
const store = useComfyRegistryStore()
// Initialize providers only once // Initialize providers only once
if (!isInitialized) { if (!store.isSearchGatewayInitialized) {
// Initialize providers in priority order // Initialize providers in priority order
try { try {
providers.push({ store.searchProviders.push({
provider: useAlgoliaSearchProvider(), provider: useAlgoliaSearchProvider(),
name: 'Algolia', name: 'Algolia',
isHealthy: true, isHealthy: true,
@@ -53,14 +41,14 @@ export const useRegistrySearchGateway = (): NodePackSearchProvider => {
console.warn('Failed to initialize Algolia provider:', error) console.warn('Failed to initialize Algolia provider:', error)
} }
providers.push({ store.searchProviders.push({
provider: useComfyRegistrySearchProvider(), provider: useComfyRegistrySearchProvider(),
name: 'ComfyRegistry', name: 'ComfyRegistry',
isHealthy: true, isHealthy: true,
consecutiveFailures: 0 consecutiveFailures: 0
}) })
isInitialized = true store.isSearchGatewayInitialized = true
} }
// TODO: Add an "offline" provider that operates on a local cache of the registry. // TODO: Add an "offline" provider that operates on a local cache of the registry.
@@ -118,16 +106,17 @@ export const useRegistrySearchGateway = (): NodePackSearchProvider => {
*/ */
const getActiveProvider = (): NodePackSearchProvider => { const getActiveProvider = (): NodePackSearchProvider => {
// First, try to use the current active provider if it's healthy // First, try to use the current active provider if it's healthy
const currentProvider = providers[activeProviderIndex.value] const currentProvider =
store.searchProviders[store.activeSearchProviderIndex]
if (currentProvider && isCircuitClosed(currentProvider)) { if (currentProvider && isCircuitClosed(currentProvider)) {
return currentProvider.provider return currentProvider.provider
} }
// Otherwise, find the first healthy provider // Otherwise, find the first healthy provider
for (let i = 0; i < providers.length; i++) { for (let i = 0; i < store.searchProviders.length; i++) {
const providerState = providers[i] const providerState = store.searchProviders[i]
if (isCircuitClosed(providerState)) { if (isCircuitClosed(providerState)) {
activeProviderIndex.value = i store.activeSearchProviderIndex = i
return providerState.provider return providerState.provider
} }
} }
@@ -140,8 +129,8 @@ export const useRegistrySearchGateway = (): NodePackSearchProvider => {
* Move to the next provider if available. * Move to the next provider if available.
*/ */
const updateActiveProviderOnFailure = () => { const updateActiveProviderOnFailure = () => {
if (activeProviderIndex.value < providers.length - 1) { if (store.activeSearchProviderIndex < store.searchProviders.length - 1) {
activeProviderIndex.value++ store.activeSearchProviderIndex++
} }
} }
@@ -155,17 +144,23 @@ export const useRegistrySearchGateway = (): NodePackSearchProvider => {
let lastError: Error | null = null let lastError: Error | null = null
// Start with the current active provider // Start with the current active provider
for (let attempts = 0; attempts < providers.length; attempts++) { for (
let attempts = 0;
attempts < store.searchProviders.length;
attempts++
) {
try { try {
const provider = getActiveProvider() const provider = getActiveProvider()
const providerState = providers[activeProviderIndex.value] const providerState =
store.searchProviders[store.activeSearchProviderIndex]
const result = await provider.searchPacks(query, params) const result = await provider.searchPacks(query, params)
recordSuccess(providerState) recordSuccess(providerState)
return result return result
} catch (error) { } catch (error) {
lastError = error as Error lastError = error as Error
const providerState = providers[activeProviderIndex.value] const providerState =
store.searchProviders[store.activeSearchProviderIndex]
recordFailure(providerState, lastError) recordFailure(providerState, lastError)
console.warn( console.warn(
`${providerState.name} search provider failed (${providerState.consecutiveFailures} failures):`, `${providerState.name} search provider failed (${providerState.consecutiveFailures} failures):`,
@@ -187,7 +182,7 @@ export const useRegistrySearchGateway = (): NodePackSearchProvider => {
* Clear the search cache for all providers that implement it. * Clear the search cache for all providers that implement it.
*/ */
const clearSearchCache = () => { const clearSearchCache = () => {
for (const providerState of providers) { for (const providerState of store.searchProviders) {
try { try {
providerState.provider.clearSearchCache() providerState.provider.clearSearchCache()
} catch (error) { } catch (error) {
@@ -229,11 +224,8 @@ export const useRegistrySearchGateway = (): NodePackSearchProvider => {
/** /**
* Get the filterable fields for the active provider. * Get the filterable fields for the active provider.
* This is now a computed property that will react to provider changes.
*/ */
const getFilterableFields = () => { const getFilterableFields = () => {
// Access activeProviderIndex.value to establish reactivity
void activeProviderIndex.value
const provider = getActiveProvider() const provider = getActiveProvider()
return provider.getFilterableFields() return provider.getFilterableFields()
} }

View File

@@ -1,10 +1,12 @@
import QuickLRU from '@alloc/quick-lru' import QuickLRU from '@alloc/quick-lru'
import { partition } from 'lodash' import { partition } from 'lodash'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref } from 'vue'
import { useCachedRequest } from '@/composables/useCachedRequest' import { useCachedRequest } from '@/composables/useCachedRequest'
import { useComfyRegistryService } from '@/services/comfyRegistryService' import { useComfyRegistryService } from '@/services/comfyRegistryService'
import type { components, operations } from '@/types/comfyRegistryTypes' import type { components, operations } from '@/types/comfyRegistryTypes'
import type { ProviderState } from '@/types/searchServiceTypes'
const PACK_LIST_CACHE_SIZE = 20 const PACK_LIST_CACHE_SIZE = 20
const PACK_BY_ID_CACHE_SIZE = 64 const PACK_BY_ID_CACHE_SIZE = 64
@@ -34,6 +36,11 @@ export const useComfyRegistryStore = defineStore('comfyRegistry', () => {
maxSize: PACK_BY_ID_CACHE_SIZE maxSize: PACK_BY_ID_CACHE_SIZE
}) })
// Search gateway state
const searchProviders = ref<ProviderState[]>([])
const activeSearchProviderIndex = ref(0)
const isSearchGatewayInitialized = ref(false)
/** /**
* Get a list of all node packs from the registry * Get a list of all node packs from the registry
*/ */
@@ -137,6 +144,11 @@ export const useComfyRegistryStore = defineStore('comfyRegistry', () => {
cancelRequests, cancelRequests,
isLoading: registryService.isLoading, isLoading: registryService.isLoading,
error: registryService.error error: registryService.error,
// Search gateway state
searchProviders,
activeSearchProviderIndex,
isSearchGatewayInitialized
} }
}) })

View File

@@ -70,3 +70,15 @@ export interface NodePackSearchProvider {
*/ */
getFilterableFields(): SearchFilter[] getFilterableFields(): SearchFilter[]
} }
/**
* State of a search provider
*/
export interface ProviderState {
provider: NodePackSearchProvider
name: string
isHealthy: boolean
lastError?: Error
lastAttempt?: Date
consecutiveFailures: number
}