mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
Extract shared isResultItemLike guard and flattenNodeExecutionOutput/ flattenTaskOutputs into resultItemParsing.ts, replacing three separate implementations that disagreed on validation strictness: - flattenNodeOutput.ts: was strict (required filename+subfolder) - jobOutputCache.ts: was permissive (accepted partial objects) - queueStore.ts: had no validation (cast blindly to ResultItem[]) The shared guard requires filename and subfolder as strings (strict domain boundary) while the wire schema (zOutputs) remains permissive via .passthrough() to accept arbitrary custom node output keys.
75 lines
2.1 KiB
TypeScript
75 lines
2.1 KiB
TypeScript
import type {
|
|
NodeExecutionOutput,
|
|
ResultItem,
|
|
TaskOutput
|
|
} from '@/schemas/apiSchema'
|
|
import { resultItemType } from '@/schemas/apiSchema'
|
|
import { ResultItemImpl } from '@/stores/queueStore'
|
|
|
|
const EXCLUDED_KEYS = new Set(['animated'])
|
|
|
|
/**
|
|
* Strict domain guard for result items.
|
|
*
|
|
* The wire-format schema (zOutputs) is intentionally permissive via
|
|
* `.passthrough()` to accept arbitrary keys from custom nodes. This guard
|
|
* is strict: it requires the fields needed to construct a valid UI model
|
|
* (ResultItemImpl) that can build preview URLs.
|
|
*/
|
|
export function isResultItemLike(item: unknown): item is ResultItem {
|
|
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
return false
|
|
}
|
|
|
|
const candidate = item as Record<string, unknown>
|
|
|
|
if (typeof candidate.filename !== 'string') {
|
|
return false
|
|
}
|
|
|
|
if (typeof candidate.subfolder !== 'string') {
|
|
return false
|
|
}
|
|
|
|
if (
|
|
candidate.type !== undefined &&
|
|
!resultItemType.safeParse(candidate.type).success
|
|
) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* Flattens a single node's execution output into ResultItemImpl instances.
|
|
*
|
|
* Iterates all output keys dynamically (to support custom node keys like
|
|
* `a_images`, `b_images`, `gifs`, etc.) and validates each item with the
|
|
* strict domain guard before constructing ResultItemImpl.
|
|
*/
|
|
export function flattenNodeExecutionOutput(
|
|
nodeId: string | number,
|
|
nodeOutput: NodeExecutionOutput
|
|
): ResultItemImpl[] {
|
|
return Object.entries(nodeOutput)
|
|
.filter(([key, value]) => !EXCLUDED_KEYS.has(key) && Array.isArray(value))
|
|
.flatMap(([mediaType, items]) =>
|
|
(items as unknown[])
|
|
.filter(isResultItemLike)
|
|
.map((item) => new ResultItemImpl({ ...item, mediaType, nodeId }))
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Flattens all nodes' outputs from a TaskOutput into ResultItemImpl instances.
|
|
*/
|
|
export function flattenTaskOutputs(
|
|
outputs?: TaskOutput
|
|
): ReadonlyArray<ResultItemImpl> {
|
|
if (!outputs) return []
|
|
return Object.entries(outputs).flatMap(([nodeId, nodeOutput]) =>
|
|
flattenNodeExecutionOutput(nodeId, nodeOutput)
|
|
)
|
|
}
|