"use strict";
(() => {
  // node_modules/abort-error/dist/src/index.js
  var AbortError = class extends Error {
    static name = "AbortError";
    name = "AbortError";
    constructor(message = "The operation was aborted", ...rest) {
      super(message, ...rest);
    }
  };

  // node_modules/p-defer/index.js
  function pDefer() {
    const deferred = {};
    deferred.promise = new Promise((resolve, reject) => {
      deferred.resolve = resolve;
      deferred.reject = reject;
    });
    return deferred;
  }

  // node_modules/it-pushable/dist/src/fifo.js
  var FixedFIFO = class {
    buffer;
    mask;
    top;
    btm;
    next;
    constructor(hwm) {
      if (!(hwm > 0) || (hwm - 1 & hwm) !== 0) {
        throw new Error("Max size for a FixedFIFO should be a power of two");
      }
      this.buffer = new Array(hwm);
      this.mask = hwm - 1;
      this.top = 0;
      this.btm = 0;
      this.next = null;
    }
    push(data) {
      if (this.buffer[this.top] !== void 0) {
        return false;
      }
      this.buffer[this.top] = data;
      this.top = this.top + 1 & this.mask;
      return true;
    }
    shift() {
      const last = this.buffer[this.btm];
      if (last === void 0) {
        return void 0;
      }
      this.buffer[this.btm] = void 0;
      this.btm = this.btm + 1 & this.mask;
      return last;
    }
    isEmpty() {
      return this.buffer[this.btm] === void 0;
    }
  };
  var FIFO = class {
    size;
    hwm;
    head;
    tail;
    constructor(options = {}) {
      this.hwm = options.splitLimit ?? 16;
      this.head = new FixedFIFO(this.hwm);
      this.tail = this.head;
      this.size = 0;
    }
    calculateSize(obj) {
      if (obj?.byteLength != null) {
        return obj.byteLength;
      }
      return 1;
    }
    push(val) {
      if (val?.value != null) {
        this.size += this.calculateSize(val.value);
      }
      if (!this.head.push(val)) {
        const prev = this.head;
        this.head = prev.next = new FixedFIFO(2 * this.head.buffer.length);
        this.head.push(val);
      }
    }
    shift() {
      let val = this.tail.shift();
      if (val === void 0 && this.tail.next != null) {
        const next = this.tail.next;
        this.tail.next = null;
        this.tail = next;
        val = this.tail.shift();
      }
      if (val?.value != null) {
        this.size -= this.calculateSize(val.value);
      }
      return val;
    }
    isEmpty() {
      return this.head.isEmpty();
    }
  };

  // node_modules/it-pushable/dist/src/index.js
  var AbortError2 = class extends Error {
    type;
    code;
    constructor(message, code) {
      super(message ?? "The operation was aborted");
      this.type = "aborted";
      this.code = code ?? "ABORT_ERR";
    }
  };
  function pushable(options = {}) {
    const getNext = (buffer) => {
      const next = buffer.shift();
      if (next == null) {
        return { done: true };
      }
      if (next.error != null) {
        throw next.error;
      }
      return {
        done: next.done === true,
        // @ts-expect-error if done is false, value will be present
        value: next.value
      };
    };
    return _pushable(getNext, options);
  }
  function _pushable(getNext, options) {
    options = options ?? {};
    let onEnd = options.onEnd;
    let buffer = new FIFO();
    let pushable2;
    let onNext;
    let ended;
    let drain = pDefer();
    const waitNext = async () => {
      try {
        if (!buffer.isEmpty()) {
          return getNext(buffer);
        }
        if (ended) {
          return { done: true };
        }
        return await new Promise((resolve, reject) => {
          onNext = (next) => {
            onNext = null;
            buffer.push(next);
            try {
              resolve(getNext(buffer));
            } catch (err) {
              reject(err);
            }
            return pushable2;
          };
        });
      } finally {
        if (buffer.isEmpty()) {
          queueMicrotask(() => {
            drain.resolve();
            drain = pDefer();
          });
        }
      }
    };
    const bufferNext = (next) => {
      if (onNext != null) {
        return onNext(next);
      }
      buffer.push(next);
      return pushable2;
    };
    const bufferError = (err) => {
      buffer = new FIFO();
      if (onNext != null) {
        return onNext({ error: err });
      }
      buffer.push({ error: err });
      return pushable2;
    };
    const push = (value) => {
      if (ended) {
        return pushable2;
      }
      if (options?.objectMode !== true && value?.byteLength == null) {
        throw new Error("objectMode was not true but tried to push non-Uint8Array value");
      }
      return bufferNext({ done: false, value });
    };
    const end = (err) => {
      if (ended)
        return pushable2;
      ended = true;
      return err != null ? bufferError(err) : bufferNext({ done: true });
    };
    const _return = () => {
      buffer = new FIFO();
      end();
      return { done: true };
    };
    const _throw = (err) => {
      end(err);
      return { done: true };
    };
    pushable2 = {
      [Symbol.asyncIterator]() {
        return this;
      },
      next: waitNext,
      return: _return,
      throw: _throw,
      push,
      end,
      get readableLength() {
        return buffer.size;
      },
      onEmpty: async (options2) => {
        const signal = options2?.signal;
        signal?.throwIfAborted();
        if (buffer.isEmpty()) {
          return;
        }
        let cancel;
        let listener;
        if (signal != null) {
          cancel = new Promise((resolve, reject) => {
            listener = () => {
              reject(new AbortError2());
            };
            signal.addEventListener("abort", listener);
          });
        }
        try {
          await Promise.race([
            drain.promise,
            cancel
          ]);
        } finally {
          if (listener != null && signal != null) {
            signal?.removeEventListener("abort", listener);
          }
        }
      }
    };
    if (onEnd == null) {
      return pushable2;
    }
    const _pushable2 = pushable2;
    pushable2 = {
      [Symbol.asyncIterator]() {
        return this;
      },
      next() {
        return _pushable2.next();
      },
      throw(err) {
        _pushable2.throw(err);
        if (onEnd != null) {
          onEnd(err);
          onEnd = void 0;
        }
        return { done: true };
      },
      return() {
        _pushable2.return();
        if (onEnd != null) {
          onEnd();
          onEnd = void 0;
        }
        return { done: true };
      },
      push,
      end(err) {
        _pushable2.end(err);
        if (onEnd != null) {
          onEnd(err);
          onEnd = void 0;
        }
        return pushable2;
      },
      get readableLength() {
        return _pushable2.readableLength;
      },
      onEmpty: (opts) => {
        return _pushable2.onEmpty(opts);
      }
    };
    return pushable2;
  }

  // node_modules/main-event/dist/src/events.browser.js
  function setMaxListeners() {
  }

  // node_modules/main-event/dist/src/index.js
  var TypedEventEmitter = class extends EventTarget {
    #listeners = /* @__PURE__ */ new Map();
    constructor() {
      super();
      setMaxListeners(Infinity, this);
    }
    listenerCount(type) {
      const listeners = this.#listeners.get(type);
      if (listeners == null) {
        return 0;
      }
      return listeners.length;
    }
    addEventListener(type, listener, options) {
      super.addEventListener(type, listener, options);
      let list = this.#listeners.get(type);
      if (list == null) {
        list = [];
        this.#listeners.set(type, list);
      }
      list.push({
        callback: listener,
        once: (options !== true && options !== false && options?.once) ?? false
      });
    }
    removeEventListener(type, listener, options) {
      super.removeEventListener(type.toString(), listener ?? null, options);
      let list = this.#listeners.get(type);
      if (list == null) {
        return;
      }
      list = list.filter(({ callback }) => callback !== listener);
      this.#listeners.set(type, list);
    }
    dispatchEvent(event) {
      const result = super.dispatchEvent(event);
      let list = this.#listeners.get(event.type);
      if (list == null) {
        return result;
      }
      list = list.filter(({ once }) => !once);
      this.#listeners.set(event.type, list);
      return result;
    }
    safeDispatchEvent(type, detail = {}) {
      return this.dispatchEvent(new CustomEvent(type, detail));
    }
  };

  // node_modules/race-event/dist/src/index.js
  var AbortError3 = class extends Error {
    type;
    code;
    constructor(message, code) {
      super(message ?? "The operation was aborted");
      this.type = "aborted";
      this.name = "AbortError";
      this.code = code ?? "ABORT_ERR";
    }
  };
  async function raceEvent(emitter, eventName, signal, opts) {
    const error = new AbortError3(opts?.errorMessage, opts?.errorCode);
    if (signal?.aborted === true) {
      return Promise.reject(error);
    }
    return new Promise((resolve, reject) => {
      function removeListeners() {
        signal?.removeEventListener("abort", abortListener);
        emitter.removeEventListener(eventName, eventListener);
        if (opts?.errorEvent != null) {
          emitter.removeEventListener(opts.errorEvent, errorEventListener);
        }
      }
      const eventListener = (evt) => {
        try {
          if (opts?.filter?.(evt) === false) {
            return;
          }
        } catch (err) {
          removeListeners();
          reject(err);
          return;
        }
        removeListeners();
        resolve(evt);
      };
      const errorEventListener = (evt) => {
        removeListeners();
        reject(evt.detail);
      };
      const abortListener = () => {
        removeListeners();
        reject(error);
      };
      signal?.addEventListener("abort", abortListener);
      emitter.addEventListener(eventName, eventListener);
      if (opts?.errorEvent != null) {
        emitter.addEventListener(opts.errorEvent, errorEventListener);
      }
    });
  }

  // node_modules/it-queue/dist/src/errors.js
  var QueueFullError = class extends Error {
    static name = "QueueFullError";
    constructor(message = "The queue was full") {
      super(message);
      this.name = "QueueFullError";
    }
  };

  // node_modules/race-signal/dist/src/index.js
  var AbortError4 = class extends Error {
    type;
    code;
    constructor(message, code, name) {
      super(message ?? "The operation was aborted");
      this.type = "aborted";
      this.name = name ?? "AbortError";
      this.code = code ?? "ABORT_ERR";
    }
  };
  async function raceSignal(promise, signal, opts) {
    if (signal == null) {
      return promise;
    }
    if (signal.aborted) {
      promise.catch(() => {
      });
      return Promise.reject(new AbortError4(opts?.errorMessage, opts?.errorCode, opts?.errorName));
    }
    let listener;
    const error = new AbortError4(opts?.errorMessage, opts?.errorCode, opts?.errorName);
    try {
      return await Promise.race([
        promise,
        new Promise((resolve, reject) => {
          listener = () => {
            reject(error);
          };
          signal.addEventListener("abort", listener);
        })
      ]);
    } finally {
      if (listener != null) {
        signal.removeEventListener("abort", listener);
      }
    }
  }

  // node_modules/it-queue/dist/src/recipient.js
  var JobRecipient = class {
    deferred;
    signal;
    constructor(signal) {
      this.signal = signal;
      this.deferred = Promise.withResolvers();
      this.onAbort = this.onAbort.bind(this);
      this.signal?.addEventListener("abort", this.onAbort);
    }
    onAbort() {
      this.deferred.reject(this.signal?.reason ?? new AbortError());
    }
    cleanup() {
      this.signal?.removeEventListener("abort", this.onAbort);
    }
  };

  // node_modules/it-queue/dist/src/job.js
  function randomId() {
    return `${parseInt(String(Math.random() * 1e9), 10).toString()}${Date.now()}`;
  }
  var Job = class {
    id;
    fn;
    options;
    recipients;
    status;
    timeline;
    controller;
    constructor(fn, options) {
      this.id = randomId();
      this.status = "queued";
      this.fn = fn;
      this.options = options;
      this.recipients = [];
      this.timeline = {
        created: Date.now()
      };
      this.controller = new AbortController();
      setMaxListeners(Infinity, this.controller.signal);
      this.onAbort = this.onAbort.bind(this);
    }
    abort(err) {
      this.controller.abort(err);
    }
    onAbort() {
      const allAborted = this.recipients.reduce((acc, curr) => {
        return acc && curr.signal?.aborted === true;
      }, true);
      if (allAborted) {
        this.controller.abort(new AbortError());
        this.cleanup();
      }
    }
    async join(options = {}) {
      const recipient = new JobRecipient(options.signal);
      this.recipients.push(recipient);
      options.signal?.addEventListener("abort", this.onAbort);
      return recipient.deferred.promise;
    }
    async run() {
      this.status = "running";
      this.timeline.started = Date.now();
      try {
        this.controller.signal.throwIfAborted();
        const result = await raceSignal(this.fn({
          ...this.options ?? {},
          signal: this.controller.signal
        }), this.controller.signal);
        this.recipients.forEach((recipient) => {
          recipient.deferred.resolve(result);
        });
        this.status = "complete";
      } catch (err) {
        this.recipients.forEach((recipient) => {
          recipient.deferred.reject(err);
        });
        this.status = "errored";
      } finally {
        this.timeline.finished = Date.now();
        this.cleanup();
      }
    }
    cleanup() {
      this.recipients.forEach((recipient) => {
        recipient.cleanup();
        recipient.signal?.removeEventListener("abort", this.onAbort);
      });
    }
  };

  // node_modules/it-queue/dist/src/utils.js
  function debounce(func, wait) {
    let timeout;
    const output = function() {
      const later = function() {
        timeout = void 0;
        void func();
      };
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
    };
    output.start = () => {
    };
    output.stop = () => {
      clearTimeout(timeout);
    };
    return output;
  }

  // node_modules/it-queue/dist/src/index.js
  var Queue = class extends TypedEventEmitter {
    concurrency;
    maxSize;
    queue;
    pending;
    sort;
    autoStart;
    constructor(init = {}) {
      super();
      this.concurrency = init.concurrency ?? Number.POSITIVE_INFINITY;
      this.maxSize = init.maxSize ?? Number.POSITIVE_INFINITY;
      this.pending = 0;
      this.autoStart = init.autoStart ?? true;
      this.sort = init.sort;
      this.queue = [];
      this.emitEmpty = debounce(this.emitEmpty.bind(this), 1);
      this.emitIdle = debounce(this.emitIdle.bind(this), 1);
    }
    [Symbol.asyncIterator]() {
      return this.toGenerator();
    }
    emitEmpty() {
      if (this.size !== 0) {
        return;
      }
      this.safeDispatchEvent("empty");
    }
    emitIdle() {
      if (this.running !== 0) {
        return;
      }
      this.safeDispatchEvent("idle");
    }
    tryToStartAnother() {
      if (this.size === 0) {
        this.emitEmpty();
        if (this.running === 0) {
          this.emitIdle();
        }
        return false;
      }
      if (this.pending < this.concurrency) {
        let job;
        for (const j of this.queue) {
          if (j.status === "queued") {
            job = j;
            break;
          }
        }
        if (job == null) {
          return false;
        }
        this.safeDispatchEvent("active");
        this.pending++;
        void job.run().finally(() => {
          for (let i = 0; i < this.queue.length; i++) {
            if (this.queue[i] === job) {
              this.queue.splice(i, 1);
              break;
            }
          }
          this.pending--;
          this.safeDispatchEvent("next");
          if (this.autoStart) {
            this.tryToStartAnother();
          }
        });
        return true;
      }
      return false;
    }
    enqueue(job) {
      this.queue.push(job);
      if (this.sort != null) {
        this.queue.sort(this.sort);
      }
    }
    /**
     * Start the queue. If the `autoStart` parameter passed to the constructor was
     * not `false` this is a no-op
     */
    start() {
      if (this.autoStart !== false) {
        return;
      }
      this.autoStart = true;
      this.tryToStartAnother();
    }
    /**
     * Prevent further jobs from running - call `.start` to start the queue again
     */
    pause() {
      this.autoStart = false;
    }
    /**
     * Adds a sync or async task to the queue. Always returns a promise.
     */
    async add(fn, options) {
      options?.signal?.throwIfAborted();
      if (this.size === this.maxSize) {
        throw new QueueFullError();
      }
      const job = new Job(fn, options);
      this.enqueue(job);
      this.safeDispatchEvent("add");
      if (this.autoStart) {
        this.tryToStartAnother();
      }
      return job.join(options).then((result) => {
        this.safeDispatchEvent("success", { detail: { job, result } });
        return result;
      }).catch((err) => {
        if (job.status === "queued") {
          for (let i = 0; i < this.queue.length; i++) {
            if (this.queue[i] === job) {
              this.queue.splice(i, 1);
              break;
            }
          }
        }
        this.safeDispatchEvent("failure", { detail: { job, error: err } });
        throw err;
      });
    }
    /**
     * Clear the queue
     */
    clear() {
      this.queue.splice(0, this.queue.length);
    }
    /**
     * Abort all jobs in the queue and clear it
     */
    abort() {
      this.queue.forEach((job) => {
        job.abort(new AbortError());
      });
      this.clear();
    }
    /**
     * Can be called multiple times. Useful if you for example add additional items at a later time.
     *
     * @returns A promise that settles when the queue becomes empty.
     */
    async onEmpty(options) {
      if (this.size === 0) {
        return;
      }
      await raceEvent(this, "empty", options?.signal);
    }
    /**
     * @returns A promise that settles when the queue size is less than the given
     * limit: `queue.size < limit`.
     *
     * If you want to avoid having the queue grow beyond a certain size you can
     * `await queue.onSizeLessThan()` before adding a new item.
     *
     * Note that this only limits the number of items waiting to start. There
     * could still be up to `concurrency` jobs already running that this call does
     * not include in its calculation.
     */
    async onSizeLessThan(limit, options) {
      if (this.size < limit) {
        return;
      }
      await raceEvent(this, "next", options?.signal, {
        filter: () => this.size < limit
      });
    }
    /**
     * The difference with `.onEmpty` is that `.onIdle` guarantees that all work
     * from the queue has finished. `.onEmpty` merely signals that the queue is
     * empty, but it could mean that some promises haven't completed yet.
     *
     * @returns A promise that settles when the queue becomes empty, and all
     * promises have completed; `queue.size === 0 && queue.pending === 0`.
     */
    async onIdle(options) {
      if (this.pending === 0 && this.size === 0) {
        return;
      }
      await raceEvent(this, "idle", options?.signal);
    }
    /**
     * Size of the queue including running items
     */
    get size() {
      return this.queue.length;
    }
    /**
     * The number of queued items waiting to run.
     */
    get queued() {
      return this.queue.length - this.pending;
    }
    /**
     * The number of items currently running.
     */
    get running() {
      return this.pending;
    }
    /**
     * Returns an async generator that makes it easy to iterate over the results
     * of jobs added to the queue.
     *
     * The generator will end when the queue becomes idle, that is there are no
     * jobs running and no jobs that have yet to run.
     *
     * If you need to keep the queue open indefinitely, consider using it-pushable
     * instead.
     */
    async *toGenerator(options) {
      options?.signal?.throwIfAborted();
      const stream = pushable({
        objectMode: true
      });
      const cleanup = (err) => {
        if (err != null) {
          this.abort();
        } else {
          this.clear();
        }
        stream.end(err);
      };
      const onQueueJobComplete = (evt) => {
        if (evt.detail != null) {
          stream.push(evt.detail.result);
        }
      };
      const onQueueError = (evt) => {
        cleanup(evt.detail.error);
      };
      const onQueueIdle = () => {
        cleanup();
      };
      const onSignalAbort = () => {
        cleanup(new AbortError("Queue aborted"));
      };
      this.addEventListener("success", onQueueJobComplete);
      this.addEventListener("failure", onQueueError);
      this.addEventListener("idle", onQueueIdle);
      options?.signal?.addEventListener("abort", onSignalAbort);
      try {
        yield* stream;
      } finally {
        this.removeEventListener("success", onQueueJobComplete);
        this.removeEventListener("failure", onQueueError);
        this.removeEventListener("idle", onQueueIdle);
        options?.signal?.removeEventListener("abort", onSignalAbort);
        cleanup();
      }
    }
  };

  // src/constants.ts
  var WORKER_REQUEST_READ_LOCK = "lock:worker:request-read";
  var WORKER_ABORT_READ_LOCK_REQUEST = "lock:worker:abort-read-request";
  var WORKER_RELEASE_READ_LOCK = "lock:worker:release-read";
  var MASTER_GRANT_READ_LOCK = "lock:master:grant-read";
  var MASTER_READ_LOCK_ERROR = "lock:master:error-read";
  var WORKER_REQUEST_WRITE_LOCK = "lock:worker:request-write";
  var WORKER_ABORT_WRITE_LOCK_REQUEST = "lock:worker:abort-write-request";
  var WORKER_RELEASE_WRITE_LOCK = "lock:worker:release-write";
  var MASTER_GRANT_WRITE_LOCK = "lock:master:grant-write";
  var MASTER_WRITE_LOCK_ERROR = "lock:master:error-write";
  var WORKER_FINALIZE = "lock:worker:finalize";
  var BROADCAST_CHANNEL_NAME = "mortice";
  var defaultOptions = {
    singleProcess: false
  };

  // src/main/channel.ts
  var handleChannelWorkerLockRequest = (emitter, channel, masterEvent, abortMasterEvent, requestType, abortType, errorType, releaseType, grantType) => {
    return (event) => {
      if (event.data == null) {
        return;
      }
      const requestEvent = {
        type: event.data.type,
        name: event.data.name,
        identifier: event.data.identifier
      };
      if (requestEvent.type === requestType) {
        emitter.safeDispatchEvent(masterEvent, {
          detail: {
            name: requestEvent.name,
            identifier: requestEvent.identifier,
            handler: async () => {
              channel.postMessage({
                type: grantType,
                name: requestEvent.name,
                identifier: requestEvent.identifier
              });
              await new Promise((resolve) => {
                const releaseEventListener = (event2) => {
                  if (event2?.data == null) {
                    return;
                  }
                  const releaseEvent = {
                    type: event2.data.type,
                    name: event2.data.name,
                    identifier: event2.data.identifier
                  };
                  if (releaseEvent.type === releaseType && releaseEvent.identifier === requestEvent.identifier) {
                    channel.removeEventListener("message", releaseEventListener);
                    resolve();
                  }
                };
                channel.addEventListener("message", releaseEventListener);
              });
            },
            onError: (err) => {
              channel.postMessage({
                type: errorType,
                name: requestEvent.name,
                identifier: requestEvent.identifier,
                error: {
                  message: err.message,
                  name: err.name,
                  stack: err.stack
                }
              });
            }
          }
        });
      }
      if (requestEvent.type === abortType) {
        emitter.safeDispatchEvent(abortMasterEvent, {
          detail: {
            name: requestEvent.name,
            identifier: requestEvent.identifier
          }
        });
      }
      if (requestEvent.type === WORKER_FINALIZE) {
        emitter.safeDispatchEvent("finalizeRequest", {
          detail: {
            name: requestEvent.name
          }
        });
      }
    };
  };

  // src/utils.ts
  var nanoid = (size = 10) => {
    return Math.random().toString().substring(2, size + 2);
  };

  // src/workers/channel.ts
  var MorticeChannelWorker = class {
    name;
    channel;
    constructor(name) {
      this.name = name;
      this.channel = new BroadcastChannel(BROADCAST_CHANNEL_NAME);
    }
    readLock(options) {
      return this.sendRequest(
        WORKER_REQUEST_READ_LOCK,
        WORKER_ABORT_READ_LOCK_REQUEST,
        MASTER_GRANT_READ_LOCK,
        MASTER_READ_LOCK_ERROR,
        WORKER_RELEASE_READ_LOCK,
        options
      );
    }
    writeLock(options) {
      return this.sendRequest(
        WORKER_REQUEST_WRITE_LOCK,
        WORKER_ABORT_WRITE_LOCK_REQUEST,
        MASTER_GRANT_WRITE_LOCK,
        MASTER_WRITE_LOCK_ERROR,
        WORKER_RELEASE_WRITE_LOCK,
        options
      );
    }
    finalize() {
      this.channel.postMessage({
        type: WORKER_FINALIZE,
        name: this.name
      });
      this.channel.close();
    }
    async sendRequest(requestType, abortType, grantType, errorType, releaseType, options) {
      options?.signal?.throwIfAborted();
      const id = nanoid();
      this.channel.postMessage({
        type: requestType,
        identifier: id,
        name: this.name
      });
      return new Promise((resolve, reject) => {
        const abortListener = () => {
          this.channel.postMessage({
            type: abortType,
            identifier: id,
            name: this.name
          });
        };
        options?.signal?.addEventListener("abort", abortListener, {
          once: true
        });
        const listener = (event) => {
          if (event.data?.identifier !== id) {
            return;
          }
          if (event.data?.type === grantType) {
            this.channel.removeEventListener("message", listener);
            options?.signal?.removeEventListener("abort", abortListener);
            resolve(() => {
              this.channel.postMessage({
                type: releaseType,
                identifier: id,
                name: this.name
              });
            });
          }
          if (event.data.type === errorType) {
            this.channel.removeEventListener("message", listener);
            options?.signal?.removeEventListener("abort", abortListener);
            const err = new Error();
            if (event.data.error != null) {
              err.message = event.data.error.message;
              err.name = event.data.error.name;
              err.stack = event.data.error.stack;
            }
            reject(err);
          }
        };
        this.channel.addEventListener("message", listener);
      });
    }
  };

  // src/browser.ts
  var browser_default = (options) => {
    options = Object.assign({}, defaultOptions, options);
    const isPrimary = Boolean(globalThis.document) || options.singleProcess;
    if (isPrimary) {
      const channel = new BroadcastChannel(BROADCAST_CHANNEL_NAME);
      const emitter = new TypedEventEmitter();
      channel.addEventListener("message", handleChannelWorkerLockRequest(
        emitter,
        channel,
        "requestReadLock",
        "abortReadLockRequest",
        WORKER_REQUEST_READ_LOCK,
        WORKER_ABORT_READ_LOCK_REQUEST,
        MASTER_READ_LOCK_ERROR,
        WORKER_RELEASE_READ_LOCK,
        MASTER_GRANT_READ_LOCK
      ));
      channel.addEventListener("message", handleChannelWorkerLockRequest(
        emitter,
        channel,
        "requestWriteLock",
        "abortWriteLockRequest",
        WORKER_REQUEST_WRITE_LOCK,
        WORKER_ABORT_WRITE_LOCK_REQUEST,
        MASTER_WRITE_LOCK_ERROR,
        WORKER_RELEASE_WRITE_LOCK,
        MASTER_GRANT_WRITE_LOCK
      ));
      return emitter;
    }
    return new MorticeChannelWorker(options.name);
  };

  // src/mortice.ts
  var mutexes = /* @__PURE__ */ new Map();
  var implementation;
  function isMortice(obj) {
    return typeof obj?.readLock === "function" && typeof obj?.writeLock === "function";
  }
  function getImplementation(opts) {
    if (implementation == null) {
      implementation = browser_default(opts);
      if (!isMortice(implementation)) {
        const emitter = implementation;
        emitter.addEventListener("requestReadLock", (event) => {
          const mutexName = event.detail.name;
          const identifier = event.detail.identifier;
          const mutex = mutexes.get(mutexName);
          if (mutex == null) {
            return;
          }
          const abortController = new AbortController();
          const abortListener = (event2) => {
            if (event2.detail.name !== mutexName || event2.detail.identifier !== identifier) {
              return;
            }
            abortController.abort();
          };
          emitter.addEventListener("abortReadLockRequest", abortListener);
          void mutex.readLock({
            signal: abortController.signal
          }).then(async (release) => {
            await event.detail.handler().finally(() => {
              release();
            });
          }).catch((err) => {
            event.detail.onError(err);
          }).finally(() => {
            emitter.removeEventListener("abortReadLockRequest", abortListener);
          });
        });
        emitter.addEventListener("requestWriteLock", (event) => {
          const mutexName = event.detail.name;
          const identifier = event.detail.identifier;
          const mutex = mutexes.get(mutexName);
          if (mutex == null) {
            return;
          }
          const abortController = new AbortController();
          const abortListener = (event2) => {
            if (event2.detail.name !== mutexName || event2.detail.identifier !== identifier) {
              return;
            }
            abortController.abort();
          };
          emitter.addEventListener("abortWriteLockRequest", abortListener);
          void mutex.writeLock({
            signal: abortController.signal
          }).then(async (release) => {
            await event.detail.handler().finally(() => {
              release();
            });
          }).catch((err) => {
            event.detail.onError(err);
          }).finally(() => {
            emitter.removeEventListener("abortWriteLockRequest", abortListener);
          });
        });
        emitter.addEventListener("finalizeRequest", (event) => {
          const mutexName = event.detail.name;
          const mutex = mutexes.get(mutexName);
          if (mutex == null) {
            return;
          }
          mutex.finalize();
        });
      }
    }
    return implementation;
  }
  async function createReleasable(queue, options) {
    let res;
    let rej;
    const p = new Promise((resolve, reject) => {
      res = resolve;
      rej = reject;
    });
    const listener = () => {
      rej(new AbortError());
    };
    options?.signal?.addEventListener("abort", listener, {
      once: true
    });
    queue.add(async () => {
      await new Promise((resolve) => {
        res(() => {
          options?.signal?.removeEventListener("abort", listener);
          resolve();
        });
      });
    }, {
      signal: options?.signal
    }).catch((err) => {
      rej(err);
    });
    return p;
  }
  var createMutex = (name, options) => {
    let mutex = mutexes.get(name);
    if (mutex != null) {
      return mutex;
    }
    const implementation2 = getImplementation(options);
    if (isMortice(implementation2)) {
      mutex = implementation2;
      mutexes.set(name, mutex);
      return mutex;
    }
    const masterQueue = new Queue({
      concurrency: 1
    });
    let readQueue;
    mutex = {
      async readLock(opts) {
        if (readQueue != null) {
          return createReleasable(readQueue, opts);
        }
        readQueue = new Queue({
          concurrency: options.concurrency,
          autoStart: false
        });
        const localReadQueue = readQueue;
        const readPromise = createReleasable(readQueue, opts);
        void masterQueue.add(async () => {
          localReadQueue.start();
          await localReadQueue.onIdle().then(() => {
            if (readQueue === localReadQueue) {
              readQueue = null;
            }
          });
        });
        return readPromise;
      },
      async writeLock(opts) {
        readQueue = null;
        return createReleasable(masterQueue, opts);
      },
      finalize: () => {
        mutexes.delete(name);
      },
      queue: masterQueue
    };
    mutexes.set(name, mutex);
    if (options.autoFinalize === true) {
      masterQueue.addEventListener("idle", () => {
        mutex.finalize();
      }, {
        once: true
      });
    }
    return mutex;
  };

  // src/index.ts
  var defaultOptions2 = {
    name: "lock",
    concurrency: Infinity,
    singleProcess: false,
    autoFinalize: false
  };
  function createMortice(options) {
    const opts = Object.assign({}, defaultOptions2, options);
    return createMutex(opts.name, opts);
  }

  // node_modules/delay/index.js
  var createAbortError = () => {
    const error = new Error("Delay aborted");
    error.name = "AbortError";
    return error;
  };
  var clearMethods = /* @__PURE__ */ new WeakMap();
  function createDelay({ clearTimeout: defaultClear, setTimeout: defaultSet } = {}) {
    return (milliseconds, { value, signal } = {}) => {
      if (signal?.aborted) {
        return Promise.reject(createAbortError());
      }
      let timeoutId;
      let settle;
      let rejectFunction;
      const clear = defaultClear ?? clearTimeout;
      const signalListener = () => {
        clear(timeoutId);
        rejectFunction(createAbortError());
      };
      const cleanup = () => {
        if (signal) {
          signal.removeEventListener("abort", signalListener);
        }
      };
      const delayPromise = new Promise((resolve, reject) => {
        settle = () => {
          cleanup();
          resolve(value);
        };
        rejectFunction = reject;
        timeoutId = (defaultSet ?? setTimeout)(settle, milliseconds);
      });
      if (signal) {
        signal.addEventListener("abort", signalListener, { once: true });
      }
      clearMethods.set(delayPromise, () => {
        clear(timeoutId);
        timeoutId = null;
        settle();
      });
      return delayPromise;
    };
  }
  var delay = createDelay();
  var delay_default = delay;

  // test/fixtures/lock.ts
  async function lock(type, mutex, counts, result, options = {}) {
    counts[type]++;
    const index = counts[type];
    result.push(`${type} ${index} waiting`);
    try {
      const release = await mutex[`${type}Lock`](options);
      result.push(`${type} ${index} start`);
      if (options.timeout != null && options.timeout > 0) {
        result.push(`${type} ${index} delay ${options.timeout}ms`);
        await delay_default(options.timeout);
      }
      result.push(`${type} ${index} complete`);
      release();
      if (options.finalize === true) {
        mutex.finalize();
        result.push(`${type} ${index} finalize`);
        await delay_default(10);
      }
    } catch (err) {
      result.push(`${type} ${index} error ${err.message}`);
    }
  }

  // test/fixtures/worker-post-message.browser.ts
  function postMessage(val) {
    globalThis.postMessage(val);
  }

  // test/fixtures/worker-single-thread.ts
  async function run() {
    const mutex = createMortice({
      singleProcess: true
    });
    const counts = {
      read: 0,
      write: 0
    };
    const result = [];
    void lock("write", mutex, counts, result);
    void lock("read", mutex, counts, result);
    void lock("read", mutex, counts, result);
    void lock("read", mutex, counts, result, {
      timeout: 500
    });
    void lock("write", mutex, counts, result);
    await lock("read", mutex, counts, result);
    return result;
  }
  run().then((result = []) => {
    postMessage({
      type: "done",
      result
    });
  }, (err) => {
    postMessage({
      type: "error",
      error: {
        message: err.message,
        stack: err.stack
      }
    });
  });
})();
