mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-05 23:50:08 +00:00
- Replace metadata-based approach with import-based version selection - Add dual endpoint fetching strategy (object_info + v3/object_info) - Implement proxy-based bidirectional data synchronization - Create version-specific type definitions (v1, v1.2, v3) - Add data transformation pipeline between API versions - Update extension service for version-aware invocation This provides type-safe API versioning where extensions choose their API version through imports, ensuring compile-time safety and zero breaking changes for existing extensions.
251 lines
6.4 KiB
TypeScript
251 lines
6.4 KiB
TypeScript
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
|
|
|
|
// Type definitions for different API versions
|
|
export interface ComfyNodeDefV1 {
|
|
name: string
|
|
display_name?: string
|
|
description?: string
|
|
category?: string
|
|
output_node?: boolean
|
|
input?: {
|
|
required?: Record<string, any>
|
|
optional?: Record<string, any>
|
|
}
|
|
output?: string[]
|
|
output_is_list?: boolean[]
|
|
python_module?: string
|
|
}
|
|
|
|
export interface ComfyNodeDefV1_2 extends ComfyNodeDefV1 {
|
|
inputs?: Array<{
|
|
name: string
|
|
type: string
|
|
required: boolean
|
|
options?: any
|
|
spec?: any
|
|
}>
|
|
metadata?: {
|
|
version?: string
|
|
author?: string
|
|
description?: string
|
|
}
|
|
}
|
|
|
|
export interface ComfyNodeDefV3 {
|
|
name: string
|
|
display_name?: string
|
|
description?: string
|
|
category?: string
|
|
output_node?: boolean
|
|
schema: {
|
|
type: 'object'
|
|
properties: Record<string, any>
|
|
required?: string[]
|
|
}
|
|
inputs: Array<{
|
|
name: string
|
|
type: string
|
|
required: boolean
|
|
schema: any
|
|
}>
|
|
outputs: Array<{
|
|
name: string
|
|
type: string
|
|
is_list: boolean
|
|
}>
|
|
python_module?: string
|
|
}
|
|
|
|
// Use current ComfyNodeDef as the canonical format
|
|
export type ComfyNodeDefLatest = ComfyNodeDef
|
|
|
|
/**
|
|
* Transforms node definitions between different API versions
|
|
*/
|
|
export class VersionTransforms {
|
|
/**
|
|
* Transform API responses to all supported versions
|
|
*/
|
|
static transformToAllVersions(currentData: any, v3Data: any = null) {
|
|
// Use current data as canonical since it's our primary source
|
|
const canonical = currentData || {}
|
|
|
|
return {
|
|
canonical,
|
|
v1: this.canonicalToV1(canonical),
|
|
v1_2: this.canonicalToV1_2(canonical),
|
|
v3: v3Data || this.canonicalToV3(canonical)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Transform canonical format to v1 format
|
|
*/
|
|
static canonicalToV1(
|
|
canonical: Record<string, ComfyNodeDefLatest>
|
|
): Record<string, ComfyNodeDefV1> {
|
|
const v1Nodes: Record<string, ComfyNodeDefV1> = {}
|
|
|
|
for (const [nodeName, nodeData] of Object.entries(canonical)) {
|
|
v1Nodes[nodeName] = {
|
|
name: nodeData.name,
|
|
display_name: nodeData.display_name,
|
|
description: nodeData.description,
|
|
category: nodeData.category,
|
|
output_node: nodeData.output_node,
|
|
python_module: nodeData.python_module,
|
|
input: nodeData.input
|
|
? {
|
|
required: nodeData.input.required,
|
|
optional: nodeData.input.optional
|
|
}
|
|
: undefined,
|
|
output: nodeData.output,
|
|
output_is_list: nodeData.output_is_list
|
|
}
|
|
}
|
|
|
|
return v1Nodes
|
|
}
|
|
|
|
/**
|
|
* Transform canonical format to v1.2 format
|
|
*/
|
|
static canonicalToV1_2(
|
|
canonical: Record<string, ComfyNodeDefLatest>
|
|
): Record<string, ComfyNodeDefV1_2> {
|
|
const v1_2Nodes: Record<string, ComfyNodeDefV1_2> = {}
|
|
|
|
for (const [nodeName, nodeData] of Object.entries(canonical)) {
|
|
const v1Node = this.canonicalToV1({ [nodeName]: nodeData })[nodeName]
|
|
|
|
v1_2Nodes[nodeName] = {
|
|
...v1Node,
|
|
inputs: nodeData.input
|
|
? [
|
|
...Object.entries(nodeData.input.required || {}).map(
|
|
([name, spec]) => ({
|
|
name,
|
|
type: this.inferTypeFromSpec(spec),
|
|
required: true,
|
|
spec,
|
|
options: Array.isArray(spec) ? spec : undefined
|
|
})
|
|
),
|
|
...Object.entries(nodeData.input.optional || {}).map(
|
|
([name, spec]) => ({
|
|
name,
|
|
type: this.inferTypeFromSpec(spec),
|
|
required: false,
|
|
spec,
|
|
options: Array.isArray(spec) ? spec : undefined
|
|
})
|
|
)
|
|
]
|
|
: undefined,
|
|
metadata: {
|
|
version: nodeData.api_version,
|
|
author: 'Unknown',
|
|
description: nodeData.description
|
|
}
|
|
}
|
|
}
|
|
|
|
return v1_2Nodes
|
|
}
|
|
|
|
/**
|
|
* Transform canonical format to v3 format
|
|
*/
|
|
static canonicalToV3(
|
|
canonical: Record<string, ComfyNodeDefLatest>
|
|
): Record<string, ComfyNodeDefV3> {
|
|
const v3Nodes: Record<string, ComfyNodeDefV3> = {}
|
|
|
|
for (const [nodeName, nodeData] of Object.entries(canonical)) {
|
|
const requiredInputs = Object.keys(nodeData.input?.required || {})
|
|
const optionalInputs = Object.keys(nodeData.input?.optional || {})
|
|
|
|
v3Nodes[nodeName] = {
|
|
name: nodeData.name,
|
|
display_name: nodeData.display_name,
|
|
description: nodeData.description,
|
|
category: nodeData.category,
|
|
output_node: nodeData.output_node,
|
|
python_module: nodeData.python_module,
|
|
schema: {
|
|
type: 'object',
|
|
properties: {
|
|
...(nodeData.input?.required || {}),
|
|
...(nodeData.input?.optional || {})
|
|
},
|
|
required: requiredInputs
|
|
},
|
|
inputs: [
|
|
...requiredInputs.map((name) => ({
|
|
name,
|
|
type: this.inferTypeFromSpec(nodeData.input!.required![name]),
|
|
required: true,
|
|
schema: nodeData.input!.required![name]
|
|
})),
|
|
...optionalInputs.map((name) => ({
|
|
name,
|
|
type: this.inferTypeFromSpec(nodeData.input!.optional![name]),
|
|
required: false,
|
|
schema: nodeData.input!.optional![name]
|
|
}))
|
|
],
|
|
outputs:
|
|
nodeData.output?.map((type, index) => ({
|
|
name: `output_${index}`,
|
|
type,
|
|
is_list: nodeData.output_is_list?.[index] || false
|
|
})) || []
|
|
}
|
|
}
|
|
|
|
return v3Nodes
|
|
}
|
|
|
|
/**
|
|
* Infer type from input specification
|
|
*/
|
|
private static inferTypeFromSpec(spec: any): string {
|
|
if (Array.isArray(spec)) {
|
|
return 'combo'
|
|
}
|
|
|
|
if (typeof spec === 'object' && spec !== null) {
|
|
if (spec.type) {
|
|
return spec.type
|
|
}
|
|
if (spec[0] === 'INT') {
|
|
return 'int'
|
|
}
|
|
if (spec[0] === 'FLOAT') {
|
|
return 'float'
|
|
}
|
|
if (spec[0] === 'STRING') {
|
|
return 'string'
|
|
}
|
|
if (spec[0] === 'BOOLEAN') {
|
|
return 'boolean'
|
|
}
|
|
}
|
|
|
|
return 'unknown'
|
|
}
|
|
|
|
/**
|
|
* Merge current and v3 data to create canonical format
|
|
*/
|
|
private static createCanonicalFormat(
|
|
currentData: any,
|
|
v3Data: any
|
|
): Record<string, ComfyNodeDefLatest> {
|
|
// For now, use current data as canonical
|
|
// In the future, we might merge v3 data for enhanced information
|
|
return currentData || {}
|
|
}
|
|
}
|