{"record":{"id":"2b894b48cdc85714","repo":"heygen-com/hyperframes","slug":"node-i-has-unknown-effect-type-string-node-t","errorCode":null,"errorMessage":"Node ${i} has unknown effect type: ${String(node.type)}","messagePattern":"Node (.+?) has unknown effect type: (.+?)","errorType":"validation","errorClass":"AudioFxChainError","httpStatus":null,"severity":"error","filePath":"packages/core/src/audioFx.ts","lineNumber":783,"sourceCode":"    throw new AudioFxChainError(`Chain file is not valid JSON: ${(err as Error).message}`);\n  }\n  if (typeof raw !== \"object\" || raw === null) {\n    throw new AudioFxChainError(\"Chain file must be a JSON object.\");\n  }\n  const obj = raw as { version?: unknown; nodes?: unknown };\n  if (obj.version !== HF_AUDIO_FX_CHAIN_VERSION) {\n    throw new AudioFxChainError(`Unsupported chain version: ${String(obj.version)}`);\n  }\n  if (!Array.isArray(obj.nodes)) {\n    throw new AudioFxChainError(\"Chain file is missing a `nodes` array.\");\n  }\n  const nodes: HfAudioFxNode[] = obj.nodes.map((n, i) => {\n    if (typeof n !== \"object\" || n === null) {\n      throw new AudioFxChainError(`Node ${i} is not an object.`);\n    }\n    const node = n as { type?: unknown; enabled?: unknown; params?: unknown };\n    if (typeof node.type !== \"string\" || !BY_ID.has(node.type)) {\n      throw new AudioFxChainError(`Node ${i} has unknown effect type: ${String(node.type)}`);\n    }\n    return {\n      type: node.type,\n      enabled: node.enabled !== false,\n      params: normalizeAudioFxParams(\n        node.type,\n        (node.params ?? undefined) as HfAudioFxParamValues | undefined,\n      ),\n    };\n  });\n  return { version: HF_AUDIO_FX_CHAIN_VERSION, nodes };\n}\n\n/** The nodes that should process audio, in order. */\nexport function enabledAudioFxNodes(chain: HfAudioFxChain): HfAudioFxNode[] {\n  return chain.nodes.filter((n) => n.enabled !== false);\n}\n","sourceCodeStart":765,"sourceCodeEnd":801,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/core/src/audioFx.ts#L765-L801","documentation":"For each node, parseAudioFxChain() requires node.type to be a string AND a key present in BY_ID (the HF_AUDIO_FX registry). Unknown ids are rejected rather than silently dropped, because a chain that loses a node would render differently from the project the author saved — failing loudly is safer than a wrong render. The offending index and type are interpolated.","triggerScenarios":"A chain file referencing an effect id that doesn't exist: a typo ('higpass'), a removed id, an id from a newer HyperFrames version, or a node whose type is a number/boolean/null. Note this is stricter than buildFxNode's runtime check — it fires at load time.","commonSituations":"Hand-authored chain with a misspelled effect; version skew (newer studio wrote an id the older renderer doesn't know); a node missing the type field entirely.","solutions":["Use only ids present in HF_AUDIO_FX_IDS; cross-check against packages/core/src/audioFx.ts HF_AUDIO_FX.","Upgrade the renderer to a version that includes the effect, or substitute a supported effect.","Regenerate via serializeAudioFxChain so ids come straight from the registry."],"exampleFix":"// before\nparseAudioFxChain('{ \"version\": 1, \"nodes\": [ {\"type\":\"compressor\"} ] }'); // unknown -> 'worklet-compressor' is the real id\n\n// after\nimport { HF_AUDIO_FX_IDS } from \"@hyperframes/core\";\nconst safeType = HF_AUDIO_FX_IDS.includes(node.type) ? node.type : \"worklet-compressor\";\nparseAudioFxChain(JSON.stringify({ version: 1, nodes: [{ type: safeType }] }));","handlingStrategy":"type-guard","validationCode":"import { HF_AUDIO_FX_IDS } from \"@hyperframes/core\";\nconst known = new Set(HF_AUDIO_FX_IDS);\nfor (const n of nodes) if (typeof n.type !== \"string\" || !known.has(n.type)) throw new Error(`unknown effect type ${n.type}`);","typeGuard":"import { HF_AUDIO_FX_IDS } from \"@hyperframes/core\";\nconst FX = new Set(HF_AUDIO_FX_IDS);\nfunction isKnownFxNode(n: unknown): n is { type: string; params?: unknown } {\n  return typeof n === \"object\" && n !== null && typeof (n as any).type === \"string\" && FX.has((n as any).type);\n}","tryCatchPattern":"try { parseAudioFxChain(json); }\ncatch (err) { if (/unknown effect type/.test(String(err))) { /* map to a supported id */ } else throw err; }","preventionTips":["Pick effect ids only from HF_AUDIO_FX_IDS.","Run parseAudioFxChain at load time to surface unknown ids early.","After upgrades, re-check the canonical HF_AUDIO_FX list for renames."],"tags":["audio","effects","parsing","audio-fx-chain","validation","registry"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}