Nice insight. You might simplify the expression by just checking for the head (since functions are truthy):
const compose = (head, ...rest) => x => head ? head(compose(...rest)(x)) : x
Same for pipe/flow:
const flow = (head, ...rest) => x => head ? flow(...rest)(head(x)) : x
x if no functions are given to compose/flow, which reads nicely: “Apply the value to the next function in the list until there’s no next”.
This is also safe when you call compose with no arguments (but I personally don’t like this kind of “safety”).