diff --git a/src/composables/widgets/useBooleanWidget.ts b/src/composables/widgets/useBooleanWidget.ts index e8d57d5709..5c226a3850 100644 --- a/src/composables/widgets/useBooleanWidget.ts +++ b/src/composables/widgets/useBooleanWidget.ts @@ -17,6 +17,7 @@ export const useBooleanWidget = () => { } return { + // @ts-expect-error InputSpec is not typed correctly widget: node.addWidget('toggle', inputName, defaultVal, () => {}, options) } } diff --git a/src/composables/widgets/useComboWidget.ts b/src/composables/widgets/useComboWidget.ts index 410bfc5d93..6a9cf7b89b 100644 --- a/src/composables/widgets/useComboWidget.ts +++ b/src/composables/widgets/useComboWidget.ts @@ -20,6 +20,7 @@ export const useComboWidget = () => { const res = { widget: node.addWidget('combo', inputName, defaultValue, () => {}, { + // @ts-expect-error InputSpec is not typed correctly values: options ?? inputData[0] }) as IComboWidget } @@ -31,6 +32,7 @@ export const useComboWidget = () => { node, widget: res.widget }) + // @ts-expect-error InputSpec is not typed correctly if (remote.refresh_button) remoteWidget.addRefreshButton() const origOptions = res.widget.options diff --git a/src/composables/widgets/useFloatWidget.ts b/src/composables/widgets/useFloatWidget.ts index 7005a50727..52a9692626 100644 --- a/src/composables/widgets/useFloatWidget.ts +++ b/src/composables/widgets/useFloatWidget.ts @@ -2,7 +2,7 @@ import type { LGraphNode } from '@comfyorg/litegraph' import type { INumericWidget } from '@comfyorg/litegraph/dist/types/widgets' import _ from 'lodash' -import type { InputSpec } from '@/schemas/nodeDefSchema' +import type { FloatInputOptions, InputSpec } from '@/schemas/nodeDefSchema' import type { ComfyWidgetConstructor } from '@/scripts/widgets' import { useSettingStore } from '@/stores/settingStore' import { getNumberDefaults } from '@/utils/mathUtil' @@ -45,11 +45,14 @@ export const useFloatWidget = () => { settingStore.get('Comfy.FloatRoundingPrecision') || undefined const enableRounding = !settingStore.get('Comfy.DisableFloatRounding') - const { val, config } = getNumberDefaults(inputOptions, { - defaultStep: 0.5, - precision, - enableRounding - }) + const { val, config } = getNumberDefaults( + inputOptions as FloatInputOptions, + { + defaultStep: 0.5, + precision, + enableRounding + } + ) return { widget: node.addWidget( @@ -57,6 +60,7 @@ export const useFloatWidget = () => { inputName, val, onFloatValueChange, + // @ts-expect-error InputSpec is not typed correctly config ) } diff --git a/src/composables/widgets/useImageUploadWidget.ts b/src/composables/widgets/useImageUploadWidget.ts index 96f885262c..4466fdb574 100644 --- a/src/composables/widgets/useImageUploadWidget.ts +++ b/src/composables/widgets/useImageUploadWidget.ts @@ -44,9 +44,11 @@ export const useImageUploadWidget = () => { const { showPreview } = isVideo ? useNodeVideo(node) : useNodeImage(node) const fileFilter = isVideo ? isVideoFile : isImageFile + // @ts-expect-error InputSpec is not typed correctly const fileComboWidget = findFileComboWidget(node, imageInputName) const initialFile = `${fileComboWidget.value}` const formatPath = (value: InternalFile) => + // @ts-expect-error InputSpec is not typed correctly createAnnotatedPath(value, { rootFolder: image_folder }) const transform = (internalValue: InternalValue): ExposedValue => { @@ -66,6 +68,7 @@ export const useImageUploadWidget = () => { // Setup file upload handling const { openFileSelection } = useNodeImageUpload(node, { + // @ts-expect-error InputSpec is not typed correctly allow_batch, fileFilter, accept, diff --git a/src/composables/widgets/useIntWidget.ts b/src/composables/widgets/useIntWidget.ts index d8001f2890..b97c6a73d5 100644 --- a/src/composables/widgets/useIntWidget.ts +++ b/src/composables/widgets/useIntWidget.ts @@ -1,7 +1,7 @@ import type { LGraphNode } from '@comfyorg/litegraph' import type { INumericWidget } from '@comfyorg/litegraph/dist/types/widgets' -import type { InputSpec } from '@/schemas/nodeDefSchema' +import type { InputSpec, IntInputOptions } from '@/schemas/nodeDefSchema' import type { ComfyApp } from '@/scripts/app' import { type ComfyWidgetConstructor, @@ -50,7 +50,7 @@ export const useIntWidget = () => { : 'number' : 'number' - const { val, config } = getNumberDefaults(inputOptions, { + const { val, config } = getNumberDefaults(inputOptions as IntInputOptions, { defaultStep: 1, precision: 0, enableRounding: true @@ -58,6 +58,7 @@ export const useIntWidget = () => { config.precision = 0 const result = { + // @ts-expect-error InputSpec is not typed correctly widget: node.addWidget(widgetType, inputName, val, onValueChange, config) } diff --git a/src/composables/widgets/useRemoteWidget.ts b/src/composables/widgets/useRemoteWidget.ts index add21a25bc..19c5cee3a6 100644 --- a/src/composables/widgets/useRemoteWidget.ts +++ b/src/composables/widgets/useRemoteWidget.ts @@ -73,7 +73,7 @@ export function useRemoteWidget< widget: IWidget }) { const { inputData, defaultValue, node, widget } = options - const config: RemoteWidgetConfig = inputData[1].remote + const config = (inputData[1]?.remote ?? {}) as RemoteWidgetConfig const { refresh = 0, max_retries = MAX_RETRIES } = config const isPermanent = refresh <= 0 const cacheKey = createCacheKey(config) diff --git a/src/composables/widgets/useStringWidget.ts b/src/composables/widgets/useStringWidget.ts index e107735232..a2884d6905 100644 --- a/src/composables/widgets/useStringWidget.ts +++ b/src/composables/widgets/useStringWidget.ts @@ -82,6 +82,7 @@ export const useStringWidget = () => { } if (inputData[1]?.dynamicPrompts != undefined) + // @ts-expect-error InputSpec is not typed correctly res.widget.dynamicPrompts = inputData[1].dynamicPrompts return res diff --git a/src/extensions/core/groupNode.ts b/src/extensions/core/groupNode.ts index 3a165c4e93..7d1ba37308 100644 --- a/src/extensions/core/groupNode.ts +++ b/src/extensions/core/groupNode.ts @@ -1162,6 +1162,7 @@ export class GroupNodeHandler { def?.input?.optional?.[old.inputName] if (!input) continue + // @ts-expect-error InputSpec is not typed correctly widget.options.values = input[0] if ( diff --git a/src/extensions/core/load3d.ts b/src/extensions/core/load3d.ts index d41c9c872e..3386ab3e46 100644 --- a/src/extensions/core/load3d.ts +++ b/src/extensions/core/load3d.ts @@ -337,6 +337,7 @@ app.registerExtension({ // @ts-expect-error ComfyNode ['Preview3D'].includes(nodeType.comfyClass) ) { + // @ts-expect-error InputSpec is not typed correctly nodeData.input.required.image = ['PREVIEW_3D'] } }, @@ -439,6 +440,7 @@ app.registerExtension({ // @ts-expect-error ComfyNode ['Preview3DAnimation'].includes(nodeType.comfyClass) ) { + // @ts-expect-error InputSpec is not typed correctly nodeData.input.required.image = ['PREVIEW_3D_ANIMATION'] } }, diff --git a/src/extensions/core/uploadAudio.ts b/src/extensions/core/uploadAudio.ts index c1781da633..d13603506a 100644 --- a/src/extensions/core/uploadAudio.ts +++ b/src/extensions/core/uploadAudio.ts @@ -91,6 +91,7 @@ app.registerExtension({ // @ts-expect-error ComfyNode ['LoadAudio', 'SaveAudio', 'PreviewAudio'].includes(nodeType.comfyClass) ) { + // @ts-expect-error InputSpec is not typed correctly nodeData.input.required.audioUI = ['AUDIO_UI'] } }, @@ -149,6 +150,7 @@ app.registerExtension({ name: 'Comfy.UploadAudio', async beforeRegisterNodeDef(nodeType, nodeData: ComfyNodeDef) { if (nodeData?.input?.required?.audio?.[1]?.audio_upload === true) { + // @ts-expect-error InputSpec is not typed correctly nodeData.input.required.upload = ['AUDIOUPLOAD'] } }, diff --git a/src/extensions/core/widgetInputs.ts b/src/extensions/core/widgetInputs.ts index a3b9cdf3af..902503a192 100644 --- a/src/extensions/core/widgetInputs.ts +++ b/src/extensions/core/widgetInputs.ts @@ -245,6 +245,7 @@ export class PrimitiveNode extends LGraphNode { if (type in ComfyWidgets) { widget = (ComfyWidgets[type](this, 'value', inputData, app) || {}).widget } else { + // @ts-expect-error InputSpec is not typed correctly widget = this.addWidget(type, 'value', null, () => {}, {}) } @@ -452,6 +453,7 @@ function getConfig(widgetName: string) { function isConvertibleWidget(widget: IWidget, config: InputSpec): boolean { return ( + // @ts-expect-error InputSpec is not typed correctly (VALID_TYPES.includes(widget.type) || VALID_TYPES.includes(config[0])) && !widget.options?.forceInput ) @@ -677,6 +679,7 @@ export function mergeIfValid( return } getCustomConfig()[k] = + // @ts-expect-error InputSpec is not typed correctly v1 == null ? v2 : v2 == null ? v1 : Math.max(v1, v2) continue } else if (k === 'max') { @@ -686,6 +689,7 @@ export function mergeIfValid( return } getCustomConfig()[k] = + // @ts-expect-error InputSpec is not typed correctly v1 == null ? v2 : v2 == null ? v1 : Math.min(v1, v2) continue } else if (k === 'step') { @@ -703,6 +707,7 @@ export function mergeIfValid( v2 = v1 v1 = a } + // @ts-expect-error InputSpec is not typed correctly if (v1 % v2) { console.log( 'connection rejected: steps not divisible', diff --git a/src/schemas/nodeDefSchema.ts b/src/schemas/nodeDefSchema.ts index 0d0b59c148..1ebdc28acf 100644 --- a/src/schemas/nodeDefSchema.ts +++ b/src/schemas/nodeDefSchema.ts @@ -1,24 +1,6 @@ -import type { ZodType } from 'zod' import { z } from 'zod' import { fromZodError } from 'zod-validation-error' -function inputSpec( - spec: [TType, TSpec], - allowUpcast: boolean = true -) { - const [inputType, inputSpec] = spec - // e.g. "INT" => ["INT", {}] - const upcastTypes = allowUpcast - ? [inputType.transform((type) => [type, {}])] - : [] - - return z.union([ - z.tuple([inputType, inputSpec]), - z.tuple([inputType]).transform(([type]) => [type, {}]), - ...upcastTypes - ]) -} - const zRemoteWidgetConfig = z.object({ route: z.string().url().or(z.string().startsWith('/')), refresh: z.number().gte(128).safe().or(z.number().lte(0).safe()).optional(), @@ -89,21 +71,27 @@ const zComboInputOptions = zBaseInputOptions.extend({ remote: zRemoteWidgetConfig.optional() }) -const zIntInputSpec = inputSpec([z.literal('INT'), zIntInputOptions]) -const zFloatInputSpec = inputSpec([z.literal('FLOAT'), zFloatInputOptions]) -const zBooleanInputSpec = inputSpec([ - z.literal('BOOLEAN'), - zBooleanInputOptions +const zIntInputSpec = z.tuple([z.literal('INT'), zIntInputOptions.optional()]) +const zFloatInputSpec = z.tuple([ + z.literal('FLOAT'), + zFloatInputOptions.optional() +]) +const zBooleanInputSpec = z.tuple([ + z.literal('BOOLEAN'), + zBooleanInputOptions.optional() +]) +const zStringInputSpec = z.tuple([ + z.literal('STRING'), + zStringInputOptions.optional() +]) +const zComboInputSpec = z.tuple([ + z.array(z.union([z.string(), z.number()])), + zComboInputOptions.optional() +]) +const zComboInputSpecV2 = z.tuple([ + z.literal('COMBO'), + zComboInputOptions.optional() ]) -const zStringInputSpec = inputSpec([z.literal('STRING'), zStringInputOptions]) -const zComboInputSpec = inputSpec( - [z.array(z.any()), zComboInputOptions], - /* allowUpcast=*/ false -) -const zComboInputSpecV2 = inputSpec( - [z.literal('COMBO'), zComboInputOptions], - /* allowUpcast=*/ false -) export function isComboInputSpecV1( inputSpec: InputSpec @@ -154,9 +142,9 @@ export function isComboInputSpec( } const excludedLiterals = new Set(['INT', 'FLOAT', 'BOOLEAN', 'STRING', 'COMBO']) -const zCustomInputSpec = inputSpec([ +const zCustomInputSpec = z.tuple([ z.string().refine((value) => !excludedLiterals.has(value)), - zBaseInputOptions + zBaseInputOptions.optional() ]) const zInputSpec = z.union([ diff --git a/src/scripts/app.ts b/src/scripts/app.ts index 384366951c..e4aece79b4 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -1606,8 +1606,10 @@ export class ComfyApp { const widget = node.widgets[widgetNum] if (widget.type === 'combo') { if (def['input'].required?.[widget.name] !== undefined) { + // @ts-expect-error InputSpec is not typed correctly widget.options.values = def['input'].required[widget.name][0] } else if (def['input'].optional?.[widget.name] !== undefined) { + // @ts-expect-error InputSpec is not typed correctly widget.options.values = def['input'].optional[widget.name][0] } } diff --git a/src/services/litegraphService.ts b/src/services/litegraphService.ts index 7b3cca27df..84ca561e20 100644 --- a/src/services/litegraphService.ts +++ b/src/services/litegraphService.ts @@ -74,11 +74,13 @@ export const useLitegraphService = () => { if (widgetType === 'COMBO') { Object.assign( config, + // @ts-expect-error InputSpec is not typed correctly app.widgets.COMBO(this, inputName, inputData, app) || {} ) } else { Object.assign( config, + // @ts-expect-error InputSpec is not typed correctly app.widgets[widgetType](this, inputName, inputData, app) || {} ) } @@ -95,6 +97,7 @@ export const useLitegraphService = () => { ...shapeOptions, localized_name: st(nameKey, inputName) } + // @ts-expect-error InputSpec is not typed correctly this.addInput(inputName, type, inputOptions) widgetCreated = false } @@ -104,15 +107,19 @@ export const useLitegraphService = () => { if (!inputIsRequired) { config.widget.options.inputIsOptional = true } + // @ts-expect-error InputSpec is not typed correctly if (inputData[1]?.forceInput) { config.widget.options.forceInput = true } + // @ts-expect-error InputSpec is not typed correctly if (inputData[1]?.defaultInput) { config.widget.options.defaultInput = true } + // @ts-expect-error InputSpec is not typed correctly if (inputData[1]?.advanced) { config.widget.advanced = true } + // @ts-expect-error InputSpec is not typed correctly if (inputData[1]?.hidden) { config.widget.hidden = true } diff --git a/src/stores/nodeDefStore.ts b/src/stores/nodeDefStore.ts index 6a3686bf46..9d0c99ca5d 100644 --- a/src/stores/nodeDefStore.ts +++ b/src/stores/nodeDefStore.ts @@ -270,7 +270,7 @@ export const SYSTEM_NODE_DEFS: Record = { name: 'Reroute', display_name: 'Reroute', category: 'utils', - input: { required: { '': ['*'] }, optional: {} }, + input: { required: { '': ['*', {}] }, optional: {} }, output: ['*'], output_name: [''], output_is_list: [false], diff --git a/src/stores/widgetStore.ts b/src/stores/widgetStore.ts index aebdf962df..b72a52d15f 100644 --- a/src/stores/widgetStore.ts +++ b/src/stores/widgetStore.ts @@ -47,6 +47,7 @@ export const useWidgetStore = defineStore('widget', () => { if (Array.isArray(inputData[0])) return getDefaultValue(transformComboInput(inputData)) + // @ts-expect-error InputSpec is not typed correctly const widgetType = getWidgetType(inputData[0], inputData[1]?.name) const [_, props] = inputData @@ -54,12 +55,14 @@ export const useWidgetStore = defineStore('widget', () => { if (!props) return undefined if (props.default) return props.default + // @ts-expect-error InputSpec is not typed correctly if (widgetType === 'COMBO' && props.options?.length) return props.options[0] if (props.remote) return 'Loading...' return undefined } const transformComboInput = (inputData: InputSpec): ComboInputSpecV2 => { + // @ts-expect-error InputSpec is not typed correctly return isComboInputSpecV1(inputData) ? [ 'COMBO', diff --git a/src/utils/mathUtil.ts b/src/utils/mathUtil.ts index d29895278a..7eeee096bb 100644 --- a/src/utils/mathUtil.ts +++ b/src/utils/mathUtil.ts @@ -1,7 +1,10 @@ -import type { InputSpec } from '@/schemas/nodeDefSchema' +import type { + FloatInputOptions, + IntInputOptions +} from '@/schemas/nodeDefSchema' export function getNumberDefaults( - inputOptions: InputSpec[1], + inputOptions: IntInputOptions | FloatInputOptions, options: { defaultStep: number precision?: number diff --git a/tests-ui/tests/apiTypes.test.ts b/tests-ui/tests/apiTypes.test.ts index c5fd12dbdf..10379be697 100644 --- a/tests-ui/tests/apiTypes.test.ts +++ b/tests-ui/tests/apiTypes.test.ts @@ -9,7 +9,7 @@ import { const EXAMPLE_NODE_DEF: ComfyNodeDef = { input: { required: { - ckpt_name: [['model1.safetensors', 'model2.ckpt']] + ckpt_name: [['model1.safetensors', 'model2.ckpt'], {}] } }, output: ['MODEL', 'CLIP', 'VAE'], @@ -31,8 +31,6 @@ describe('validateNodeDef', () => { }) describe.each([ - [{ ckpt_name: 'foo' }, ['foo', {}]], - [{ ckpt_name: ['foo'] }, ['foo', {}]], [{ ckpt_name: ['foo', { default: 1 }] }, ['foo', { default: 1 }]], // Extra input spec should be preserved [{ ckpt_name: ['foo', { bar: 1 }] }, ['foo', { bar: 1 }]], diff --git a/tests-ui/tests/nodeDef.test.ts b/tests-ui/tests/nodeDef.test.ts index 79771dc998..ce542a7ebf 100644 --- a/tests-ui/tests/nodeDef.test.ts +++ b/tests-ui/tests/nodeDef.test.ts @@ -1,6 +1,7 @@ // @ts-strict-ignore import { describe, expect, it } from 'vitest' +import type { ComfyNodeDef } from '@/schemas/nodeDefSchema' import { BooleanInputSpec, ComfyInputsSpec, @@ -27,7 +28,7 @@ describe('ComfyInputsSpec', () => { hidden: { someHiddenValue: 42 } - } + } as ComfyNodeDef['input'] const result = new ComfyInputsSpec(plainObject) @@ -43,7 +44,7 @@ describe('ComfyInputsSpec', () => { intInput: ['INT', { min: 0, max: 100, default: 50 }], stringInput: ['STRING', { default: 'Hello', multiline: true }] } - } + } as ComfyNodeDef['input'] const result = new ComfyInputsSpec(plainObject) @@ -68,7 +69,7 @@ describe('ComfyInputsSpec', () => { ], floatInput: ['FLOAT', { min: 0, max: 1, step: 0.1 }] } - } + } as ComfyNodeDef['input'] const result = new ComfyInputsSpec(plainObject) @@ -88,7 +89,7 @@ describe('ComfyInputsSpec', () => { optional: { comboInput: [[1, 2, 3], { default: 2 }] } - } + } as ComfyNodeDef['input'] const result = new ComfyInputsSpec(plainObject) expect(result.optional.comboInput.type).toBe('COMBO') @@ -100,7 +101,7 @@ describe('ComfyInputsSpec', () => { optional: { comboInput: [[1, 2, 3], {}] } - } + } as ComfyNodeDef['input'] const result = new ComfyInputsSpec(plainObject) expect(result.optional.comboInput.type).toBe('COMBO') @@ -113,7 +114,7 @@ describe('ComfyInputsSpec', () => { optional: { customInput: ['CUSTOM_TYPE', { default: 'custom value' }] } - } + } as ComfyNodeDef['input'] const result = new ComfyInputsSpec(plainObject) expect(result.optional.customInput.type).toBe('CUSTOM_TYPE') @@ -126,7 +127,7 @@ describe('ComfyInputsSpec', () => { someHiddenValue: 42, anotherHiddenValue: { nested: 'object' } } - } + } as ComfyNodeDef['input'] const result = new ComfyInputsSpec(plainObject) @@ -163,7 +164,7 @@ describe('ComfyNodeDefImpl', () => { output: ['INT'], output_is_list: [false], output_name: ['intOutput'] - } + } as ComfyNodeDef const result = new ComfyNodeDefImpl(plainObject) @@ -201,7 +202,7 @@ describe('ComfyNodeDefImpl', () => { output_is_list: [false], output_name: ['intOutput'], deprecated: true - } + } as ComfyNodeDef const result = new ComfyNodeDefImpl(plainObject) expect(result.deprecated).toBe(true) @@ -224,7 +225,7 @@ describe('ComfyNodeDefImpl', () => { output: ['INT'], output_is_list: [false], output_name: ['intOutput'] - } + } as ComfyNodeDef const result = new ComfyNodeDefImpl(plainObject) expect(result.deprecated).toBe(true) @@ -393,7 +394,7 @@ describe('ComfyNodeDefImpl', () => { output: ['INT'], output_is_list: [false], output_name: ['result'] - } + } as ComfyNodeDef const result = new ComfyNodeDefImpl(plainObject) diff --git a/tests-ui/tests/nodeSearchService.test.ts b/tests-ui/tests/nodeSearchService.test.ts index 80f244c20b..09f670e1f2 100644 --- a/tests-ui/tests/nodeSearchService.test.ts +++ b/tests-ui/tests/nodeSearchService.test.ts @@ -1,59 +1,62 @@ // @ts-strict-ignore import { describe, expect, it } from 'vitest' +import { ComfyNodeDef } from '@/schemas/nodeDefSchema' import { NodeSearchService } from '@/services/nodeSearchService' import { ComfyNodeDefImpl } from '@/stores/nodeDefStore' -const EXAMPLE_NODE_DEFS: ComfyNodeDefImpl[] = [ - { - input: { - required: { - ckpt_name: [['model1.safetensors', 'model2.ckpt'], {}] - } +const EXAMPLE_NODE_DEFS: ComfyNodeDefImpl[] = ( + [ + { + input: { + required: { + ckpt_name: [['model1.safetensors', 'model2.ckpt'], {}] + } + }, + output: ['MODEL', 'CLIP', 'VAE'], + output_is_list: [false, false, false], + output_name: ['MODEL', 'CLIP', 'VAE'], + name: 'CheckpointLoaderSimple', + display_name: 'Load Checkpoint', + description: '', + python_module: 'nodes', + category: 'loaders', + output_node: false }, - output: ['MODEL', 'CLIP', 'VAE'], - output_is_list: [false, false, false], - output_name: ['MODEL', 'CLIP', 'VAE'], - name: 'CheckpointLoaderSimple', - display_name: 'Load Checkpoint', - description: '', - python_module: 'nodes', - category: 'loaders', - output_node: false - }, - { - input: { - required: { - samples: ['LATENT'], - batch_index: [ - 'INT', - { - default: 0, - min: 0, - max: 63 - } - ], - length: [ - 'INT', - { - default: 1, - min: 1, - max: 64 - } - ] - } - }, - output: ['LATENT'], - output_is_list: [false], - output_name: ['LATENT'], - name: 'LatentFromBatch', - display_name: 'Latent From Batch', - description: '', - python_module: 'nodes', - category: 'latent/batch', - output_node: false - } -].map((nodeDef) => { + { + input: { + required: { + samples: ['LATENT'], + batch_index: [ + 'INT', + { + default: 0, + min: 0, + max: 63 + } + ], + length: [ + 'INT', + { + default: 1, + min: 1, + max: 64 + } + ] + } + }, + output: ['LATENT'], + output_is_list: [false], + output_name: ['LATENT'], + name: 'LatentFromBatch', + display_name: 'Latent From Batch', + description: '', + python_module: 'nodes', + category: 'latent/batch', + output_node: false + } + ] as ComfyNodeDef[] +).map((nodeDef: ComfyNodeDef) => { const def = new ComfyNodeDefImpl(nodeDef) def['postProcessSearchScores'] = (s) => s return def