JSValue toJS(ExecState& state, JSGlobalObject& globalObject, IDBKey* key)
{
    if (!key) {
        // This must be undefined, not null.
        // Spec: http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#idl-def-IDBKeyRange
        return jsUndefined();
    }

    VM& vm = state.vm();
    Locker<JSLock> locker(vm.apiLock());
    auto scope = DECLARE_THROW_SCOPE(vm);

    switch (key->type()) {
    case IndexedDB::KeyType::Array: {
        auto& inArray = key->array();
        unsigned size = inArray.size();
        auto outArray = constructEmptyArray(&state, 0, &globalObject, size);
        RETURN_IF_EXCEPTION(scope, JSValue());
        for (size_t i = 0; i < size; ++i) {
            outArray->putDirectIndex(&state, i, toJS(state, globalObject, inArray.at(i).get()));
            RETURN_IF_EXCEPTION(scope, JSValue());
        }
        return outArray;
    }
    case IndexedDB::KeyType::Binary: {
        auto* data = key->binary().data();
        if (!data) {
            ASSERT_NOT_REACHED();
            return jsNull();
        }

        auto arrayBuffer = ArrayBuffer::create(data->data(), data->size());
        Structure* structure = globalObject.arrayBufferStructure(arrayBuffer->sharingMode());
        if (!structure)
            return jsNull();

        return JSArrayBuffer::create(state.vm(), structure, WTFMove(arrayBuffer));
    }
    case IndexedDB::KeyType::String:
        return jsStringWithCache(&state, key->string());
    case IndexedDB::KeyType::Date:
        // FIXME: This should probably be toJS<IDLDate>(...) as per:
        // http://w3c.github.io/IndexedDB/#request-convert-a-key-to-a-value
        return toJS<IDLNullable<IDLDate>>(state, key->date());
    case IndexedDB::KeyType::Number:
        return jsNumber(key->number());
    case IndexedDB::KeyType::Min:
    case IndexedDB::KeyType::Max:
    case IndexedDB::KeyType::Invalid:
        ASSERT_NOT_REACHED();
        return jsUndefined();
    }

    ASSERT_NOT_REACHED();
    return jsUndefined();
}