repo_description: Node.js JavaScript runtime ✨🐢🚀✨
repo_topics: javascript js linux macos mit node nodejs runtime windows
code: 'use strict';
  
  const {
    ArrayPrototypeIncludes,
    ArrayPrototypeIndexOf,
    ArrayPrototypePush,
    ArrayPrototypeSplice,
    ArrayPrototypeUnshift,
    FunctionPrototypeBind,
    NumberIsSafeInteger,
    ObjectDefineProperties,
    ObjectFreeze,
    ReflectApply,
    Symbol,
  } = primordials;
  
  const {
    ERR_ASYNC_CALLBACK,
    ERR_ASYNC_TYPE,
    ERR_INVALID_ASYNC_ID,
    ERR_INVALID_ARG_TYPE,
    ERR_INVALID_ARG_VALUE,
  } = require('internal/errors').codes;
  const {
    kEmptyObject,
  } = require('internal/util');
  const {
    validateFunction,
    validateString,
  } = require('internal/validators');
  const internal_async_hooks = require('internal/async_hooks');
  
  const AsyncContextFrame = require('internal/async_context_frame');
  
  // Get functions
  // For userland AsyncResources, make sure to emit a destroy event when the
  // resource gets gced.
  const { registerDestroyHook, kNoPromiseHook } = internal_async_hooks;
  const {
    asyncWrap,
    executionAsyncId,
    triggerAsyncId,
    // Private API
    hasAsyncIdStack,
    getHookArrays,
    enableHooks,
    disableHooks,
    updatePromiseHookMode,
    executionAsyncResource,
    // Internal Embedder API
    newAsyncId,
    getDefaultTriggerAsyncId,
    emitInit,
    emitBefore,
    emitAfter,
    emitDestroy,
    initHooksExist,
    destroyHooksExist,
  } = internal_async_hooks;
  
  // Get symbols
  const {
    async_id_symbol, trigger_async_id_symbol,
    init_symbol, before_symbol, after_symbol, destroy_symbol,
    promise_resolve_symbol,
  } = internal_async_hooks.symbols;
  
  // Get constants
  const {
    kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve,
  } = internal_async_hooks.constants;
  
  // Listener API //
  
  class AsyncHook {
    constructor({ init, before, after, destroy, promiseResolve, trackPromises }) {
      if (init !== undefined && typeof init !== 'function')
        throw new ERR_ASYNC_CALLBACK('hook.init');
      if (before !== undefined && typeof before !== 'function')
        throw new ERR_ASYNC_CALLBACK('hook.before');
      if (after !== undefined && typeof after !== 'function')
        throw new ERR_ASYNC_CALLBACK('hook.after');
      if (destroy !== undefined && typeof destroy !== 'function')
        throw new ERR_ASYNC_CALLBACK('hook.destroy');
      if (promiseResolve !== undefined && typeof promiseResolve !== 'function')
        throw new ERR_ASYNC_CALLBACK('hook.promiseResolve');
      if (trackPromises !== undefined && typeof trackPromises !== 'boolean') {
        throw new ERR_INVALID_ARG_TYPE('trackPromises', 'boolean', trackPromises);
      }
  
      this[init_symbol] = init;
      this[before_symbol] = before;
      this[after_symbol] = after;
      this[destroy_symbol] = destroy;
      this[promise_resolve_symbol] = promiseResolve;
      if (trackPromises === false) {
        if (promiseResolve) {
          throw new ERR_INVALID_ARG_VALUE('trackPromises',
                                          trackPromises, 'must not be false when promiseResolve is enabled');
        }
        this[kNoPromiseHook] = true;
      } else {
        // Default to tracking promises for now.
        this[kNoPromiseHook] = false;
      }
    }
  
    enable() {
      // The set of callbacks for a hook should be the same regardless of whether
      // enable()/disable() are run during their execution. The following
      // references are reassigned to the tmp arrays if a hook is currently being
      // processed.
      const { 0: hooks_array, 1: hook_fields } = getHookArrays();
  
      // Each hook is only allowed to be added once.
      if (ArrayPrototypeIncludes(hooks_array, this))
        return this;
  
      const prev_kTotals = hook_fields[kTotals];
  
      // createHook() has already enforced that the callbacks are all functions,
      // so here simply increment the count of whether each callbacks exists or
      // not.
      hook_fields[kTotals] = hook_fields[kInit] += +!!this[init_symbol];
      hook_fields[kTotals] += hook_fields[kBefore] += +!!this[before_symbol];
      hook_fields[kTotals] += hook_fields[kAfter] += +!!this[after_symbol];
      hook_fields[kTotals] += hook_fields[kDestroy] += +!!this[destroy_symbol];
      hook_fields[kTotals] +=
          hook_fields[kPromiseResolve] += +!!this[promise_resolve_symbol];
      ArrayPrototypePush(hooks_array, this);
  
      if (prev_kTotals === 0 && hook_fields[kTotals] > 0) {
        enableHooks();
      }
  
      if (!this[kNoPromiseHook]) {
        updatePromiseHookMode();
      }
  
      return this;
    }
  
    disable() {
      const { 0: hooks_array, 1: hook_fields } = getHookArrays();
  
      const index = ArrayPrototypeIndexOf(hooks_array, this);
      if (index === -1)
        return this;
  
      const prev_kTotals = hook_fields[kTotals];
  
      hook_fields[kTotals] = hook_fields[kInit] -= +!!this[init_symbol];
      hook_fields[kTotals] += hook_fields[kBefore] -= +!!this[before_symbol];
      hook_fields[kTotals] += hook_fields[kAfter] -= +!!this[after_symbol];
      hook_fields[kTotals] += hook_fields[kDestroy] -= +!!this[destroy_symbol];
      hook_fields[kTotals] +=
          hook_fields[kPromiseResolve] -= +!!this[promise_resolve_symbol];
      ArrayPrototypeSplice(hooks_array, index, 1);
  
      if (prev_kTotals > 0 && hook_fields[kTotals] === 0) {
        disableHooks();
      }
  
      return this;
    }
  }
  
  
  function createHook(fns) {
    return new AsyncHook(fns);
  }
  
  
  // Embedder API //
  
  const destroyedSymbol = Symbol('destroyed');
  const contextFrameSymbol = Symbol('context_frame');
  
  class AsyncResource {
    constructor(type, opts = kEmptyObject) {
      validateString(type, 'type');
  
      let triggerAsyncId = opts;
      let requireManualDestroy = false;
      if (typeof opts !== 'number') {
        triggerAsyncId = opts.triggerAsyncId === undefined ?
          getDefaultTriggerAsyncId() : opts.triggerAsyncId;
        requireManualDestroy = !!opts.requireManualDestroy;
      }
  
      // Unlike emitInitScript, AsyncResource doesn't supports null as the
      // triggerAsyncId.
      if (!NumberIsSafeInteger(triggerAsyncId) || triggerAsyncId < -1) {
        throw new ERR_INVALID_ASYNC_ID('triggerAsyncId', triggerAsyncId);
      }
  
      this[contextFrameSymbol] = AsyncContextFrame.current();
  
      const asyncId = newAsyncId();
      this[async_id_symbol] = asyncId;
      this[trigger_async_id_symbol] = triggerAsyncId;
  
      if (initHooksExist()) {
        if (type.length === 0) {
          throw new ERR_ASYNC_TYPE(type);
        }
  
        emitInit(asyncId, type, triggerAsyncId, this);
      }
  
      if (!requireManualDestroy && destroyHooksExist()) {
        // This prop name (destroyed) has to be synchronized with C++
        const destroyed = { destroyed: false };
        this[destroyedSymbol] = destroyed;
        registerDestroyHook(this, asyncId, destroyed);
      }
    }
  
    runInAsyncScope(fn, thisArg, ...args) {
      const asyncId = this[async_id_symbol];
      emitBefore(asyncId, this[trigger_async_id_symbol], this);
  
      const contextFrame = this[contextFrameSymbol];
      const prior = AsyncContextFrame.exchange(contextFrame);
      try {
        return ReflectApply(fn, thisArg, args);
      } finally {
        AsyncContextFrame.set(prior);
        if (hasAsyncIdStack())
          emitAfter(asyncId);
      }
    }
  
    emitDestroy() {
      if (this[destroyedSymbol] !== undefined) {
        this[destroyedSymbol].destroyed = true;
      }
      emitDestroy(this[async_id_symbol]);
      return this;
    }
  
    asyncId() {
      return this[async_id_symbol];
    }
  
    triggerAsyncId() {
      return this[trigger_async_id_symbol];
    }
  
    bind(fn, thisArg) {
      validateFunction(fn, 'fn');
      let bound;
      if (thisArg === undefined) {
        const resource = this;
        bound = function(...args) {
          ArrayPrototypeUnshift(args, fn, this);
          return ReflectApply(resource.runInAsyncScope, resource, args);
        };
      } else {
        bound = FunctionPrototypeBind(this.runInAsyncScope, this, fn, thisArg);
      }
      ObjectDefineProperties(bound, {
        'length': {
          __proto__: null,
          configurable: true,
          enumerable: false,
          value: fn.length,
          writable: false,
        },
      });
      return bound;
    }
  
    static bind(fn, type, thisArg) {
      type ||= fn.name;
      return (new AsyncResource(type || 'bound-anonymous-fn')).bind(fn, thisArg);
    }
  }
  
  // Placing all exports down here because the exported classes won't export
  // otherwise.
  module.exports = {
    // Public API
    get AsyncLocalStorage() {
      return AsyncContextFrame.enabled ?
        require('internal/async_local_storage/async_context_frame') :
        require('internal/async_local_storage/async_hooks');
    },
    createHook,
    executionAsyncId,
    triggerAsyncId,
    executionAsyncResource,
    asyncWrapProviders: ObjectFreeze({ __proto__: null, ...asyncWrap.Providers }),
    // Embedder API
    AsyncResource,
  };
  