docs: function createHubType() {
      log.info("create hub");
      // [ -Events- ]
      // error - emitted when received messages are poorly formatted
      // listen - emitted when listening
      // close - emitted when the hub is closed
      // status - various hub status updates
      // [ -Constructor- ]
      function Hub() {
          log.info("instantiate hub");
          // get non-ambiguous this
          var self = this;
          // Call parent constructor
          Hub.super_.call(self);
          // Internal structures
          self._subscribers = {};
          // subsribers is { source._id: {'source': source, patterns: [pattern*]}}
          self._transport = undefined;
          self._closed = false;
          // state listeners
          self.once('close', function handleClosed() {
              self._closed = true;
          });
          // Constructor.  Return undefined.
          return undefined;
      }
      // [ -Inheritance- ]
      util.inherits(Hub, events.EventEmitter);
      // [ -Private API- ]
      // disconnect a client
      Hub.prototype._disconnectClient = function hubDisconnectClient(client, error, cb) {
          client.close(cb);
          setImmediate(this.emit.bind(this), 'status', "closed client connection", error);
          this.emit('error', error);
          // allow chaining
          return this;
      };
      // handle publication request
      Hub.prototype._handlePublication = function hubHandlePublication(source, messageObj) {
          log.info("hub on publication");
          // get a non-ambiguous "this"
          var self = this;
          var error = validateProperties(
              messageObj,
              "publication message from transport layer",
              ['type', 'message', 'channel', 'transactionID'],
              []
          );
          if (error) { self._disconnectClient(source, error); return null; }
          // FIXME message should be JSON
          // FIXME channel should be regex
          self._distribute(messageObj.message, messageObj.channel, function distributeCB(numPublished) {
              // notify source of publication and number published
              source.send({
                  type: 'published',
                  'message': messageObj.message,
                  channel: messageObj.channel,
                  number: numPublished,
                  transactionID: messageObj.transactionID,
              }, function onError(error) {
                  if (error) { self._disconnectClient(source, error); }
              });
          });
          // allow chaining
          return this;
      };
      // handle subscription request
      Hub.prototype._handleSubscription = function hubHandleSubscription(source, messageObj) {
          log.info("hub on subscription");
          // get a non-ambiguous "this"
          var self = this;
          var error = validateProperties(
              messageObj,
              "subscription message from transport layer",
              ['type', 'pattern', 'transactionID'],
              []
          );
          if (error) { self._disconnectClient(source, error); return null; }
          // FIXME - pattern should be a regex
          var subscriber = self._subscribers[source._id];
          if (subscriber === undefined) {
              subscriber = {
                  patterns: [],
                  connection: source,
              };
              log.debug("number of unique subscribers: ", _.keys(self._subscribers).length + 1);
          }
          if (!_.contains(subscriber.patterns, messageObj.pattern)) {
              subscriber.patterns.push(messageObj.pattern);
          }
          self._subscribers[source._id] = subscriber;
          // notify source that subscription is in place
          source.send({
              type: 'subscribed',
              pattern: messageObj.pattern,
              number: subscriber.patterns.length,
              transactionID: messageObj.transactionID,
          }, function onError(error) {
              if (error) { self._disconnectClient(source, error); }
          });
          // allow chaining
          return this;
      };
      // handle unsubscription request
      Hub.prototype._handleUnsubscription = function hubHandleUnsubscription(source, messageObj) {
          log.info("hub on unsubscription");
          // get a non-ambiguous "this"
          var self = this;
          var error = validateProperties(
              messageObj,
              "unsubscription message from transport layer",
              ['type', 'pattern', 'transactionID'],
              []
          );
          if (error) { self._disconnectClient(source, error); return null; }
          // FIXME - pattern should be a regex
          var subscriber = self._subscribers[source._id];
          var numPatterns = 0;
          if (subscriber && _.contains(subscriber.patterns, messageObj.pattern)) {
              subscriber.patterns = _.without(subscriber.patterns, messageObj.pattern);
              if (subscriber.patterns.length === 0) {
                  delete self._subscribers[source._id];
                  log.debug("number of unique subscribers: ", _.keys(self._subscribers).length);
              } else {
                  numPatterns = subscriber.patterns.length;
              }
          }
          // notify source that unsubscription occurred
          source.send({
              type: 'unsubscribed',
              pattern: messageObj.pattern,
              number: numPatterns,
              transactionID: messageObj.transactionID,
          }, function onError(error) {
              if (error) { self._disconnectClient(source, error); }
          });
          // allow chaining
          return this;
      };
      // unsubscribe a dead client
      Hub.prototype._unsubscribeFromAll = function hubUnsubscribeFromAll(source) {
          log.info("hub _unsubscribeFromAll", source);
          if (_.contains(_.keys(this._subscribers)), source._id) {
              delete this._subscribers[source._id];
              this.emit('status', "client disconnected", source._id);
          }
          // allow chaining
          return this;
      };
      // handle messages from clients
      Hub.prototype._handleMessage = function hubHandleMessage(source, messageObj) {
          log.info("hub handle message", messageObj);
          // parse the message type and respond accordingly
          if (!messageObj.type) {
              var noTypeError = new VError("Message from transport layer has no type (%j)", messageObj);
              this._disconnectClient(source, noTypeError);
          } else if (messageObj.type == "publication") {
              this._handlePublication(source, messageObj);
          } else if (messageObj.type == "subscription") {
              this._handleSubscription(source, messageObj);
          } else if (messageObj.type == "unsubscription") {
              this._handleUnsubscription(source, messageObj);
          } else {
              var badTypeError = new VError("Unrecognized message type from transport layer: %s. (%j)", messageObj.type, messageObj);
              this._disconnectClient(source, badTypeError);
          }
          // allow chaining
          return this;
      };
      // distribute a message to subscribers
      Hub.prototype._distribute = function hubDistribute(message, channel, cb) {
          // distribute a message on a channel to subscribers
          // get a non-ambiguous "this"
          var self = this;
          // new message object for distribution to clients
          var newMessageObj = {
              type: 'distribution',
              'message': message,
              'channel': channel,
          };
          // send to each subscriber
          var numPublished = 0;
          var match;
          var matchPattern = function matchPattern(pattern) {
              // closure for match.
              // returns true/false for match test, and also sets
              // the matching pattern
              match = pattern;
              return channel.match(pattern);
          };
          async.eachSeries(_.keys(self._subscribers), function distribute(subscriberId, eachCB) {
              // error response
              var subscriber = self._subscribers[subscriberId];
              if (_.any(subscriber.patterns, matchPattern)) {
                  newMessageObj.match = match;
                  subscriber.connection.send(newMessageObj, function sendError(error) {
                      log.debug('hub sent to client (%j)', newMessageObj);
                      // close the client conn on error.
                      if (error) {
                          self._disconnectClient(subscriber.connection, error, eachCB);
                      } else {
                          numPublished++;
                          eachCB();
                      }
                      return null;
                  });
              } else {
                  eachCB();
              }
              return null;
          }, function eachFinal() {
              // errors here would come from disconnectClient, which calls
              // the cb when the client is closed.
              // Client.close does not report an error on close.
              callback(cb)(numPublished);
              return null;
          });
          return this;
      };
      // [ -Public API- ]
      // close a transport object
      Hub.prototype.close = function hubClose(cb) {
          log.info("hub close");
          if (this._closed) {
              throw new VError("Hub has already been closed");
          }
          // add cb to listeners
          if (cb) {
              this.on('close', cb);
          }
          this._transport.close();
          // it doesn't make sense to do anything with the object after this,
          // so return null
          return null;
      };
      // listen for connections
      Hub.prototype.listen = function hubListen(options, cb) {
          log.info("hub listen");
          // non-ambiguous self
          var self = this;
          // FIXME - deal with options better - ask for url/http explicitly?
          // only allow this to be called once on the object
          if (self._transport) {
              // throw immediately - this is programmer error
              throw new VError("'listen' has already been called on this hub.");
          }
          // add cb to listeners
          if (cb) {
              self.on('listening', cb);
          }
          // set transport
          self._transport = new ActualTransportServer();
          // proxy events
          self._transport.on('listening', self.emit.bind(self, 'listening'));
          self._transport.on('close', self.emit.bind(self, 'close'));
          // handle client actions
          self._transport.on('client closed', self._unsubscribeFromAll.bind(self));
          self._transport.on('message', self._handleMessage.bind(self));
          // handle transport actions
          self._transport.on('error', function handleTransportError() {
              // FIXME need a test for this
              // FIXME need some notice - either a propagated error event or a reason for the close
              self.close();
          });
          // listen
          self._transport.listen(options);
          // it doesn't make sense to do anything with the object until listen
          // calls back, so return null
          return null;
      };
      // return a new Hub
      return Hub;
  }