From b9f75b6cc8d96b90b50a99c2740f9df98692275c Mon Sep 17 00:00:00 2001 From: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com> Date: Thu, 11 Dec 2025 22:10:01 +0100 Subject: [PATCH] fix: improve type safety in type definitions (#7337) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Replace `any` types with proper TypeScript types in core type definitions - Add generics to `SettingParams`, `setting.get()`, and `setting.set()` 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('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()` and `setting.set()`, 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`, `SettingMigration`, `SettingParams` | | `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) --- src/composables/maskeditor/useToolManager.ts | 4 ++-- src/extensions/core/previewAny.ts | 3 ++- .../settings/constants/coreSettings.ts | 10 ++++++---- src/platform/settings/settingStore.ts | 7 ++++--- src/platform/settings/types.ts | 19 +++++++++---------- src/platform/telemetry/types.ts | 4 +++- .../validation/schemas/workflowSchema.ts | 7 ++++--- src/schemas/apiSchema.ts | 5 ++++- src/schemas/nodeDefSchema.ts | 2 +- src/types/extensionTypes.ts | 11 ++++++----- src/types/litegraph-augmentation.d.ts | 8 +++++++- src/types/metadataTypes.ts | 6 +++--- 12 files changed, 51 insertions(+), 35 deletions(-) diff --git a/src/composables/maskeditor/useToolManager.ts b/src/composables/maskeditor/useToolManager.ts index f51ce7b15..9b1847699 100644 --- a/src/composables/maskeditor/useToolManager.ts +++ b/src/composables/maskeditor/useToolManager.ts @@ -22,10 +22,10 @@ export function useToolManager( const coordinateTransform = useCoordinateTransform() const brushDrawing = useBrushDrawing({ - useDominantAxis: app.extensionManager.setting.get( + useDominantAxis: app.extensionManager.setting.get( 'Comfy.MaskEditor.UseDominantAxis' ), - brushAdjustmentSpeed: app.extensionManager.setting.get( + brushAdjustmentSpeed: app.extensionManager.setting.get( 'Comfy.MaskEditor.BrushAdjustmentSpeed' ) }) diff --git a/src/extensions/core/previewAny.ts b/src/extensions/core/previewAny.ts index fd8dd96f7..89fcf3cd8 100644 --- a/src/extensions/core/previewAny.ts +++ b/src/extensions/core/previewAny.ts @@ -74,7 +74,8 @@ useExtensionService().registerExtension({ this.widgets?.filter((w) => w.name === 'preview') ?? [] for (const previewWidget of previewWidgets) { - previewWidget.value = message.text[0] + const text = message.text ?? '' + previewWidget.value = Array.isArray(text) ? (text[0] ?? '') : text } } } diff --git a/src/platform/settings/constants/coreSettings.ts b/src/platform/settings/constants/coreSettings.ts index 5bc48e0bf..340a7c77c 100644 --- a/src/platform/settings/constants/coreSettings.ts +++ b/src/platform/settings/constants/coreSettings.ts @@ -602,10 +602,12 @@ export const CORE_SETTINGS: SettingParams[] = [ defaultValue: [] as Keybinding[], versionAdded: '1.3.7', versionModified: '1.7.3', - migrateDeprecatedValue: (value: any[]) => { + migrateDeprecatedValue: ( + value: (Keybinding & { targetSelector?: string })[] + ) => { return value.map((keybinding) => { - if (keybinding['targetSelector'] === '#graph-canvas') { - keybinding['targetElementId'] = 'graph-canvas-container' + if (keybinding.targetSelector === '#graph-canvas') { + keybinding.targetElementId = 'graph-canvas-container' } return keybinding }) @@ -780,7 +782,7 @@ export const CORE_SETTINGS: SettingParams[] = [ tooltip: 'Server config values used for frontend display only', type: 'hidden', // Mapping from server config id to value. - defaultValue: {} as Record, + defaultValue: {} as Record, versionAdded: '1.4.8' }, { diff --git a/src/platform/settings/settingStore.ts b/src/platform/settings/settingStore.ts index 83742de70..2c9e8e0a3 100644 --- a/src/platform/settings/settingStore.ts +++ b/src/platform/settings/settingStore.ts @@ -117,9 +117,10 @@ export const useSettingStore = defineStore('setting', () => { return versionedDefault } - return typeof param.defaultValue === 'function' - ? param.defaultValue() - : param.defaultValue + const defaultValue = param.defaultValue + return typeof defaultValue === 'function' + ? (defaultValue as () => Settings[K])() + : defaultValue } function getVersionedDefaultValue< diff --git a/src/platform/settings/types.ts b/src/platform/settings/types.ts index 9bd48fc8c..021fac375 100644 --- a/src/platform/settings/types.ts +++ b/src/platform/settings/types.ts @@ -16,21 +16,21 @@ type SettingInputType = type SettingCustomRenderer = ( name: string, - setter: (v: any) => void, - value: any, - attrs: any + setter: (v: unknown) => void, + value: unknown, + attrs?: Record ) => HTMLElement export interface SettingOption { text: string - value?: any + value?: string | number } -export interface SettingParams extends FormItem { +export interface SettingParams extends FormItem { id: keyof Settings - defaultValue: any | (() => any) + defaultValue: TValue | (() => 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 // new category has poor backward compatibility. Use this field to overwrite // default category from id. @@ -38,8 +38,7 @@ export interface SettingParams extends FormItem { category?: string[] experimental?: boolean deprecated?: boolean - // Deprecated values are mapped to new values. - migrateDeprecatedValue?: (value: any) => any + migrateDeprecatedValue?: (value: TValue) => TValue // Version of the setting when it was added versionAdded?: string // Version of the setting when it was last modified @@ -57,7 +56,7 @@ export interface FormItem { name: string type: SettingInputType | SettingCustomRenderer tooltip?: string - attrs?: Record + attrs?: Record options?: Array } diff --git a/src/platform/telemetry/types.ts b/src/platform/telemetry/types.ts index 5b5e0f9fc..a707069a4 100644 --- a/src/platform/telemetry/types.ts +++ b/src/platform/telemetry/types.ts @@ -12,6 +12,8 @@ * 3. Check dist/assets/*.js files contain no tracking code */ +import type { AuditLog } from '@/services/customerEventsService' + /** * Authentication metadata for sign-up tracking */ @@ -276,7 +278,7 @@ export interface TelemetryProvider { // Credit top-up tracking (composition with internal utilities) startTopupTracking(): void - checkForCompletedTopup(events: any[] | undefined | null): boolean + checkForCompletedTopup(events: AuditLog[] | undefined | null): boolean clearTopupTracking(): void // Survey flow events diff --git a/src/platform/workflow/validation/schemas/workflowSchema.ts b/src/platform/workflow/validation/schemas/workflowSchema.ts index cc73a3928..558ef819d 100644 --- a/src/platform/workflow/validation/schemas/workflowSchema.ts +++ b/src/platform/workflow/validation/schemas/workflowSchema.ts @@ -466,14 +466,14 @@ type SubgraphDefinition = z.infer * Type guard to check if an object is a SubgraphDefinition. * 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 ( - obj && + obj !== null && typeof obj === 'object' && 'id' in obj && 'name' in obj && 'nodes' in obj && - Array.isArray(obj.nodes) && + Array.isArray((obj as SubgraphDefinition).nodes) && 'inputNode' in obj && 'outputNode' in obj ) @@ -514,6 +514,7 @@ export async function validateComfyWorkflow( */ const zNodeInputValue = z.union([ // For widget values (can be any type) + z.any(), // For node links [nodeId, slotIndex] z.tuple([zNodeId, zSlotIndex]) diff --git a/src/schemas/apiSchema.ts b/src/schemas/apiSchema.ts index 50bf0813d..224ceb0df 100644 --- a/src/schemas/apiSchema.ts +++ b/src/schemas/apiSchema.ts @@ -28,10 +28,13 @@ const zOutputs = z audio: z.array(zResultItem).optional(), images: 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() +export type NodeExecutionOutput = z.infer + // WS messages const zStatusWsMessageStatus = z.object({ exec_info: z.object({ diff --git a/src/schemas/nodeDefSchema.ts b/src/schemas/nodeDefSchema.ts index 867ee46ba..bb36da228 100644 --- a/src/schemas/nodeDefSchema.ts +++ b/src/schemas/nodeDefSchema.ts @@ -267,7 +267,7 @@ export type ComboInputSpecV2 = z.infer export type InputSpec = z.infer export function validateComfyNodeDef( - data: any, + data: unknown, onError: (error: string) => void = console.warn ): ComfyNodeDef | null { const result = zComfyNodeDef.safeParse(data) diff --git a/src/types/extensionTypes.ts b/src/types/extensionTypes.ts index 2c96faa05..7061a9b15 100644 --- a/src/types/extensionTypes.ts +++ b/src/types/extensionTypes.ts @@ -67,7 +67,7 @@ export interface ToastMessageOptions { /** * Detail content of the message. */ - detail?: any | undefined + detail?: string /** * Whether the message can be closed manually using the close icon. * @defaultValue true @@ -84,11 +84,12 @@ export interface ToastMessageOptions { /** * Style class of the message. */ - styleClass?: any + styleClass?: string | string[] | Record /** * Style class of the content. + * Matches PrimeVue Toast API which accepts Vue class bindings. */ - contentStyleClass?: any + contentStyleClass?: string | string[] | Record } export type ToastManager = { @@ -107,8 +108,8 @@ export interface ExtensionManager { dialog: ReturnType command: CommandManager setting: { - get: (id: string) => any - set: (id: string, value: any) => void + get: (id: string) => T | undefined + set: (id: string, value: T) => void } } diff --git a/src/types/litegraph-augmentation.d.ts b/src/types/litegraph-augmentation.d.ts index f6b4aaf42..0b283b158 100644 --- a/src/types/litegraph-augmentation.d.ts +++ b/src/types/litegraph-augmentation.d.ts @@ -7,6 +7,7 @@ import type { } from '@/lib/litegraph/src/litegraph' import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' 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 ComfyNodeDefV1 } from '@/schemas/nodeDefSchema' import type { DOMWidget, DOMWidgetOptions } from '@/scripts/domWidget' @@ -94,7 +95,12 @@ declare module '@/lib/litegraph/src/litegraph' { */ onAfterGraphConfigured?(): 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 /** @deprecated groupNode */ setInnerNodes?(nodes: LGraphNode[]): void diff --git a/src/types/metadataTypes.ts b/src/types/metadataTypes.ts index 943e39ae7..72f3a7293 100644 --- a/src/types/metadataTypes.ts +++ b/src/types/metadataTypes.ts @@ -69,15 +69,15 @@ export type GltfChunkHeader = { type GltfExtras = { workflow?: string | object prompt?: string | object - [key: string]: any + [key: string]: unknown } export type GltfJsonData = { asset?: { extras?: GltfExtras - [key: string]: any + [key: string]: unknown } - [key: string]: any + [key: string]: unknown } /**