ionary(options, 'options');
    let {
      type = '',
      endings = 'transparent',
    } = options ?? kEmptyObject;

    endings = `${endings}`;
    if (endings !== 'transparent' && endings !== 'native')
      throw new ERR_INVALID_ARG_VALUE('options.endings', endings);

    let length = 0;
    const sources_ = ArrayFrom(sources, (source) => {
      const { 0: len, 1: src } = getSource(source, endings);
      length += len;
      return src;
    });

    if (!isUint32(length))
      throw new ERR_BUFFER_TOO_LARGE(0xFFFFFFFF);

    this[kHandle] = _createBlob(sources_, length);
    this[kLength] = length;

    type = `${type}`;
    this[kType] = RegExpPrototypeExec(disallowedTypeCharacters, type) !== null ?
      '' : StringPrototypeToLowerCase(type);

    // eslint-disable-next-line no-constructor-return
    return makeTransferable(this);
  }

  [kInspect](depth, options) {
    if (depth < 0)
      return this;

    const opts = {
      ...options,
      depth: options.depth == null ? null : options.depth - 1,
    };

    return `Blob ${inspect({
      size: this.size,
      type: this.type,
    }, opts)}`;
  }

  [kClone]() {
    const handle = this[kHandle];
    const type = this[kType];
    const length = this[kLength];
    return {
      data: { handle, type, length },
      deserializeInfo: 'internal/blob:ClonedBlob',
    };
  }

  [kDeserialize]({ handle, type, length }) {
    this[kHandle] = handle;
    this[kType] = type;
    this[kLength] = length;
  }

  /**
   * @readonly
   * @type {string}
   */
  get type() {
    if (!isBlob(this))
      throw new ERR_INVALID_THIS('Blob');
    return this[kType];
  }

  /**
   * @readonly
   * @type {number}
   */
  get size() {
    if (!isBlob(this))
      throw new ERR_INVALID_THIS('Blob');
    return this[kLength];
  }

  /**
   * @param {number} [start]
   * @param {number} [end]
   * @param {string} [contentType]
   * @returns {Blob}
   */
  slice(start = 0, end = this[kLength], contentType = '') {
    if (!isBlob(this))
      throw new ERR_INVALID_THIS('Blob');
    if (start < 0) {
      start = MathMax(this[kLength] + start, 0);
    } else {
      start = MathMin(start, this[kLength]);
    }
    start |= 0;

    if (end < 0) {
      end = MathMax(this[kLength] + end, 0);
    } else {
      end = MathMin(end, this[kLength]);
    }
    end |= 0;

    contentType = `${contentType}`;
    if (RegExpPrototypeExec(disallowedTypeCharacters, contentType) !== null) {
      contentType = '';
    } else {
      contentType = StringPrototypeToLowerCase(contentType);
    }

    const span = MathMax(end - start, 0);

    return createBlob(
      this[kHandle].slice(start, start + span),
      span,
      contentType);
  }

  /**
   * @returns {Promise<ArrayBuffer>}
   */
  arrayBuffer() {
    if (!isBlob(this))
      return PromiseReject(new ERR_INVALID_THIS('Blob'));

    // If there's already a promise in flight for the content,
    // reuse it, but only while it's in flight. After the cached
    // promise resolves it will be cleared, allowing it to be
    // garbage collected as soon as possible.
    if (this[kArrayBufferPromise])
      return this[kArrayBufferPromise];

    const job = new FixedSizeBlobCopyJob(this[kHandle]);

    const ret = job.run();

    // If the job returns a value immediately, the ArrayBuffer
    // was generated synchronously and should just be returned
    // directly.
    if (ret !== undefined)
      return PromiseResolve(ret);

    const {
      promise,
      resolve,
      reject,
    } = createDeferredPromise();

    job.ondone = (err, ab) => {
      if (err !== undefined)
        return reject(new AbortError(undefined, { cause: err }));
      resolve(ab);
    };
    this[kArrayBufferPromise] =
    SafePromisePrototypeFinally(
      promise,
      () => this[kArrayBufferPromise] = undefined);

    return this[kArrayBufferPromise];
  }

  /**
   * @returns {Promise<string>}
   */
  async text() {
    if (!isBlob(this))
      throw new ERR_INVALID_THIS('Blob');

    dec ??= new TextDecoder();

    return dec.decode(await this.arrayBuffer());
  }

  /**
   * @returns {ReadableStream}
   */
  stream() {
    if (!isBlob(this))
      throw new ERR_INVALID_THIS('Blob');

    const self = this;
    return new lazyReadableStream({
      async start() {
        this[kState] = await self.arrayBuffer();
        this[kIndex] = 0;
      },

      pull(controller) {
        if (this[kState].byteLength - this[kIndex] <= kMaxChunkSize) {
          controller.enqueue(new Uint8Array(this[kState], this[kIndex]));
          controller.close();
          this[kState] = undefined;
        } else {
          controller.enqueue(new Uint8Array(this[kState], this[kIndex], kMaxChunkSize));
          this[kIndex] += kMaxChunkSize;
        }
      },
    });
  }
}

function ClonedBlob() {
  return makeTransferable(ReflectConstruct(function() {}, [], Blob));
}
ClonedBlob.prototype[kDeserialize] = () => {};

function createBlob(handle, length, type = '') {
  return makeTransferable(ReflectConstruct(function() {
    this[kHandle] = handle;
    this[kType] = type;
    this[kLength] = length;
  }, [], Blob));
}

ObjectDefineProperty(Blob.prototype, SymbolToStringTag, {
  __proto__: null,
  configurable: true,
  value: 'Blob',
});

ObjectDefineProperties(Blob.prototype, {
  size: kEnumerableProperty,
  type: kEnumerableProperty,
  slice: kEnumerableProperty,
  stream: kEnumerableProperty,
  text: kEnumerableProperty,
  arrayBuffer: kEnumerableProperty,
});

function resolveObjectURL(url) {
  url = `${url}`;
  try {
    const parsed = new lazyURL(url);

    const split = StringPrototypeSplit(parsed.pathname, ':');

    if (split.length !== 2)
      return;

    const {
      0: base,
      1: id,
    } = split;

    if (base !== 'nodedata')
      return;

    const ret = getDataObject(id);

    if (ret === undefined)
      return;

    const {
      0: handle,
      1: length,
      2: type,
    } = ret;

    if (handle !== undefined)
      return createBlob(handle, length, type);
  } catch {
    // If there's an error, it's ignored and nothing is returned
  }
}

module.exports = {
  Blob,
  ClonedBlob,
  createBlob,
  isBlob,
  kHandle,
  resolveObjectURL,
};
