code: function (term, opts) {
        opts = opts || {};
        if (!term) { // if no term, return all known terms as an array of strings
          return _.keys(_M._vocab);
        }
        if (typeof _M._vocab[term] === 'undefined') { 
          if (opts.exists) return false; // they just wanted to test for existence
          throw new UnknownVocabularyTerm('The term "'+term+'" is not a known vocabulary term.');
        }
        if (opts.exists) return true; // they just wanted to test for existence
      
        var ret = _.cloneDeep(_M._vocab[term]);
        if (opts.merge) {
          _.merge(ret, opts.merge);
          // If changing the propertySchema or it's default, update the properties
          // from the new propertySchema:
          if (opts.merge.propertySchema || opts.merge.propertySchemaDefault) {
            ret = _M.propertySchemaToProperties(ret);
          }
        }
        // If there's an also schema, add it with allOf.  Useful when 
        // restricting a schema to a subset of the main vocab's properties.
        if (opts.also) {
          ret = {
            allOf: [ ret, opts.also ]
          };
        }
  
        return ret;
      }
docs: Use a function to access vocab terms and schemas so we can copy each one as needed to prevent accidentally changing them. If nothing is passed, it returns the list of all known terms. You can have opts of: opts: - exists: true => return true or false if term exists - merge: { object } => merge this object into the resulting schema to override it - also: { schema } => returns allOf: [ vocab(term), schema ]