Skip to content

Commit 6a95948

Browse files
christian-byrneviva-jinyi
authored andcommitted
[feat] Implement getNodeByComfyNodeName API integration (#4343)
1 parent fd4d565 commit 6a95948

File tree

4 files changed

+137
-10
lines changed

4 files changed

+137
-10
lines changed

src/composables/nodePack/useWorkflowPacks.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const CORE_NODES_PACK_NAME = 'comfy-core'
2626
export const useWorkflowPacks = (options: UseNodePacksOptions = {}) => {
2727
const nodeDefStore = useNodeDefStore()
2828
const systemStatsStore = useSystemStatsStore()
29-
const { search } = useComfyRegistryStore()
29+
const { inferPackFromNodeName } = useComfyRegistryStore()
3030

3131
const workflowPacks = ref<WorkflowPack[]>([])
3232

@@ -69,18 +69,19 @@ export const useWorkflowPacks = (options: UseNodePacksOptions = {}) => {
6969
}
7070
}
7171

72-
// Search the registry for non-core nodes
73-
const searchResult = await search.call({
74-
comfy_node_search: nodeName,
75-
limit: 1
76-
})
77-
if (searchResult?.nodes?.length) {
78-
const pack = searchResult.nodes[0]
72+
// Query the registry to find which pack provides this node
73+
const pack = await inferPackFromNodeName.call(nodeName)
74+
75+
if (pack) {
7976
return {
8077
id: pack.id,
8178
version: pack.latest_version?.version ?? 'nightly'
8279
}
8380
}
81+
82+
// No pack found - this node doesn't exist in the registry or couldn't be
83+
// extracted from the parent node pack successfully
84+
return undefined
8485
}
8586

8687
/**

src/services/comfyRegistryService.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,47 @@ export const useComfyRegistryService = () => {
318318
)
319319
}
320320

321+
/**
322+
* Get the node pack that contains a specific ComfyUI node by its name.
323+
* This method queries the registry to find which pack provides the given node.
324+
*
325+
* When multiple packs contain a node with the same name, the API returns the best match based on:
326+
* 1. Preemption match - If the node name matches any in the pack's preempted_comfy_node_names array
327+
* 2. Search ranking - Lower search_ranking values are preferred
328+
* 3. Total installs - Higher installation counts are preferred as a tiebreaker
329+
*
330+
* @param nodeName - The name of the ComfyUI node (e.g., 'KSampler', 'CLIPTextEncode')
331+
* @param signal - Optional AbortSignal for request cancellation
332+
* @returns The node pack containing the specified node, or null if not found or on error
333+
*
334+
* @example
335+
* ```typescript
336+
* const pack = await inferPackFromNodeName('KSampler')
337+
* if (pack) {
338+
* console.log(`Node found in pack: ${pack.name}`)
339+
* }
340+
* ```
341+
*/
342+
const inferPackFromNodeName = async (
343+
nodeName: operations['getNodeByComfyNodeName']['parameters']['path']['comfyNodeName'],
344+
signal?: AbortSignal
345+
) => {
346+
const endpoint = `/comfy-nodes/${nodeName}/node`
347+
const errorContext = 'Failed to infer pack from comfy node name'
348+
const routeSpecificErrors = {
349+
404: `Comfy node not found: The node with name ${nodeName} does not exist in the registry`
350+
}
351+
352+
return executeApiRequest(
353+
() =>
354+
registryApiClient.get<components['schemas']['Node']>(endpoint, {
355+
signal
356+
}),
357+
errorContext,
358+
routeSpecificErrors
359+
)
360+
}
361+
321362
return {
322363
isLoading,
323364
error,
@@ -330,6 +371,7 @@ export const useComfyRegistryService = () => {
330371
getPublisherById,
331372
listPacksForPublisher,
332373
getNodeDefs,
333-
postPackReview
374+
postPackReview,
375+
inferPackFromNodeName
334376
}
335377
}

src/stores/comfyRegistryStore.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,25 @@ export const useComfyRegistryStore = defineStore('comfyRegistry', () => {
104104
ListPacksResult
105105
>(registryService.search, { maxSize: PACK_LIST_CACHE_SIZE })
106106

107+
/**
108+
* Get the node pack that contains a specific ComfyUI node by its name.
109+
* Results are cached to avoid redundant API calls.
110+
*
111+
* @see {@link useComfyRegistryService.inferPackFromNodeName} for details on the ranking algorithm
112+
*/
113+
const inferPackFromNodeName = useCachedRequest<
114+
operations['getNodeByComfyNodeName']['parameters']['path']['comfyNodeName'],
115+
NodePack
116+
>(registryService.inferPackFromNodeName, { maxSize: PACK_BY_ID_CACHE_SIZE })
117+
107118
/**
108119
* Clear all cached data
109120
*/
110121
const clearCache = () => {
111122
getNodeDefs.clear()
112123
listAllPacks.clear()
113124
getPackById.clear()
125+
inferPackFromNodeName.clear()
114126
}
115127

116128
/**
@@ -120,6 +132,7 @@ export const useComfyRegistryStore = defineStore('comfyRegistry', () => {
120132
getNodeDefs.cancel()
121133
listAllPacks.cancel()
122134
getPackById.cancel()
135+
inferPackFromNodeName.cancel()
123136
getPacksByIdController?.abort()
124137
}
125138

@@ -132,6 +145,7 @@ export const useComfyRegistryStore = defineStore('comfyRegistry', () => {
132145
},
133146
getNodeDefs,
134147
search,
148+
inferPackFromNodeName,
135149

136150
clearCache,
137151
cancelRequests,

tests-ui/tests/store/comfyRegistryStore.test.ts

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ describe('useComfyRegistryStore', () => {
7272
error: ReturnType<typeof ref<string | null>>
7373
listAllPacks: ReturnType<typeof vi.fn>
7474
getPackById: ReturnType<typeof vi.fn>
75+
inferPackFromNodeName: ReturnType<typeof vi.fn>
76+
search: ReturnType<typeof vi.fn>
77+
getPackVersions: ReturnType<typeof vi.fn>
78+
getPackByVersion: ReturnType<typeof vi.fn>
79+
getPublisherById: ReturnType<typeof vi.fn>
80+
listPacksForPublisher: ReturnType<typeof vi.fn>
81+
getNodeDefs: ReturnType<typeof vi.fn>
82+
postPackReview: ReturnType<typeof vi.fn>
7583
}
7684

7785
beforeEach(() => {
@@ -106,7 +114,15 @@ describe('useComfyRegistryStore', () => {
106114
// Otherwise return paginated results
107115
return Promise.resolve(mockListResult)
108116
}),
109-
getPackById: vi.fn().mockResolvedValue(mockNodePack)
117+
getPackById: vi.fn().mockResolvedValue(mockNodePack),
118+
inferPackFromNodeName: vi.fn().mockResolvedValue(mockNodePack),
119+
search: vi.fn().mockResolvedValue(mockListResult),
120+
getPackVersions: vi.fn().mockResolvedValue([]),
121+
getPackByVersion: vi.fn().mockResolvedValue({}),
122+
getPublisherById: vi.fn().mockResolvedValue({}),
123+
listPacksForPublisher: vi.fn().mockResolvedValue([]),
124+
getNodeDefs: vi.fn().mockResolvedValue({}),
125+
postPackReview: vi.fn().mockResolvedValue({})
110126
}
111127

112128
vi.mocked(useComfyRegistryService).mockReturnValue(
@@ -186,4 +202,58 @@ describe('useComfyRegistryStore', () => {
186202
expect.any(Object) // abort signal
187203
)
188204
})
205+
206+
describe('inferPackFromNodeName', () => {
207+
it('should fetch a pack by comfy node name', async () => {
208+
const store = useComfyRegistryStore()
209+
const nodeName = 'KSampler'
210+
211+
const result = await store.inferPackFromNodeName.call(nodeName)
212+
213+
expect(result).toEqual(mockNodePack)
214+
expect(mockRegistryService.inferPackFromNodeName).toHaveBeenCalledWith(
215+
nodeName,
216+
expect.any(Object) // abort signal
217+
)
218+
})
219+
220+
it('should cache results', async () => {
221+
const store = useComfyRegistryStore()
222+
const nodeName = 'KSampler'
223+
224+
// First call
225+
const result1 = await store.inferPackFromNodeName.call(nodeName)
226+
expect(mockRegistryService.inferPackFromNodeName).toHaveBeenCalledTimes(1)
227+
228+
// Second call - should use cache
229+
const result2 = await store.inferPackFromNodeName.call(nodeName)
230+
expect(mockRegistryService.inferPackFromNodeName).toHaveBeenCalledTimes(1)
231+
expect(result2).toEqual(result1)
232+
})
233+
234+
it('should handle null results when node is not found', async () => {
235+
mockRegistryService.inferPackFromNodeName.mockResolvedValueOnce(null)
236+
237+
const store = useComfyRegistryStore()
238+
const result = await store.inferPackFromNodeName.call('NonExistentNode')
239+
240+
expect(result).toBeNull()
241+
})
242+
243+
it('should clear cache when clearCache is called', async () => {
244+
const store = useComfyRegistryStore()
245+
const nodeName = 'KSampler'
246+
247+
// First call to populate cache
248+
await store.inferPackFromNodeName.call(nodeName)
249+
expect(mockRegistryService.inferPackFromNodeName).toHaveBeenCalledTimes(1)
250+
251+
// Clear cache
252+
store.clearCache()
253+
254+
// Call again - should hit the service again
255+
await store.inferPackFromNodeName.call(nodeName)
256+
expect(mockRegistryService.inferPackFromNodeName).toHaveBeenCalledTimes(2)
257+
})
258+
})
189259
})

0 commit comments

Comments
 (0)