static char *read_from_handle_all(JSContext *cx, HandleType handle, size_t *nwritten,
                                  bool read_until_zero) {
  // TODO(performance): investigate passing a size hint in situations where we might know
  // the final size, e.g. via the `content-length` header.
  // https://github.com/fastly/js-compute-runtime/issues/216
  size_t buf_size = HANDLE_READ_CHUNK_SIZE;
  // TODO(performance): make use of malloc slack.
  // https://github.com/fastly/js-compute-runtime/issues/217
  char *buf = static_cast<char *>(JS_malloc(cx, buf_size));
  if (!buf) {
    JS_ReportOutOfMemory(cx);
    return nullptr;
  }

  // For realloc below.
  char *new_buf;

  size_t offset = 0;
  while (true) {
    size_t num_written = 0;
    int result = op(handle, buf + offset, HANDLE_READ_CHUNK_SIZE, &num_written);
    if (!HANDLE_RESULT(cx, result)) {
      JS_free(cx, buf);
      return nullptr;
    }

    offset += num_written;
    if (num_written == 0 || (!read_until_zero && num_written < HANDLE_READ_CHUNK_SIZE)) {
      break;
    }

    // TODO(performance): make use of malloc slack, and use a smarter buffer growth strategy.
    // https://github.com/fastly/js-compute-runtime/issues/217
    size_t new_size = buf_size + HANDLE_READ_CHUNK_SIZE;
    new_buf = static_cast<char *>(JS_realloc(cx, buf, buf_size, new_size));
    if (!new_buf) {
      JS_free(cx, buf);
      JS_ReportOutOfMemory(cx);
      return nullptr;
    }
    buf = new_buf;

    buf_size += HANDLE_READ_CHUNK_SIZE;
  }

  new_buf = static_cast<char *>(JS_realloc(cx, buf, buf_size, offset + 1));
  if (!buf) {
    JS_free(cx, buf);
    JS_ReportOutOfMemory(cx);
    return nullptr;
  }
  buf = new_buf;

  buf[offset] = '\0';
  *nwritten = offset;

  return buf;
}
static char *read_from_handle_all(JSContext *cx, HandleType handle, size_t *nwritten,
                                  bool read_until_zero) {
   
   
   
  size_t buf_size = HANDLE_READ_CHUNK_SIZE;
   
   
  char *buf = static_cast<char *>(JS_malloc(cx, buf_size));
  if (!buf) {
    JS_ReportOutOfMemory(cx);
    return nullptr;
  }

   
  char *new_buf;

  size_t offset = 0;
  while (true) {
    size_t num_written = 0;
    int result = op(handle, buf + offset, HANDLE_READ_CHUNK_SIZE, &num_written);
    if (!HANDLE_RESULT(cx, result)) {
      JS_free(cx, buf);
      return nullptr;
    }

    offset += num_written;
    if (num_written == 0 || (!read_until_zero && num_written < HANDLE_READ_CHUNK_SIZE)) {
      break;
    }

     
     
    size_t new_size = buf_size + HANDLE_READ_CHUNK_SIZE;
    new_buf = static_cast<char *>(JS_realloc(cx, buf, buf_size, new_size));
    if (!new_buf) {
      JS_free(cx, buf);
      JS_ReportOutOfMemory(cx);
      return nullptr;
    }
    buf = new_buf;

    buf_size += HANDLE_READ_CHUNK_SIZE;
  }

  new_buf = static_cast<char *>(JS_realloc(cx, buf, buf_size, offset + 1));
  if (!buf) {
    JS_free(cx, buf);
    JS_ReportOutOfMemory(cx);
    return nullptr;
  }
  buf = new_buf;

  buf[offset] = '\0';
  *nwritten = offset;

  return buf;
}