{"version":3,"sources":["callback-hook/hook.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,mE;AACA,mC;AACA,+D;AACA,E;AACA,+D;AACA,E;AACA,qE;AACA,0D;AACA,sE;AACA,kD;AACA,E;AACA,qE;AACA,8D;AACA,2B;AACA,E;AACA,gE;AACA,yC;AACA,E;AACA,oE;AACA,+C;AACA,E;AACA,oE;AACA,mE;AACA,+D;AACA,sE;AACA,kE;AACA,wB;AACA,E;AACA,oE;AACA,qE;AACA,wD;AACA,wD;AACA,E;AACA,oE;AACA,gE;AACA,2D;;AAEA,2B;AACA,kB;AACA,0B;AACA,0B;AACA,sB;;AAEA,+B;AACA,qD;AACA,0C;AACA,mD;AACA,6E;AACA,yD;AACA,G;AACA,E;;AAEA,0B;AACA,iC;AACA,oB;;AAEA,sC;AACA,e;AACA,qD;AACA,+E;AACA,8E;AACA,+E;AACA,wB;AACA,O;AACA,M;;AAEA,mC;AACA,kC;;AAEA,Y;AACA,yB;AACA,kC;AACA,O;AACA,M;AACA,I;;AAEA,oE;AACA,uB;AACA,I;AACA,gE;AACA,mE;AACA,oD;AACA,I;AACA,sE;AACA,kC;AACA,I;AACA,6B;AACA,oB;;AAEA,8E;AACA,6E;AACA,gD;AACA,oC;;AAEA,qC;AACA,4C;AACA,sB;AACA,kE;AACA,sC;AACA,0C;;AAEA,iC;AACA,gB;AACA,O;AACA,K;AACA,G;AACA,G","file":"/packages/callback-hook.js","sourcesContent":["// XXX This pattern is under development. Do not add more callsites\n// using this package for now. See:\n// https://meteor.hackpad.com/Design-proposal-Hooks-YxvgEW06q6f\n//\n// Encapsulates the pattern of registering callbacks on a hook.\n//\n// The `each` method of the hook calls its iterator function argument\n// with each registered callback.  This allows the hook to\n// conditionally decide not to call the callback (if, for example, the\n// observed object has been closed or terminated).\n//\n// Callbacks are bound with `Meteor.bindEnvironment`, so they will be\n// called with the Meteor environment of the calling code that\n// registered the callback.\n//\n// Registering a callback returns an object with a single `stop`\n// method which unregisters the callback.\n//\n// The code is careful to allow a callback to be safely unregistered\n// while the callbacks are being iterated over.\n//\n// If the hook is configured with the `exceptionHandler` option, the\n// handler will be called if a called callback throws an exception.\n// By default (if the exception handler doesn't itself throw an\n// exception, or if the iterator function doesn't return a falsy value\n// to terminate the calling of callbacks), the remaining callbacks\n// will still be called.\n//\n// Alternatively, the `debugPrintExceptions` option can be specified\n// as string describing the callback.  On an exception the string and\n// the exception will be printed to the console log with\n// `Meteor._debug`, and the exception otherwise ignored.\n//\n// If an exception handler isn't specified, exceptions thrown in the\n// callback will propagate up to the iterator function, and will\n// terminate calling the remaining callbacks if not caught.\n\nHook = function (options) {\n  var self = this;\n  options = options || {};\n  self.nextCallbackId = 0;\n  self.callbacks = {};\n\n  if (options.exceptionHandler)\n    self.exceptionHandler = options.exceptionHandler;\n  else if (options.debugPrintExceptions) {\n    if (! _.isString(options.debugPrintExceptions))\n      throw new Error(\"Hook option debugPrintExceptions should be a string\");\n    self.exceptionHandler = options.debugPrintExceptions;\n  }\n};\n\n_.extend(Hook.prototype, {\n  register: function (callback) {\n    var self = this;\n\n    callback = Meteor.bindEnvironment(\n      callback,\n      self.exceptionHandler || function (exception) {\n        // Note: this relies on the undocumented fact that if bindEnvironment's\n        // onException throws, and you are invoking the callback either in the\n        // browser or from within a Fiber in Node, the exception is propagated.\n        throw exception;\n      }\n    );\n\n    var id = self.nextCallbackId++;\n    self.callbacks[id] = callback;\n\n    return {\n      stop: function () {\n        delete self.callbacks[id];\n      }\n    };\n  },\n\n  // For each registered callback, call the passed iterator function\n  // with the callback.\n  //\n  // The iterator function can choose whether or not to call the\n  // callback.  (For example, it might not call the callback if the\n  // observed object has been closed or terminated).\n  //\n  // The iteration is stopped if the iterator function returns a falsy\n  // value or throws an exception.\n  //\n  each: function (iterator) {\n    var self = this;\n\n    // Invoking bindEnvironment'd callbacks outside of a Fiber in Node doesn't\n    // run them to completion (and exceptions thrown from onException are not\n    // propagated), so we need to be in a Fiber.\n    Meteor._nodeCodeMustBeInFiber();\n\n    var ids = _.keys(self.callbacks);\n    for (var i = 0;  i < ids.length;  ++i) {\n      var id = ids[i];\n      // check to see if the callback was removed during iteration\n      if (_.has(self.callbacks, id)) {\n        var callback = self.callbacks[id];\n\n        if (! iterator(callback))\n          break;\n      }\n    }\n  }\n});\n"]}