import { useContext } from "react"; import { REACT_AUDIO_CODES, ReactAudioError, } from "../error-codes/error-codes.js"; import { type AudioContextValue, AudioReactContext, type ProviderState, } from "../provider/audio-context.js"; /** * Internal helper: returns the resolved `ProviderState`, throwing * `REACT_AUDIO.NO_PROVIDER` if rendered outside `` * and `REACT_AUDIO.PROVIDER_PENDING` if the engine has not yet * finished its async init. * * Implementation note: `useContext` is the only React hook called by * this helper, so any downstream hook that uses it can register its * own `useCallback`/`useMemo`/`useSyncExternalStore` hooks * unconditionally without violating the Rules of Hooks. The throws * happen *after* the context read, so the hook count is the same * across the pending → ready transition. */ export function useProviderStateOrThrow(): ProviderState { const ctx = useContext(AudioReactContext); if (ctx === null) { throw new ReactAudioError( REACT_AUDIO_CODES.NO_PROVIDER, "react-audio.no-provider" ); } if (ctx.state === null) { throw new ReactAudioError( REACT_AUDIO_CODES.PROVIDER_PENDING, "react-audio.provider-pending", { context: { engineState: ctx.engineState } } ); } return ctx.state; } /** * Internal helper: returns the raw context value (without throwing * on pending), throwing `REACT_AUDIO.NO_PROVIDER` if the consumer * is outside ``. Used by `useEngineState`. */ export function useAudioContextOrThrow(): AudioContextValue { const ctx = useContext(AudioReactContext); if (ctx === null) { throw new ReactAudioError( REACT_AUDIO_CODES.NO_PROVIDER, "react-audio.no-provider" ); } return ctx; }