Skip to content
8 changes: 8 additions & 0 deletions src/lib/litegraph/src/LGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2603,6 +2603,10 @@ export class Subgraph
}

addInput(name: string, type: string): SubgraphInput {
if (name === null || type === null) {
throw new Error('Name and type are required for subgraph input')
}
Comment on lines +2606 to +2608
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Runtime validation does not catch undefined values.

The check name === null || type === null only catches null, but the tests expect both null and undefined to throw (see SubgraphEdgeCases.test.ts lines 114-123). Since undefined === null evaluates to false, passing undefined will not trigger the error.

🐛 Proposed fix to also check for undefined
 addInput(name: string, type: string): SubgraphInput {
-  if (name === null || type === null) {
+  if (name == null || type == null) {
     throw new Error('Name and type are required for subgraph input')
   }

Using loose equality (==) catches both null and undefined in a single check.

🤖 Prompt for AI Agents
In `@src/lib/litegraph/src/LGraph.ts` around lines 2606 - 2608, The runtime
validation only checks for null and misses undefined; update the guard around
subgraph inputs in LGraph (the block that currently throws "Name and type are
required for subgraph input") to detect both null and undefined (e.g., use loose
equality like name == null || type == null or explicitly check name === null ||
name === undefined || type === null || type === undefined) so the same error is
thrown when name or type is undefined as expected by SubgraphEdgeCases tests.


this.events.dispatch('adding-input', { name, type })

const input = new SubgraphInput(
Expand All @@ -2621,6 +2625,10 @@ export class Subgraph
}

addOutput(name: string, type: string): SubgraphOutput {
if (name === null || type === null) {
throw new Error('Name and type are required for subgraph output')
}
Comment on lines +2628 to +2630
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Same issue: undefined values bypass validation.

This check mirrors addInput and has the same problem—undefined will not trigger the error.

🐛 Proposed fix
 addOutput(name: string, type: string): SubgraphOutput {
-  if (name === null || type === null) {
+  if (name == null || type == null) {
     throw new Error('Name and type are required for subgraph output')
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (name === null || type === null) {
throw new Error('Name and type are required for subgraph output')
}
if (name == null || type == null) {
throw new Error('Name and type are required for subgraph output')
}
🤖 Prompt for AI Agents
In `@src/lib/litegraph/src/LGraph.ts` around lines 2628 - 2630, The current
validation only rejects null but allows undefined for subgraph outputs; update
the guard that throws "Name and type are required for subgraph output" to check
for both null and undefined (e.g. use name == null || type == null or explicit
checks name === undefined || name === null and same for type) so undefined
values are rejected the same way as null; modify the condition in the same block
that currently reads if (name === null || type === null) { throw new Error('Name
and type are required for subgraph output') }.


this.events.dispatch('adding-output', { name, type })

const output = new SubgraphOutput(
Expand Down
27 changes: 4 additions & 23 deletions src/lib/litegraph/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,10 @@ type KeysOfType<T, Match> = Exclude<
>

/** The names of all (optional) methods and functions in T */
export type MethodNames<T> = KeysOfType<T, ((...args: any) => any) | undefined>
export type MethodNames<T> = KeysOfType<
T,
((...args: unknown[]) => unknown) | undefined
>
export interface NewNodePosition {
node: LGraphNode
newPos: {
Expand Down Expand Up @@ -459,28 +462,6 @@ export interface ISubgraphInput extends INodeInputSlot {
_subgraphSlot: SubgraphInput
}

/**
* Shorthand for {@link Parameters} of optional callbacks.
* @example
* ```ts
* const { onClick } = CustomClass.prototype
* CustomClass.prototype.onClick = function (...args: CallbackParams<typeof onClick>) {
* const r = onClick?.apply(this, args)
* // ...
* return r
* }
* ```
*/
export type CallbackParams<T extends ((...args: any) => any) | undefined> =
Parameters<Exclude<T, undefined>>

/**
* Shorthand for {@link ReturnType} of optional callbacks.
* @see {@link CallbackParams}
*/
export type CallbackReturn<T extends ((...args: any) => any) | undefined> =
ReturnType<Exclude<T, undefined>>

/**
* An object that can be hovered over.
*/
Expand Down
10 changes: 3 additions & 7 deletions src/lib/litegraph/src/subgraph/ExecutableNodeDTO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import { InvalidLinkError } from '@/lib/litegraph/src/infrastructure/InvalidLink
import { NullGraphError } from '@/lib/litegraph/src/infrastructure/NullGraphError'
import { RecursionError } from '@/lib/litegraph/src/infrastructure/RecursionError'
import { SlotIndexError } from '@/lib/litegraph/src/infrastructure/SlotIndexError'
import type {
CallbackParams,
CallbackReturn,
ISlotType
} from '@/lib/litegraph/src/interfaces'
import type { ISlotType } from '@/lib/litegraph/src/interfaces'
import { LGraphEventMode, LiteGraph } from '@/lib/litegraph/src/litegraph'

import type { Subgraph } from './Subgraph'
Expand Down Expand Up @@ -45,8 +41,8 @@ type ResolvedInput = {
*/
export class ExecutableNodeDTO implements ExecutableLGraphNode {
applyToGraph?(
...args: CallbackParams<typeof this.node.applyToGraph>
): CallbackReturn<typeof this.node.applyToGraph>
...args: Parameters<NonNullable<typeof this.node.applyToGraph>>
): ReturnType<NonNullable<typeof this.node.applyToGraph>>

/** The graph that this node is a part of. */
readonly graph: LGraph | Subgraph
Expand Down
50 changes: 28 additions & 22 deletions src/lib/litegraph/src/subgraph/SubgraphEdgeCases.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ describe.skip('SubgraphEdgeCases - Invalid States', () => {
name: 'fake',
type: 'number',
disconnect: () => {}
} as any
} as Partial<Parameters<typeof subgraph.removeInput>[0]> as Parameters<
typeof subgraph.removeInput
>[0]

// Should throw appropriate error for non-existent input
expect(() => {
Expand All @@ -97,41 +99,43 @@ describe.skip('SubgraphEdgeCases - Invalid States', () => {
name: 'fake',
type: 'number',
disconnect: () => {}
} as any
} as Partial<Parameters<typeof subgraph.removeOutput>[0]> as Parameters<
typeof subgraph.removeOutput
>[0]

expect(() => {
subgraph.removeOutput(fakeOutput)
}).toThrow(/Output not found/) // Expected error
})

it('should handle null/undefined input names', () => {
it('should throw error for null/undefined input names', () => {
const subgraph = createTestSubgraph()

// ISSUE: Current implementation allows null/undefined names which may cause runtime errors
// TODO: Consider adding validation to prevent null/undefined names
// This test documents the current permissive behavior
const nullString: string = null!
const undefinedString: string = undefined!

expect(() => {
subgraph.addInput(null as any, 'number')
}).not.toThrow() // Current behavior: allows null
subgraph.addInput(nullString, 'number')
}).toThrow()

expect(() => {
subgraph.addInput(undefined as any, 'number')
}).not.toThrow() // Current behavior: allows undefined
subgraph.addInput(undefinedString, 'number')
}).toThrow()
})

it('should handle null/undefined output names', () => {
const subgraph = createTestSubgraph()

// ISSUE: Current implementation allows null/undefined names which may cause runtime errors
// TODO: Consider adding validation to prevent null/undefined names
// This test documents the current permissive behavior
const nullString: string = null!
const undefinedString: string = undefined!

expect(() => {
subgraph.addOutput(null as any, 'number')
}).not.toThrow() // Current behavior: allows null
subgraph.addOutput(nullString, 'number')
}).toThrow()

expect(() => {
subgraph.addOutput(undefined as any, 'number')
}).not.toThrow() // Current behavior: allows undefined
subgraph.addOutput(undefinedString, 'number')
}).toThrow()
})

it('should handle empty string names', () => {
Expand All @@ -151,14 +155,16 @@ describe.skip('SubgraphEdgeCases - Invalid States', () => {
it('should handle undefined types gracefully', () => {
const subgraph = createTestSubgraph()

// Undefined type should not crash but may have default behavior
const undefinedString: string = undefined!

// Undefined type should throw error
expect(() => {
subgraph.addInput('test', undefined as any)
}).not.toThrow()
subgraph.addInput('test', undefinedString)
}).toThrow()

expect(() => {
subgraph.addOutput('test', undefined as any)
}).not.toThrow()
subgraph.addOutput('test', undefinedString)
}).toThrow()
})

it('should handle duplicate slot names', () => {
Expand Down
32 changes: 21 additions & 11 deletions src/lib/litegraph/src/subgraph/SubgraphMemory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import {
createTestSubgraphNode
} from './__fixtures__/subgraphHelpers'

type InputWithWidget = {
_widget?: IWidget | { type: string; value: unknown; name: string }
_connection?: { id: number; type: string }
_listenerController?: AbortController
}

describe.skip('SubgraphNode Memory Management', () => {
describe.skip('Event Listener Cleanup', () => {
it('should register event listeners on construction', () => {
Expand Down Expand Up @@ -308,14 +314,14 @@ describe.skip('SubgraphMemory - Widget Reference Management', () => {

// Set widget reference
if (input && '_widget' in input) {
;(input as any)._widget = mockWidget
expect((input as any)._widget).toBe(mockWidget)
;(input as InputWithWidget)._widget = mockWidget
expect((input as InputWithWidget)._widget).toBe(mockWidget)
}

// Clear widget reference
if (input && '_widget' in input) {
;(input as any)._widget = undefined
expect((input as any)._widget).toBeUndefined()
;(input as InputWithWidget)._widget = undefined
expect((input as InputWithWidget)._widget).toBeUndefined()
}
}
)
Expand Down Expand Up @@ -360,30 +366,34 @@ describe.skip('SubgraphMemory - Widget Reference Management', () => {

// Set up references that should be cleaned up
const mockReferences = {
widget: { type: 'number', value: 42 },
widget: { type: 'number', value: 42, name: 'mock_widget' },
connection: { id: 1, type: 'number' },
listener: vi.fn()
}

// Set references
if (input) {
;(input as any)._widget = mockReferences.widget
;(input as any)._connection = mockReferences.connection
;(input as InputWithWidget)._widget = mockReferences.widget
;(input as InputWithWidget)._connection = mockReferences.connection
}
if (output) {
;(input as any)._connection = mockReferences.connection
;(input as InputWithWidget)._connection = mockReferences.connection
}

// Verify references are set
expect((input as any)?._widget).toBe(mockReferences.widget)
expect((input as any)?._connection).toBe(mockReferences.connection)
expect((input as InputWithWidget)?._widget).toBe(mockReferences.widget)
expect((input as InputWithWidget)?._connection).toBe(
mockReferences.connection
)

// Simulate proper cleanup (what onRemoved should do)
subgraphNode.onRemoved()

// Input-specific listeners should be cleaned up (this works)
if (input && '_listenerController' in input) {
expect((input as any)._listenerController?.signal.aborted).toBe(true)
expect(
(input as InputWithWidget)._listenerController?.signal.aborted
).toBe(true)
}
}
)
Expand Down
5 changes: 3 additions & 2 deletions src/lib/litegraph/src/subgraph/SubgraphNode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { describe, expect, it, vi } from 'vitest'

import type { SubgraphNode } from '@/lib/litegraph/src/litegraph'
import { LGraph, Subgraph } from '@/lib/litegraph/src/litegraph'
import type { SubgraphInput } from '@/lib/litegraph/src/subgraph/SubgraphInput'

import { subgraphTest } from './__fixtures__/subgraphFixtures'
import {
Expand Down Expand Up @@ -531,7 +532,7 @@ describe.skip('SubgraphNode Cleanup', () => {

// Now trigger an event - only node1 should respond
subgraph.events.dispatch('input-added', {
input: { name: 'test', type: 'number', id: 'test-id' } as any
input: { name: 'test', type: 'number', id: 'test-id' } as SubgraphInput
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider using as Partial<SubgraphInput> as SubgraphInput for consistency.

The mock object { name: 'test', type: 'number', id: 'test-id' } only implements a subset of the SubgraphInput interface. For consistency with other patterns in this PR (e.g., as Partial<CanvasRenderingContext2D> as CanvasRenderingContext2D in textUtils.test.ts), consider making the partial implementation explicit:

    subgraph.events.dispatch('input-added', {
-      input: { name: 'test', type: 'number', id: 'test-id' } as SubgraphInput
+      input: { name: 'test', type: 'number', id: 'test-id' } as Partial<SubgraphInput> as SubgraphInput
    })

This makes it clear the mock is intentionally incomplete. Based on learnings, this pattern is preferred for test mocks.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
input: { name: 'test', type: 'number', id: 'test-id' } as SubgraphInput
subgraph.events.dispatch('input-added', {
input: { name: 'test', type: 'number', id: 'test-id' } as Partial<SubgraphInput> as SubgraphInput
})
🤖 Prompt for AI Agents
In `@src/lib/litegraph/src/subgraph/SubgraphNode.test.ts` at line 535, Replace the
inline cast of the test input object with an explicit partial cast to signal
it's an intentionally incomplete mock: change the object used for the input
parameter in SubgraphNode.test.ts from using "as SubgraphInput" to "as
Partial<SubgraphInput> as SubgraphInput" so the mock `{ name: 'test', type:
'number', id: 'test-id' }` clearly indicates it's a partial implementation of
the SubgraphInput interface.

})

// Only node1 should have added an input
Expand All @@ -558,7 +559,7 @@ describe.skip('SubgraphNode Cleanup', () => {

// Trigger an event - no nodes should respond
subgraph.events.dispatch('input-added', {
input: { name: 'test', type: 'number', id: 'test-id' } as any
input: { name: 'test', type: 'number', id: 'test-id' } as SubgraphInput
})

// Without cleanup: all 3 removed nodes would have added an input
Expand Down
Loading