mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 03:01:54 +00:00
fix: improve type safety in type definitions (#7337)
## Summary
- Replace `any` types with proper TypeScript types in core type
definitions
- Add generics to `SettingParams`, `setting.get<T>()`, and
`setting.set<T>()` for type-safe setting access
- Add `NodeExecutionOutput` interface for `onExecuted` callback
- Change function parameters from `any` to `unknown` where appropriate
## Type Research
Performed GitHub code search across custom node repositories to
understand actual usage patterns:
**`onExecuted` output properties** (used in rgthree-comfy,
ComfyUI-KJNodes, ComfyUI-ExLlama-Nodes, comfy_mtb, etc.):
- `output.text` - string or string array for text display nodes
- `output.images`, `output.audio`, `output.video` - media outputs
- `output.ui.items` - complex debug/preview data with `input`, `text`,
`b64_images`
**`extensionManager.setting.get/set`** (used in ComfyUI-Crystools,
ComfyUI-Copilot, etc.):
- Returns various types (boolean, number, string, objects)
- Now uses generics: `setting.get<boolean>('MyExt.Setting')`
**`ComfyExtension` custom properties** (used in rgthree-comfy,
ComfyUI-Manager):
- `aboutPageBadges`, `commands`, custom methods
- Kept as `any` index signature since extensions add arbitrary
properties
## Changes
| File | Change |
|------|--------|
| `extensionTypes.ts` | Generic `setting.get<T>()` and
`setting.set<T>()`, typed Toast options |
| `litegraph-augmentation.d.ts` | `onExecuted(output:
NodeExecutionOutput)` |
| `metadataTypes.ts` | GLTF index signatures `any` → `unknown` |
| `apiSchema.ts` | New `NodeExecutionOutput` interface |
| `settings/types.ts` | `SettingOnChange<T>`, `SettingMigration<T>`,
`SettingParams<TValue>` |
| `nodeDefSchema.ts` | `validateComfyNodeDef(data: unknown)` |
| `workflowSchema.ts` | `isSubgraphDefinition(obj: unknown)` |
| `telemetry/types.ts` | `checkForCompletedTopup(events: AuditLog[])` |
## Test plan
- [x] `pnpm typecheck` passes
- [x] `pnpm test:unit` passes (3732 tests)
- [x] `pnpm lint` passes
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7337-fix-improve-type-safety-in-type-definitions-2c66d73d365081bdbc30e916cac607d6)
by [Unito](https://www.unito.io)
This commit is contained in:
committed by
GitHub
parent
3c8b7b015c
commit
b9f75b6cc8
@@ -22,10 +22,10 @@ export function useToolManager(
|
|||||||
const coordinateTransform = useCoordinateTransform()
|
const coordinateTransform = useCoordinateTransform()
|
||||||
|
|
||||||
const brushDrawing = useBrushDrawing({
|
const brushDrawing = useBrushDrawing({
|
||||||
useDominantAxis: app.extensionManager.setting.get(
|
useDominantAxis: app.extensionManager.setting.get<boolean>(
|
||||||
'Comfy.MaskEditor.UseDominantAxis'
|
'Comfy.MaskEditor.UseDominantAxis'
|
||||||
),
|
),
|
||||||
brushAdjustmentSpeed: app.extensionManager.setting.get(
|
brushAdjustmentSpeed: app.extensionManager.setting.get<number>(
|
||||||
'Comfy.MaskEditor.BrushAdjustmentSpeed'
|
'Comfy.MaskEditor.BrushAdjustmentSpeed'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -74,7 +74,8 @@ useExtensionService().registerExtension({
|
|||||||
this.widgets?.filter((w) => w.name === 'preview') ?? []
|
this.widgets?.filter((w) => w.name === 'preview') ?? []
|
||||||
|
|
||||||
for (const previewWidget of previewWidgets) {
|
for (const previewWidget of previewWidgets) {
|
||||||
previewWidget.value = message.text[0]
|
const text = message.text ?? ''
|
||||||
|
previewWidget.value = Array.isArray(text) ? (text[0] ?? '') : text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -602,10 +602,12 @@ export const CORE_SETTINGS: SettingParams[] = [
|
|||||||
defaultValue: [] as Keybinding[],
|
defaultValue: [] as Keybinding[],
|
||||||
versionAdded: '1.3.7',
|
versionAdded: '1.3.7',
|
||||||
versionModified: '1.7.3',
|
versionModified: '1.7.3',
|
||||||
migrateDeprecatedValue: (value: any[]) => {
|
migrateDeprecatedValue: (
|
||||||
|
value: (Keybinding & { targetSelector?: string })[]
|
||||||
|
) => {
|
||||||
return value.map((keybinding) => {
|
return value.map((keybinding) => {
|
||||||
if (keybinding['targetSelector'] === '#graph-canvas') {
|
if (keybinding.targetSelector === '#graph-canvas') {
|
||||||
keybinding['targetElementId'] = 'graph-canvas-container'
|
keybinding.targetElementId = 'graph-canvas-container'
|
||||||
}
|
}
|
||||||
return keybinding
|
return keybinding
|
||||||
})
|
})
|
||||||
@@ -780,7 +782,7 @@ export const CORE_SETTINGS: SettingParams[] = [
|
|||||||
tooltip: 'Server config values used for frontend display only',
|
tooltip: 'Server config values used for frontend display only',
|
||||||
type: 'hidden',
|
type: 'hidden',
|
||||||
// Mapping from server config id to value.
|
// Mapping from server config id to value.
|
||||||
defaultValue: {} as Record<string, any>,
|
defaultValue: {} as Record<string, unknown>,
|
||||||
versionAdded: '1.4.8'
|
versionAdded: '1.4.8'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -117,9 +117,10 @@ export const useSettingStore = defineStore('setting', () => {
|
|||||||
return versionedDefault
|
return versionedDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
return typeof param.defaultValue === 'function'
|
const defaultValue = param.defaultValue
|
||||||
? param.defaultValue()
|
return typeof defaultValue === 'function'
|
||||||
: param.defaultValue
|
? (defaultValue as () => Settings[K])()
|
||||||
|
: defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVersionedDefaultValue<
|
function getVersionedDefaultValue<
|
||||||
|
|||||||
@@ -16,21 +16,21 @@ type SettingInputType =
|
|||||||
|
|
||||||
type SettingCustomRenderer = (
|
type SettingCustomRenderer = (
|
||||||
name: string,
|
name: string,
|
||||||
setter: (v: any) => void,
|
setter: (v: unknown) => void,
|
||||||
value: any,
|
value: unknown,
|
||||||
attrs: any
|
attrs?: Record<string, unknown>
|
||||||
) => HTMLElement
|
) => HTMLElement
|
||||||
|
|
||||||
export interface SettingOption {
|
export interface SettingOption {
|
||||||
text: string
|
text: string
|
||||||
value?: any
|
value?: string | number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SettingParams<TValue = unknown> extends FormItem {
|
export interface SettingParams<TValue = any> extends FormItem {
|
||||||
id: keyof Settings
|
id: keyof Settings
|
||||||
defaultValue: any | (() => any)
|
defaultValue: TValue | (() => TValue)
|
||||||
defaultsByInstallVersion?: Record<`${number}.${number}.${number}`, TValue>
|
defaultsByInstallVersion?: Record<`${number}.${number}.${number}`, TValue>
|
||||||
onChange?: (newValue: any, oldValue?: any) => void
|
onChange?: (newValue: TValue, oldValue?: TValue) => void
|
||||||
// By default category is id.split('.'). However, changing id to assign
|
// By default category is id.split('.'). However, changing id to assign
|
||||||
// new category has poor backward compatibility. Use this field to overwrite
|
// new category has poor backward compatibility. Use this field to overwrite
|
||||||
// default category from id.
|
// default category from id.
|
||||||
@@ -38,8 +38,7 @@ export interface SettingParams<TValue = unknown> extends FormItem {
|
|||||||
category?: string[]
|
category?: string[]
|
||||||
experimental?: boolean
|
experimental?: boolean
|
||||||
deprecated?: boolean
|
deprecated?: boolean
|
||||||
// Deprecated values are mapped to new values.
|
migrateDeprecatedValue?: (value: TValue) => TValue
|
||||||
migrateDeprecatedValue?: (value: any) => any
|
|
||||||
// Version of the setting when it was added
|
// Version of the setting when it was added
|
||||||
versionAdded?: string
|
versionAdded?: string
|
||||||
// Version of the setting when it was last modified
|
// Version of the setting when it was last modified
|
||||||
@@ -57,7 +56,7 @@ export interface FormItem {
|
|||||||
name: string
|
name: string
|
||||||
type: SettingInputType | SettingCustomRenderer
|
type: SettingInputType | SettingCustomRenderer
|
||||||
tooltip?: string
|
tooltip?: string
|
||||||
attrs?: Record<string, any>
|
attrs?: Record<string, unknown>
|
||||||
options?: Array<string | SettingOption>
|
options?: Array<string | SettingOption>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
* 3. Check dist/assets/*.js files contain no tracking code
|
* 3. Check dist/assets/*.js files contain no tracking code
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { AuditLog } from '@/services/customerEventsService'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authentication metadata for sign-up tracking
|
* Authentication metadata for sign-up tracking
|
||||||
*/
|
*/
|
||||||
@@ -276,7 +278,7 @@ export interface TelemetryProvider {
|
|||||||
|
|
||||||
// Credit top-up tracking (composition with internal utilities)
|
// Credit top-up tracking (composition with internal utilities)
|
||||||
startTopupTracking(): void
|
startTopupTracking(): void
|
||||||
checkForCompletedTopup(events: any[] | undefined | null): boolean
|
checkForCompletedTopup(events: AuditLog[] | undefined | null): boolean
|
||||||
clearTopupTracking(): void
|
clearTopupTracking(): void
|
||||||
|
|
||||||
// Survey flow events
|
// Survey flow events
|
||||||
|
|||||||
@@ -466,14 +466,14 @@ type SubgraphDefinition = z.infer<typeof zSubgraphDefinition>
|
|||||||
* Type guard to check if an object is a SubgraphDefinition.
|
* Type guard to check if an object is a SubgraphDefinition.
|
||||||
* This helps TypeScript understand the type when z.lazy() breaks inference.
|
* This helps TypeScript understand the type when z.lazy() breaks inference.
|
||||||
*/
|
*/
|
||||||
export function isSubgraphDefinition(obj: any): obj is SubgraphDefinition {
|
export function isSubgraphDefinition(obj: unknown): obj is SubgraphDefinition {
|
||||||
return (
|
return (
|
||||||
obj &&
|
obj !== null &&
|
||||||
typeof obj === 'object' &&
|
typeof obj === 'object' &&
|
||||||
'id' in obj &&
|
'id' in obj &&
|
||||||
'name' in obj &&
|
'name' in obj &&
|
||||||
'nodes' in obj &&
|
'nodes' in obj &&
|
||||||
Array.isArray(obj.nodes) &&
|
Array.isArray((obj as SubgraphDefinition).nodes) &&
|
||||||
'inputNode' in obj &&
|
'inputNode' in obj &&
|
||||||
'outputNode' in obj
|
'outputNode' in obj
|
||||||
)
|
)
|
||||||
@@ -514,6 +514,7 @@ export async function validateComfyWorkflow(
|
|||||||
*/
|
*/
|
||||||
const zNodeInputValue = z.union([
|
const zNodeInputValue = z.union([
|
||||||
// For widget values (can be any type)
|
// For widget values (can be any type)
|
||||||
|
|
||||||
z.any(),
|
z.any(),
|
||||||
// For node links [nodeId, slotIndex]
|
// For node links [nodeId, slotIndex]
|
||||||
z.tuple([zNodeId, zSlotIndex])
|
z.tuple([zNodeId, zSlotIndex])
|
||||||
|
|||||||
@@ -28,10 +28,13 @@ const zOutputs = z
|
|||||||
audio: z.array(zResultItem).optional(),
|
audio: z.array(zResultItem).optional(),
|
||||||
images: z.array(zResultItem).optional(),
|
images: z.array(zResultItem).optional(),
|
||||||
video: z.array(zResultItem).optional(),
|
video: z.array(zResultItem).optional(),
|
||||||
animated: z.array(z.boolean()).optional()
|
animated: z.array(z.boolean()).optional(),
|
||||||
|
text: z.union([z.string(), z.array(z.string())]).optional()
|
||||||
})
|
})
|
||||||
.passthrough()
|
.passthrough()
|
||||||
|
|
||||||
|
export type NodeExecutionOutput = z.infer<typeof zOutputs>
|
||||||
|
|
||||||
// WS messages
|
// WS messages
|
||||||
const zStatusWsMessageStatus = z.object({
|
const zStatusWsMessageStatus = z.object({
|
||||||
exec_info: z.object({
|
exec_info: z.object({
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ export type ComboInputSpecV2 = z.infer<typeof zComboInputSpecV2>
|
|||||||
export type InputSpec = z.infer<typeof zInputSpec>
|
export type InputSpec = z.infer<typeof zInputSpec>
|
||||||
|
|
||||||
export function validateComfyNodeDef(
|
export function validateComfyNodeDef(
|
||||||
data: any,
|
data: unknown,
|
||||||
onError: (error: string) => void = console.warn
|
onError: (error: string) => void = console.warn
|
||||||
): ComfyNodeDef | null {
|
): ComfyNodeDef | null {
|
||||||
const result = zComfyNodeDef.safeParse(data)
|
const result = zComfyNodeDef.safeParse(data)
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export interface ToastMessageOptions {
|
|||||||
/**
|
/**
|
||||||
* Detail content of the message.
|
* Detail content of the message.
|
||||||
*/
|
*/
|
||||||
detail?: any | undefined
|
detail?: string
|
||||||
/**
|
/**
|
||||||
* Whether the message can be closed manually using the close icon.
|
* Whether the message can be closed manually using the close icon.
|
||||||
* @defaultValue true
|
* @defaultValue true
|
||||||
@@ -84,11 +84,12 @@ export interface ToastMessageOptions {
|
|||||||
/**
|
/**
|
||||||
* Style class of the message.
|
* Style class of the message.
|
||||||
*/
|
*/
|
||||||
styleClass?: any
|
styleClass?: string | string[] | Record<string, boolean>
|
||||||
/**
|
/**
|
||||||
* Style class of the content.
|
* Style class of the content.
|
||||||
|
* Matches PrimeVue Toast API which accepts Vue class bindings.
|
||||||
*/
|
*/
|
||||||
contentStyleClass?: any
|
contentStyleClass?: string | string[] | Record<string, boolean>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ToastManager = {
|
export type ToastManager = {
|
||||||
@@ -107,8 +108,8 @@ export interface ExtensionManager {
|
|||||||
dialog: ReturnType<typeof useDialogService>
|
dialog: ReturnType<typeof useDialogService>
|
||||||
command: CommandManager
|
command: CommandManager
|
||||||
setting: {
|
setting: {
|
||||||
get: (id: string) => any
|
get: <T = unknown>(id: string) => T | undefined
|
||||||
set: (id: string, value: any) => void
|
set: <T = unknown>(id: string, value: T) => void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
8
src/types/litegraph-augmentation.d.ts
vendored
8
src/types/litegraph-augmentation.d.ts
vendored
@@ -7,6 +7,7 @@ import type {
|
|||||||
} from '@/lib/litegraph/src/litegraph'
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||||
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||||
|
import type { NodeExecutionOutput } from '@/schemas/apiSchema'
|
||||||
import type { ComfyNodeDef as ComfyNodeDefV2 } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
import type { ComfyNodeDef as ComfyNodeDefV2 } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||||
import type { ComfyNodeDef as ComfyNodeDefV1 } from '@/schemas/nodeDefSchema'
|
import type { ComfyNodeDef as ComfyNodeDefV1 } from '@/schemas/nodeDefSchema'
|
||||||
import type { DOMWidget, DOMWidgetOptions } from '@/scripts/domWidget'
|
import type { DOMWidget, DOMWidgetOptions } from '@/scripts/domWidget'
|
||||||
@@ -94,7 +95,12 @@ declare module '@/lib/litegraph/src/litegraph' {
|
|||||||
*/
|
*/
|
||||||
onAfterGraphConfigured?(): void
|
onAfterGraphConfigured?(): void
|
||||||
onGraphConfigured?(): void
|
onGraphConfigured?(): void
|
||||||
onExecuted?(output: any): void
|
/**
|
||||||
|
* Callback fired when node execution completes.
|
||||||
|
* Output contains known media properties (images, audio, video) plus
|
||||||
|
* arbitrary node-specific outputs (text, ui, custom properties).
|
||||||
|
*/
|
||||||
|
onExecuted?(output: NodeExecutionOutput): void
|
||||||
onNodeCreated?(this: LGraphNode): void
|
onNodeCreated?(this: LGraphNode): void
|
||||||
/** @deprecated groupNode */
|
/** @deprecated groupNode */
|
||||||
setInnerNodes?(nodes: LGraphNode[]): void
|
setInnerNodes?(nodes: LGraphNode[]): void
|
||||||
|
|||||||
@@ -69,15 +69,15 @@ export type GltfChunkHeader = {
|
|||||||
type GltfExtras = {
|
type GltfExtras = {
|
||||||
workflow?: string | object
|
workflow?: string | object
|
||||||
prompt?: string | object
|
prompt?: string | object
|
||||||
[key: string]: any
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GltfJsonData = {
|
export type GltfJsonData = {
|
||||||
asset?: {
|
asset?: {
|
||||||
extras?: GltfExtras
|
extras?: GltfExtras
|
||||||
[key: string]: any
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
[key: string]: any
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user