repo_description: Node.js JavaScript runtime ✨🐢🚀✨
repo_topics: javascript js linux macos mit node nodejs runtime windows
code: 'use strict';
  
  const {
    ObjectDefineProperties,
    Promise,
    PromisePrototypeThen,
    PromiseResolve,
    SafePromisePrototypeFinally,
    Symbol,
    SymbolToStringTag,
  } = primordials;
  
  const {
    ERR_ILLEGAL_CONSTRUCTOR,
    ERR_INVALID_THIS,
  } = require('internal/errors');
  const {
    kEmptyObject,
    lazyDOMException,
    kEnumerableProperty,
  } = require('internal/util');
  const {
    validateAbortSignal,
    validateFunction,
  } = require('internal/validators');
  const { threadId } = require('internal/worker');
  const {
    converters,
    createEnumConverter,
    createDictionaryConverter,
  } = require('internal/webidl');
  const dc = require('diagnostics_channel');
  
  const locks = internalBinding('locks');
  const lockRequestStartChannel = dc.channel('locks.request.start');
  const lockRequestGrantChannel = dc.channel('locks.request.grant');
  const lockRequestMissChannel = dc.channel('locks.request.miss');
  const lockRequestEndChannel = dc.channel('locks.request.end');
  
  const kName = Symbol('kName');
  const kMode = Symbol('kMode');
  const kConstructLock = Symbol('kConstructLock');
  const kConstructLockManager = Symbol('kConstructLockManager');
  
  // WebIDL dictionary LockOptions
  const convertLockOptions = createDictionaryConverter([
    {
      key: 'mode',
      converter: createEnumConverter('LockMode', [
        'shared',
        'exclusive',
      ]),
      defaultValue: () => 'exclusive',
    },
    {
      key: 'ifAvailable',
      converter: (value) => !!value,
      defaultValue: () => false,
    },
    {
      key: 'steal',
      converter: (value) => !!value,
      defaultValue: () => false,
    },
    {
      key: 'signal',
      converter: converters.object,
    },
  ]);
  
  // https://w3c.github.io/web-locks/#api-lock
  class Lock {
    constructor(symbol = undefined, name, mode) {
      if (symbol !== kConstructLock) {
        throw new ERR_ILLEGAL_CONSTRUCTOR();
      }
      this[kName] = name;
      this[kMode] = mode;
    }
  
    get name() {
      if (this instanceof Lock) {
        return this[kName];
      }
      throw new ERR_INVALID_THIS('Lock');
    }
  
    get mode() {
      if (this instanceof Lock) {
        return this[kMode];
      }
      throw new ERR_INVALID_THIS('Lock');
    }
  }
  
  ObjectDefineProperties(Lock.prototype, {
    name: kEnumerableProperty,
    mode: kEnumerableProperty,
    [SymbolToStringTag]: {
      __proto__: null,
      value: 'Lock',
      writable: false,
      enumerable: false,
      configurable: true,
    },
  });
  
  // Helper to create Lock objects from internal C++ lock data
  function createLock(internalLock) {
    return internalLock === null ? null : new Lock(kConstructLock, internalLock.name, internalLock.mode);
  }
  
  // Convert LOCK_STOLEN_ERROR to AbortError DOMException
  function convertLockError(error) {
    if (error?.message === locks.LOCK_STOLEN_ERROR) {
      return lazyDOMException('The operation was aborted', 'AbortError');
    }
    return error;
  }
  
  function publishLockRequestStart(name, mode) {
    if (lockRequestStartChannel.hasSubscribers) {
      lockRequestStartChannel.publish({ name, mode });
    }
  }
  
  function publishLockRequestGrant(name, mode) {
    if (lockRequestGrantChannel.hasSubscribers) {
      lockRequestGrantChannel.publish({ name, mode });
    }
  }
  
  function publishLockRequestMiss(name, mode, ifAvailable) {
    if (ifAvailable && lockRequestMissChannel.hasSubscribers) {
      lockRequestMissChannel.publish({ name, mode });
    }
  }
  
  function publishLockRequestEnd(name, mode, ifAvailable, steal, error) {
    if (lockRequestEndChannel.hasSubscribers) {
      lockRequestEndChannel.publish({ name, mode, ifAvailable, steal, error });
    }
  }
  
  // https://w3c.github.io/web-locks/#api-lock-manager
  class LockManager {
    constructor(symbol = undefined) {
      if (symbol !== kConstructLockManager) {
        throw new ERR_ILLEGAL_CONSTRUCTOR();
      }
    }
  
    /**
     * Request a Web Lock for a named resource.
     * @param {string} name - The name of the lock resource
     * @param {object} [options] - Lock options (optional)
     * @param {string} [options.mode] - Lock mode: 'exclusive' or 'shared' default is exclusive
     * @param {boolean} [options.ifAvailable] - Only grant if immediately available
     * @param {boolean} [options.steal] - Steal existing locks with same name
     * @param {AbortSignal} [options.signal] - Signal to abort pending lock request
     * @param {Function} [callback] - Function called when lock is granted
     * @returns {Promise} Promise that resolves when the lock is released
     * @throws {TypeError} When name is not a string or callback is not a function
     * @throws {DOMException} When validation fails or operation is not supported
     */
    // https://w3c.github.io/web-locks/#api-lock-manager-request
    async request(name, options, callback = undefined) {
      if (callback === undefined) {
        callback = options;
        options = undefined;
      }
  
      name = converters.DOMString(name);
      validateFunction(callback, 'callback');
  
      if (options === undefined || typeof options === 'function') {
        options = kEmptyObject;
      }
  
      // Convert LockOptions dictionary
      options = convertLockOptions(options);
  
      const { mode, ifAvailable, steal, signal } = options;
  
      validateAbortSignal(signal, 'options.signal');
  
      if (signal) {
        signal.throwIfAborted();
      }
  
      if (name[0] === '-') {
        // If name starts with U+002D HYPHEN-MINUS (-), then reject promise with a
        // "NotSupportedError" DOMException.
        throw lazyDOMException('Lock name may not start with hyphen',
                               'NotSupportedError');
      }
  
      if (ifAvailable === true && steal === true) {
        // If both options' steal dictionary member and option's
        // ifAvailable dictionary member are true, then reject promise with a
        // "NotSupportedError" DOMException.
        throw lazyDOMException('ifAvailable and steal are mutually exclusive',
                               'NotSupportedError');
      }
  
      if (mode !== locks.LOCK_MODE_EXCLUSIVE && steal === true) {
        // If options' steal dictionary member is true and options' mode
        // dictionary member is not "exclusive", then return a promise rejected
        // with a "NotSupportedError" DOMException.
        throw lazyDOMException(`mode: "${locks.LOCK_MODE_SHARED}" and steal are mutually exclusive`,
                               'NotSupportedError');
      }
  
      if (signal && (steal === true || ifAvailable === true)) {
        // If options' signal dictionary member is present, and either of
        // options' steal dictionary member or options' ifAvailable dictionary
        // member is true, then return a promise rejected with a
        // "NotSupportedError" DOMException.
        throw lazyDOMException('signal cannot be used with steal or ifAvailable',
                               'NotSupportedError');
      }
  
      const clientId = `node-${process.pid}-${threadId}`;
      publishLockRequestStart(name, mode);
  
      // Handle requests with AbortSignal
      if (signal) {
        return new Promise((resolve, reject) => {
          let lockGranted = false;
  
          const abortListener = () => {
            if (!lockGranted) {
              reject(signal.reason || lazyDOMException('The operation was aborted', 'AbortError'));
            }
          };
  
          signal.addEventListener('abort', abortListener, { once: true });
  
          const wrappedCallback = (lock) => {
            return PromisePrototypeThen(PromiseResolve(), () => {
              if (signal.aborted) {
                return undefined;
              }
              lockGranted = true;
              publishLockRequestGrant(name, mode);
  
              return callback(createLock(lock));
            });
          };
  
          try {
            const released = locks.request(
              name,
              clientId,
              mode,
              steal,
              ifAvailable,
              wrappedCallback,
            );
  
            // When released promise settles, clean up listener and resolve main promise
            SafePromisePrototypeFinally(
              PromisePrototypeThen(
                released,
                (result) => {
                  publishLockRequestEnd(name, mode, ifAvailable, steal, undefined);
                  resolve(result);
                },
                (error) => {
                  const convertedError = convertLockError(error);
                  publishLockRequestEnd(name, mode, ifAvailable, steal, convertedError);
                  reject(convertedError);
                },
              ),
              () => signal.removeEventListener('abort', abortListener),
            );
          } catch (error) {
            signal.removeEventListener('abort', abortListener);
            const convertedError = convertLockError(error);
            publishLockRequestEnd(name, mode, ifAvailable, steal, convertedError);
            reject(convertedError);
          }
        });
      }
  
      // When ifAvailable: true and lock is not available, C++ passes null to indicate no lock granted
      const wrapCallback = (internalLock) => {
        if (internalLock === null) {
          publishLockRequestMiss(name, mode, ifAvailable);
        } else {
          publishLockRequestGrant(name, mode);
        }
        const lock = createLock(internalLock);
        return callback(lock);
      };
  
      // Standard request without signal
      try {
        const result = await locks.request(name, clientId, mode, steal, ifAvailable, wrapCallback);
        publishLockRequestEnd(name, mode, ifAvailable, steal, undefined);
  
        return result;
      } catch (error) {
        const convertedError = convertLockError(error);
        publishLockRequestEnd(name, mode, ifAvailable, steal, convertedError);
        throw convertedError;
      }
    }
  
    /**
     * Query the current state of locks for this environment.
     * @returns {Promise<{held: Array<object>, pending: Array<object>}>} Promise resolving to lock manager snapshot
     */
    // https://w3c.github.io/web-locks/#api-lock-manager-query
    async query() {
      if (this instanceof LockManager) {
        return locks.query();
      }
      throw new ERR_INVALID_THIS('LockManager');
    }
  }
  
  ObjectDefineProperties(LockManager.prototype, {
    request: kEnumerableProperty,
    query: kEnumerableProperty,
    [SymbolToStringTag]: {
      __proto__: null,
      value: 'LockManager',
      writable: false,
      enumerable: false,
      configurable: true,
    },
  });
  
  module.exports = {
    Lock,
    LockManager,
    locks: new LockManager(kConstructLockManager),
  };
  