<commit_before>/** unauthenticated.js
* @file: /src/app/routes/ajax/unauthenticated.js
* @description: Handles express routing for the unathenticated ajax routes
* @parameters: Object(app), Object(resources)
* @returns: Express routes
*/

var ObjectID = require('mongodb').ObjectID
var striptags = require('striptags')
var extend = require('util')._extend;

module.exports = function (app, resources) {
  /** /ajax/map
  * @type: GET
  * @description: Ajax route for loading page info to a map.
  * @parameters: {
      Object(filter): {
        id: String("568207facddd6a6a0808c770"),
        type: String("5")
      }
  * }
  */
  app.get('/ajax/map', function (req, res) {
    var filter = {
      accepted: true, //Only published pages
      $or: [
        { type: '3' },
        { type: '5' }
      ]
    }

    if(req.query.filter !== undefined) { //A filter is used
      var filters = {
        id: function(id) {
          return {
            _id: new ObjectID(id)
          }
        },
        type: function(type) {
          return {
            type: String(type)
          }
        },
        ids: function(ids) {
          var objIds = ids.map(function(id) {
            return new ObjectID(id)
          })

          return {
            _id: {
              $in: objIds
            }
          }
        },
        city: function(city) {
          return {
            "post.city": String(city).toLowerCase()
          }
        }
      }

      for(var key in req.query.filter) {
        if(key in filters) {
          filter = filters[key](req.query.filter[key])
        }
      }
    }

    var pagesdb = resources.collections.pages

    pagesdb.find(filter).toArray(function(err, doc) {
      for (var i = 0; i < doc.length; i++) {
        doc[i].post.content = striptags(doc[i].post.content, ['br'])
      }
      res.json(doc)
    })
  })

  /** /ajax/search
  *
  */
  app.get('/ajax/search', function (req, res) {
    var searchString = req.query.s
    var searchArray = searchString.toLowerCase().split(' ')

    //The default query object
    var query = {}

    //The default fields we search
    var searchFields = ['url','title','post.content','post.food','post.product_type','post.city']

    //Check if query matches a type
    var queryType = false

    var queryOperations = {
      'butik': function() {
        query['type'] = '5'
        return true
      },
      'produkt': function() {
        query['type'] = '4'
        return true
      },
      'restaurang': function() {
        query['type'] = '3'
        return true
      },
      'recept': function() {
        query['type'] = '2'
        return true
      },
      'fakta': function() {
        query['type'] = '1'
        return true
      },
      'vegan': function() {
        if('type' in query && query.type === '3') {
          query['veg_type'] = 'vegan'
          return true
        } else {
          searchFields.push({
            property: 'post.veg_type',
            value: 'vegan'
          })
          return false
        }
      },
      'laktoovo': function() {
        if('type' in query && query.type === '3') {
          query['veg_type'] = 'lacto_ovo'
          return true
        } else {
          searchFields.push({
            property: 'post.veg_type',
            value: 'lacto_ovo'
          })
          return false
        }
      },
      'animal': function() {
        if('type' in query && query.type === '3') {
          query['veg_type'] = 'animal'
          return true
        } else {
          searchFields.push({
            property: 'post.veg_type',
            value: 'animal'
          })
          return false
        }
      }
    }

    var queryTypes = function(string) {
      string = string.toLowerCase().replace(/[^a-z]/gi,'') //Remove non alphabet characters

      var keywords = ['butik','restaurang','produkt','recept','fakta','vegan','laktoovo','animal']
      var regexMatches = new RegExp('^' + keywords.join('|'))

      var match = string.match(regexMatches)
      if(match !== null && match.length > 0) {
        var key = match[0]

        if(key in queryOperations) {
          //Perform the operations on the search query
          if(queryOperations[key]()) {
            //Update the search string to remove the key from the search, otherwise might result in unwanted results

            var tmpArray = searchString.split(' ')

            searchString = tmpArray.filter(function(text) {
              var isKey = text.match(key)
              if(isKey === null) {
                return text
              }
            }).join(' ').trim()
          }
        }
      }
    }

    for (var i in searchArray) {
      queryTypes(searchArray[i])
    }

    //Build general query
    var regexTitleArray = []

    //As the searchString has been manipulated it might not even be anything at all anymore
    if(searchString.length > 0) {
      regexTitleArray = searchString.split(' ').map(function(text) {
        return new RegExp(text, 'i')
      })
    }

    var orFields = []
    for (var i = searchFields.length - 1; i >= 0; i--) {
      var obj = {}

      if(typeof searchFields[i] === 'object') {
        if('property' in searchFields[i] && 'value' in searchFields[i]) {
          obj[searchFields[i].property] = {
            $in: [searchFields[i].value]
          }
        }
      } else {
        obj[searchFields[i]] = {
          $in: regexTitleArray
        }
      }

      orFields.push(obj)
    }

    var filteredQuery = {
      $match: {
        accepted: true, //Only published pages
        $or: orFields
      }
    }

    for(var key in query) {
      if( ! (key in filteredQuery.$match) ) {
        filteredQuery.$match[key] = query[key]
      }
    }

    //Check if query matches a city
    var citiesdb = resources.collections.cities
    citiesdb.find({
      name: {
        $in: searchArray
      }
    }).toArray(function(err, cities) {
      if(err) throw err

      if(cities.length > 0) {
        filteredQuery.$match['post.city'] = {
          '$regex': cities[0].name, //Search only this city
          '$options': '-i'
        }

      }

      /*
      //Check if query matches a category
      var categoriesdb = resources.collections.categories
      categoriesdb.find({
        $or: [{
          name: {
            $in: searchArray
          }
        }, {
          subcategory: {
            $in: searchArray
          }
        }]
      }).toArray(function(err, categories) {
        //console.log(categories)
        //If we found a match here, we might want to restrict the query to that type of category
      })
      */

      //TODO build aggregate query better, utilizing $meta: "textScore"
      //Maybe rebuild results with projection or just use group

      //Find pages
      var pagesdb = resources.collections.pages
      pagesdb.aggregate([filteredQuery], function (err, pages) {
        if(err) throw err

        for (var i = 0; i < pages.length; i++) {
          if('post' in pages[i]) {
            if('content' in pages[i].post) {
              pages[i].post.content = striptags(pages[i].post.content, ['br','p','a','span','i','b'])
            } else {
              delete(pages[i])
            }
          } else {
            delete(pages[i])
          }
        }

        //Reindex array
        pages = pages.filter(function(page) {
          return page
        })

        res.json(pages)
      })
    })
  })

  /** /ajax/imageInfo
  *
  */
  app.get('/ajax/imageInfo', function (req, res) {
    if(req.query.id != undefined) {
      var imagesdb = resources.collections.images
      imagesdb.find({ _id : new ObjectID(req.query.id) }).toArray(function (err, doc) {
        res.json(doc)
      })
    }
  })
}<commit_msg>Better search results with new weight system<commit_after>/** unauthenticated.js
* @file: /src/app/routes/ajax/unauthenticated.js
* @description: Handles express routing for the unathenticated ajax routes
* @parameters: Object(app), Object(resources)
* @returns: Express routes
*/

var ObjectID = require('mongodb').ObjectID
var striptags = require('striptags')
var extend = require('util')._extend;

module.exports = function (app, resources) {
  /** /ajax/map
  * @type: GET
  * @description: Ajax route for loading page info to a map.
  * @parameters: {
      Object(filter): {
        id: String("568207facddd6a6a0808c770"),
        type: String("5")
      }
  * }
  */
  app.get('/ajax/map', function (req, res) {
    var filter = {
      accepted: true, //Only published pages
      $or: [
        { type: '3' },
        { type: '5' }
      ]
    }

    if(req.query.filter !== undefined) { //A filter is used
      var filters = {
        id: function(id) {
          return {
            _id: new ObjectID(id)
          }
        },
        type: function(type) {
          return {
            type: String(type)
          }
        },
        ids: function(ids) {
          var objIds = ids.map(function(id) {
            return new ObjectID(id)
          })

          return {
            _id: {
              $in: objIds
            }
          }
        },
        city: function(city) {
          return {
            "post.city": String(city).toLowerCase()
          }
        }
      }

      for(var key in req.query.filter) {
        if(key in filters) {
          filter = filters[key](req.query.filter[key])
        }
      }
    }

    var pagesdb = resources.collections.pages

    pagesdb.find(filter).toArray(function(err, doc) {
      for (var i = 0; i < doc.length; i++) {
        doc[i].post.content = striptags(doc[i].post.content, ['br'])
      }
      res.json(doc)
    })
  })

  /** /ajax/search
  *
  */
  app.get('/ajax/search', function (req, res) {
    var searchString = req.query.s
    var searchArray = searchString.toLowerCase().split(' ')

    //The default query object
    var query = {}

    //The default fields we search
    var searchFields = ['url','title','post.content','post.food','post.product_type','post.city']

    //Check if query matches a type
    var queryType = false

    var queryOperations = {
      'butik': function() {
        query['type'] = '5'
        return true
      },
      'produkt': function() {
        query['type'] = '4'
        return true
      },
      'restaurang': function() {
        query['type'] = '3'
        return true
      },
      'recept': function() {
        query['type'] = '2'
        return true
      },
      'fakta': function() {
        query['type'] = '1'
        return true
      },
      'vegan': function() {
        if('type' in query && query.type === '3') {
          query['veg_type'] = 'vegan'
          return true
        } else {
          searchFields.push({
            property: 'post.veg_type',
            value: 'vegan'
          })
          return false
        }
      },
      'laktoovo': function() {
        if('type' in query && query.type === '3') {
          query['veg_type'] = 'lacto_ovo'
          return true
        } else {
          searchFields.push({
            property: 'post.veg_type',
            value: 'lacto_ovo'
          })
          return false
        }
      },
      'animal': function() {
        if('type' in query && query.type === '3') {
          query['veg_type'] = 'animal'
          return true
        } else {
          searchFields.push({
            property: 'post.veg_type',
            value: 'animal'
          })
          return false
        }
      }
    }

    var queryTypes = function(string) {
      string = string.toLowerCase().replace(/[^a-z]/gi,'') //Remove non alphabet characters

      var keywords = ['butik','restaurang','produkt','recept','fakta','vegan','laktoovo','animal']
      var regexMatches = new RegExp('^' + keywords.join('|'))

      var match = string.match(regexMatches)
      if(match !== null && match.length > 0) {
        var key = match[0]

        if(key in queryOperations) {
          //Perform the operations on the search query
          if(queryOperations[key]()) {
            //Update the search string to remove the key from the search, otherwise might result in unwanted results

            var tmpArray = searchString.split(' ')

            searchString = tmpArray.filter(function(text) {
              var isKey = text.match(key)
              if(isKey === null) {
                return text
              }
            }).join(' ').trim()
          }
        }
      }
    }

    for (var i in searchArray) {
      queryTypes(searchArray[i])
    }

    //Build general query
    var regexArray = []

    //As the searchString has been manipulated it might not even be anything at all anymore
    if(searchString.length > 0) {
      regexArray = searchString.split(' ').map(function(text) {
        return new RegExp(text, 'i')
      })
    }

    var orFields = []
    for (var i = searchFields.length - 1; i >= 0; i--) {
      var obj = {}

      if(typeof searchFields[i] === 'object') {
        if('property' in searchFields[i] && 'value' in searchFields[i]) {
          obj[searchFields[i].property] = {
            $in: [searchFields[i].value]
          }
        }
      } else {
        obj[searchFields[i]] = {
          $in: regexArray
        }
      }

      orFields.push(obj)
    }

    var filteredQuery = {
      $match: {
        accepted: true, //Only published pages
        $or: orFields
      }
    }

    for(var key in query) {
      if( ! (key in filteredQuery.$match) ) {
        filteredQuery.$match[key] = query[key]
      }
    }

    //Check if query matches a city
    var citiesdb = resources.collections.cities
    citiesdb.find({
      name: {
        $in: searchArray
      }
    }).toArray(function(err, cities) {
      if(err) throw err

      if(cities.length > 0) {
        filteredQuery.$match['post.city'] = {
          '$regex': cities[0].name, //Search only this city
          '$options': '-i'
        }

      }

      /*
      //Check if query matches a category
      var categoriesdb = resources.collections.categories
      categoriesdb.find({
        $or: [{
          name: {
            $in: searchArray
          }
        }, {
          subcategory: {
            $in: searchArray
          }
        }]
      }).toArray(function(err, categories) {
        //console.log(categories)
        //If we found a match here, we might want to restrict the query to that type of category
      })
      */

      //Find pages
      var pagesdb = resources.collections.pages
      pagesdb.aggregate([filteredQuery], function (err, pages) {
        if(err) throw err

        var weightedResult = [] //To hold the page results with their newly calculated weights

        for (var i = 0; i < pages.length; i++) {
          //Look up weights for each field and reorder array of results accordingly
          var searchWeights = {
            "title": 20,
            "url": 18,
            "post.city": 15,
            "post.food": 10,
            "post.product_type": 10,
            "post.content": 5,
            "post.veg_type": 3,
            "post.veg_offer": 1
          }

          //Calculate the weight of each result
          var totalWeight = 0
          for(var key in searchWeights) {
            var weight = searchWeights[key]
            var value

            //TODO This should really be replaced with a loop that supports deeper structures, will do for now
            if(key.indexOf('.') >= 0) {
              subKey = key.substr( key.indexOf('.') +1 )
              parentKey = key.substr(0, key.indexOf('.') )
              if(parentKey in pages[i]) {
                if(subKey in pages[i][parentKey]) {
                    value = pages[i][parentKey][subKey]
                }
              }
            } else {
              if(key in pages[i]) {
                value = pages[i][key]
              }
            }

            if(value) { //If value was found, check which fields it matched in the db query
              var matches = 0

              //Count the matched regex cases against the search query string
              for(var index in regexArray) {
                regex = regexArray[index]
                if(value.match(regex) !== null) {
                  matches++
                }
              }

              totalWeight += (matches * weight) //Add the new weight to the totalWeight, more matches = better match
            } else {
              //TODO to fix this we need to replace above loop with one that supports a deeper structure
              console.log('Failed to find value for key: ', key)
            }
          }

          if('post' in pages[i]) {
            if('content' in pages[i].post) { //All pages should have content, if not, then something is wrong with document in db
              //Strip html tags of content
              pages[i].post.content = striptags(pages[i].post.content, ['br','p','a','span','i','b'])

              //Add the page with its new weight
              weightedResult.push({
                page: pages[i],
                weight: totalWeight
              })
            }
          }
        }

        //Sort the weighted results based on the weight value
        weightedResult.sort(function(a,b) {
          if (a.weight < b.weight)
            return 1
          else if (a.weight > b.weight)
            return -1
          else
            return 0
        })

        //Get the final result array of just the pages
        var finalResult = weightedResult.map(function(obj) {
          return obj.page
        })

        res.json(finalResult)
      })
    })
  })

  /** /ajax/imageInfo
  *
  */
  app.get('/ajax/imageInfo', function (req, res) {
    if(req.query.id != undefined) {
      var imagesdb = resources.collections.images
      imagesdb.find({ _id : new ObjectID(req.query.id) }).toArray(function (err, doc) {
        res.json(doc)
      })
    }
  })
}<|endoftext|>


<commit_before>const del                   = require('del');
const eslint                = require('gulp-eslint');
const fs                    = require('fs');
const { Transform }         = require('stream');
const childProcess          = require('child_process');
const gulp                  = require('gulp');
const gulpStep              = require('gulp-step');
const qunitHarness          = require('gulp-qunit-harness');
const mocha                 = require('gulp-mocha-simple');
const mustache              = require('gulp-mustache');
const rename                = require('gulp-rename');
const uglify                = require('gulp-uglify');
const util                  = require('gulp-util');
const ll                    = require('gulp-ll-next');
const gulpRunCommand        = require('gulp-run-command').default;
const clone                 = require('gulp-clone');
const mergeStreams          = require('merge-stream');
const getClientTestSettings = require('./gulp/utils/get-client-test-settings');
const SAUCELABS_SETTINGS    = require('./gulp/saucelabs-settings');
const runPlayground         = require('./gulp/utils/run-playground');

gulpStep.install();

const needBeautifyScripts = process.argv.includes('--beautify');
const noBuild             = process.argv.includes('--no-build');

ll
    .install()
    .tasks('lint')
    .onlyInDebug([
        'server-scripts',
        'client-scripts-bundle'
    ]);

// Build
gulp.task('clean-lib', () => {
    return del(['./lib']);
});

gulp.step('client-scripts-bundle', () => {
    return childProcess
        .spawn('npx rollup -c', { shell: true, stdio: 'inherit' });
});

gulp.step('client-scripts-processing', () => {
    const script = gulp.src('./src/client/index.js.wrapper.mustache')
        .pipe(mustache({ source: fs.readFileSync('./lib/client/hammerhead.js').toString() }))
        .pipe(rename('hammerhead.js'));

    const bundledScript = script.pipe(clone())
        .pipe(uglify())
        .pipe(rename('hammerhead.min.js'));

    const bundledTransportWorker = gulp.src('./lib/client/transport-worker.js')
        .pipe(uglify())
        .pipe(rename('transport-worker.min.js'));

    const bundledWorkerHammerhead = gulp.src('./lib/client/worker-hammerhead.js')
        .pipe(uglify())
        .pipe(rename('worker-hammerhead.min.js'));

    return mergeStreams(script, bundledScript, bundledTransportWorker, bundledWorkerHammerhead)
        .pipe(gulp.dest('./lib/client'));
});

gulp.step('client-scripts', gulp.series('client-scripts-bundle', 'client-scripts-processing'));

// TODO: get rid of this step when we migrate to proper ES6 default imports
gulp.step('server-scripts-add-exports', () => {
    const transform = new Transform({
        objectMode: true,

        transform (file, enc, cb) {
            const fileSource = file.contents.toString();

            if (fileSource.includes('exports.default =')) {
                const sourceMapIndex = fileSource.indexOf('//# sourceMappingURL');
                const modifiedSource = fileSource.slice(0, sourceMapIndex) + 'module.exports = exports.default;\n' + fileSource.slice(sourceMapIndex);

                file.contents = Buffer.from(modifiedSource);
            }

            cb(null, file);
        }
    });

    return gulp
        .src([
            'lib/**/*.js',
            '!lib/client/**/*.js'
        ])
        .pipe(transform)
        .pipe(gulp.dest('lib'));
});

gulp.step('server-scripts', () => {
    const generateSourceMap = util.env.dev ? '--inlineSourceMap true' : '';

    return childProcess
        .spawn(`npx tsc -p tsconfig.json ${generateSourceMap}`, { shell: true, stdio: 'inherit' });
});

gulp.step('templates', () => {
    return gulp
        .src('./src/client/task.js.mustache', { silent: false })
        .pipe(gulp.dest('./lib/client'));
});

gulp.step('lint-js', () => {
    return gulp
        .src([
            './test/server/**/*.js',
            './test/client/fixtures/**/*.js',
            './gulp/**/*.js',
            'Gulpfile.js',
            '!./test/server/data/**/*.js'
        ])
        .pipe(eslint())
        .pipe(eslint.format())
        .pipe(eslint.failAfterError());
});

gulp.step('lint-ts', () => {
    return gulp.src('./src/**/*.ts')
        .pipe(eslint())
        .pipe(eslint.format())
        .pipe(eslint.failAfterError());
});

gulp.task('lint', gulp.parallel('lint-js', 'lint-ts'));

gulp.task('build',
    gulp.series(
        'clean-lib',
        'server-scripts',
        'server-scripts-add-exports',
        gulp.parallel(
            'client-scripts',
            'templates',
            'lint'
        )
    )
);

const BUILD_TASK = noBuild ? () => Promise.resolve() : gulp.registry().get('build');

// Test
gulp.step('test-server-run', () => {
    return gulp.src('./test/server/**/*-test.js', { read: false })
        .pipe(mocha({
            // NOTE: Disable timeouts in debug mode.
            timeout:   typeof v8debug !== 'undefined' || !!process.debugPort ? Infinity : 2000,
            fullTrace: true
        }));
});

gulp.step('disable-node-tls-reject-unauthorized', done => {
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
    done();
});

gulp.task('test-server', gulp.series(BUILD_TASK, 'disable-node-tls-reject-unauthorized', 'test-server-run'));

gulp.step('test-client-run', () => {
    gulp.watch('./src/**/*.ts', BUILD_TASK);

    return gulp
        .src('./test/client/fixtures/**/*-test.js')
        .pipe(qunitHarness(getClientTestSettings()));
});

gulp.task('test-client', gulp.series(BUILD_TASK, 'test-client-run'));

gulp.step('set-dev-mode', done => {
    util.env.dev = true;
    done();
});

gulp.task('test-client-dev', gulp.series('set-dev-mode', 'test-client'));

gulp.step('test-client-cloud-run', () => {
    return gulp
        .src('./test/client/fixtures/**/*-test.js')
        .pipe(qunitHarness(getClientTestSettings(), SAUCELABS_SETTINGS));
});

gulp.task('test-client-cloud', gulp.series(BUILD_TASK, 'test-client-cloud-run'));

gulp.step('http-playground-server', () => {
    return runPlayground({ needBeautifyScripts });
});

gulp.step('set-multi-browser-mode', done => {
    process.env.allowMultipleWindows = true;

    done();
});

gulp.task('multi-window-http-playground', gulp.series(BUILD_TASK, 'set-multi-browser-mode', 'http-playground-server'));

gulp.task('http-playground', gulp.series(BUILD_TASK, 'http-playground-server'));

gulp.step('https-playground-server', () => {
    const selfSignedCertificate = require('openssl-self-signed-certificate');

    return runPlayground({
        ssl: {
            key:  selfSignedCertificate.key,
            cert: selfSignedCertificate.cert,
            needBeautifyScripts
        }
    });
});

gulp.task('https-playground', gulp.series(BUILD_TASK, 'https-playground-server'));

gulp.step('cached-http-playground-server', () => {
    return runPlayground({ cache: true, needBeautifyScripts });
});

gulp.task('cached-http-playground', gulp.series(BUILD_TASK, 'cached-http-playground-server'));

gulp.step('test-functional-testcafe-run', gulpRunCommand([
    'chmod +x ./test/functional/run-testcafe-functional-tests.sh',
    './test/functional/run-testcafe-functional-tests.sh'
]));

gulp.task('test-functional-testcafe', gulp.series(BUILD_TASK, 'test-functional-testcafe-run'));

gulp.task('travis', process.env.GULP_TASK ? gulp.series(process.env.GULP_TASK) : () => {});
<commit_msg>Remove outdated gulp task (#2729)<commit_after>const del                   = require('del');
const eslint                = require('gulp-eslint');
const fs                    = require('fs');
const { Transform }         = require('stream');
const childProcess          = require('child_process');
const gulp                  = require('gulp');
const gulpStep              = require('gulp-step');
const qunitHarness          = require('gulp-qunit-harness');
const mocha                 = require('gulp-mocha-simple');
const mustache              = require('gulp-mustache');
const rename                = require('gulp-rename');
const uglify                = require('gulp-uglify');
const util                  = require('gulp-util');
const ll                    = require('gulp-ll-next');
const gulpRunCommand        = require('gulp-run-command').default;
const clone                 = require('gulp-clone');
const mergeStreams          = require('merge-stream');
const getClientTestSettings = require('./gulp/utils/get-client-test-settings');
const SAUCELABS_SETTINGS    = require('./gulp/saucelabs-settings');
const runPlayground         = require('./gulp/utils/run-playground');

gulpStep.install();

const needBeautifyScripts = process.argv.includes('--beautify');
const noBuild             = process.argv.includes('--no-build');

ll
    .install()
    .tasks('lint')
    .onlyInDebug([
        'server-scripts',
        'client-scripts-bundle'
    ]);

// Build
gulp.task('clean-lib', () => {
    return del(['./lib']);
});

gulp.step('client-scripts-bundle', () => {
    return childProcess
        .spawn('npx rollup -c', { shell: true, stdio: 'inherit' });
});

gulp.step('client-scripts-processing', () => {
    const script = gulp.src('./src/client/index.js.wrapper.mustache')
        .pipe(mustache({ source: fs.readFileSync('./lib/client/hammerhead.js').toString() }))
        .pipe(rename('hammerhead.js'));

    const bundledScript = script.pipe(clone())
        .pipe(uglify())
        .pipe(rename('hammerhead.min.js'));

    const bundledTransportWorker = gulp.src('./lib/client/transport-worker.js')
        .pipe(uglify())
        .pipe(rename('transport-worker.min.js'));

    const bundledWorkerHammerhead = gulp.src('./lib/client/worker-hammerhead.js')
        .pipe(uglify())
        .pipe(rename('worker-hammerhead.min.js'));

    return mergeStreams(script, bundledScript, bundledTransportWorker, bundledWorkerHammerhead)
        .pipe(gulp.dest('./lib/client'));
});

gulp.step('client-scripts', gulp.series('client-scripts-bundle', 'client-scripts-processing'));

// TODO: get rid of this step when we migrate to proper ES6 default imports
gulp.step('server-scripts-add-exports', () => {
    const transform = new Transform({
        objectMode: true,

        transform (file, enc, cb) {
            const fileSource = file.contents.toString();

            if (fileSource.includes('exports.default =')) {
                const sourceMapIndex = fileSource.indexOf('//# sourceMappingURL');
                const modifiedSource = fileSource.slice(0, sourceMapIndex) + 'module.exports = exports.default;\n' + fileSource.slice(sourceMapIndex);

                file.contents = Buffer.from(modifiedSource);
            }

            cb(null, file);
        }
    });

    return gulp
        .src([
            'lib/**/*.js',
            '!lib/client/**/*.js'
        ])
        .pipe(transform)
        .pipe(gulp.dest('lib'));
});

gulp.step('server-scripts', () => {
    const generateSourceMap = util.env.dev ? '--inlineSourceMap true' : '';

    return childProcess
        .spawn(`npx tsc -p tsconfig.json ${generateSourceMap}`, { shell: true, stdio: 'inherit' });
});

gulp.step('templates', () => {
    return gulp
        .src('./src/client/task.js.mustache', { silent: false })
        .pipe(gulp.dest('./lib/client'));
});

gulp.step('lint-js', () => {
    return gulp
        .src([
            './test/server/**/*.js',
            './test/client/fixtures/**/*.js',
            './gulp/**/*.js',
            'Gulpfile.js',
            '!./test/server/data/**/*.js'
        ])
        .pipe(eslint())
        .pipe(eslint.format())
        .pipe(eslint.failAfterError());
});

gulp.step('lint-ts', () => {
    return gulp.src('./src/**/*.ts')
        .pipe(eslint())
        .pipe(eslint.format())
        .pipe(eslint.failAfterError());
});

gulp.task('lint', gulp.parallel('lint-js', 'lint-ts'));

gulp.task('build',
    gulp.series(
        'clean-lib',
        'server-scripts',
        'server-scripts-add-exports',
        gulp.parallel(
            'client-scripts',
            'templates',
            'lint'
        )
    )
);

const BUILD_TASK = noBuild ? () => Promise.resolve() : gulp.registry().get('build');

// Test
gulp.step('test-server-run', () => {
    return gulp.src('./test/server/**/*-test.js', { read: false })
        .pipe(mocha({
            // NOTE: Disable timeouts in debug mode.
            timeout:   typeof v8debug !== 'undefined' || !!process.debugPort ? Infinity : 2000,
            fullTrace: true
        }));
});

gulp.step('disable-node-tls-reject-unauthorized', done => {
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
    done();
});

gulp.task('test-server', gulp.series(BUILD_TASK, 'disable-node-tls-reject-unauthorized', 'test-server-run'));

gulp.step('test-client-run', () => {
    gulp.watch('./src/**/*.ts', BUILD_TASK);

    return gulp
        .src('./test/client/fixtures/**/*-test.js')
        .pipe(qunitHarness(getClientTestSettings()));
});

gulp.task('test-client', gulp.series(BUILD_TASK, 'test-client-run'));

gulp.step('set-dev-mode', done => {
    util.env.dev = true;
    done();
});

gulp.task('test-client-dev', gulp.series('set-dev-mode', 'test-client'));

gulp.step('test-client-cloud-run', () => {
    return gulp
        .src('./test/client/fixtures/**/*-test.js')
        .pipe(qunitHarness(getClientTestSettings(), SAUCELABS_SETTINGS));
});

gulp.task('test-client-cloud', gulp.series(BUILD_TASK, 'test-client-cloud-run'));

gulp.step('http-playground-server', () => {
    return runPlayground({ needBeautifyScripts });
});

gulp.step('set-multi-browser-mode', done => {
    process.env.allowMultipleWindows = true;

    done();
});

gulp.task('multi-window-http-playground', gulp.series(BUILD_TASK, 'set-multi-browser-mode', 'http-playground-server'));

gulp.task('http-playground', gulp.series(BUILD_TASK, 'http-playground-server'));

gulp.step('https-playground-server', () => {
    const selfSignedCertificate = require('openssl-self-signed-certificate');

    return runPlayground({
        ssl: {
            key:  selfSignedCertificate.key,
            cert: selfSignedCertificate.cert,
            needBeautifyScripts
        }
    });
});

gulp.task('https-playground', gulp.series(BUILD_TASK, 'https-playground-server'));

gulp.step('cached-http-playground-server', () => {
    return runPlayground({ cache: true, needBeautifyScripts });
});

gulp.task('cached-http-playground', gulp.series(BUILD_TASK, 'cached-http-playground-server'));

gulp.step('test-functional-testcafe-run', gulpRunCommand([
    'chmod +x ./test/functional/run-testcafe-functional-tests.sh',
    './test/functional/run-testcafe-functional-tests.sh'
]));

gulp.task('test-functional-testcafe', gulp.series(BUILD_TASK, 'test-functional-testcafe-run'));

<|endoftext|>


<commit_before>import {FlowWS, error} from '../../utils/index';
import {searchReceive, SEARCH_RECEIVE} from '../../modules/search/index';
import {receiveFields, FIELDS_RECEIVE} from '../../modules/fields/index';
import {
    receiveInitialState,
    INITIAL_STATE_RECEIVE
} from '../../modules/data/index';
import {requestCompleted} from "../actions";
import {REQUEST_COMPLETED} from "../constants";
import {ITEMS_RECEIVE} from "../../modules/items/constants";
import {receiveItems} from "../../modules/items/actions";

export const Socket = {
    ws: null,
    searchResults: {},
    searchTimeouts: {},
    searchReceive: (newResults, query, dispatch) => {
        if (newResults === null) {
            return;
        }

        let results = Socket.searchResults[query] || [];

        for ( var i = 0; i < newResults.length; i++ ) {
            let result = newResults[i];

            let index = results.findIndex((item) => (item.id == result.id));

            if (index == -1) {
                results.push(result);
                continue;
            }

            // already exists, update count
            results[index].count = result.count;
        };

        Socket.searchResults[query] = results;

        clearTimeout(Socket.searchTimeouts[query]);

        Socket.searchTimeouts[query] = setTimeout(() => {
            dispatch(searchReceive({
                results: Socket.searchResults[query],
                query: query,
            }));

            delete Socket.searchResults[query];
        }, 500);
    },
    wsDispatcher: (message, dispatch) => {
        if (message.error) {
            return dispatch(error(message.error.message));
        }

        switch (message.type) {
            case SEARCH_RECEIVE:
                Socket.searchReceive(message.results, message.query, dispatch);
                break;

            case 'LIVE_RECEIVE':
                Socket.searchReceive(message.graphs, message.datasource, dispatch);
                break;

            case FIELDS_RECEIVE:
                dispatch(receiveFields(message.fields));
                break;

            case INITIAL_STATE_RECEIVE:
                dispatch(receiveInitialState({
                    datasources: message.datasources,
                    version: message.version
                }));
                break;

            case REQUEST_COMPLETED:
                dispatch(requestCompleted(message['request-id']));
                break;

            case ITEMS_RECEIVE:
                dispatch(receiveItems(message.items));
                break;
        }
    },
    startWS: (dispatch) => {
        if (!!Socket.ws) {
            return;
        }

        let url;

        if (process.env.WEBSOCKET_URI) {
            url = process.env.WEBSOCKET_URI;
        } else {
            const {location} = window;
            url = ((location.protocol === "https:") ? "wss://" : "ws://") + location.host + "/ws";
        }

        console.log('%cMarija', 'font-size:40px;color:#blue;text-shadow:0 1px 0 #ccc,0 2px 0 #c9c9c9,0 3px 0 #bbb,0 4px 0 #b9b9b9,0 5px 0 #aaa,0 6px 1px rgba(0,0,0,.1),0 0 5px rgba(0,0,0,.1),0 1px 3px rgba(0,0,0,.3),0 3px 5px rgba(0,0,0,.2),0 5px 10px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.2),0 20px 20px rgba(0,0,0,.15);');
        console.log(`Using backend: ${ url }`);
        try {
            Socket.ws = new FlowWS(url, null, Socket.wsDispatcher, dispatch);
        } catch (e) {
            dispatch(error(e));
        }
    }
};
<commit_msg>add searchReceive debouncing docs<commit_after>import {FlowWS, error} from '../../utils/index';
import {searchReceive, SEARCH_RECEIVE} from '../../modules/search/index';
import {receiveFields, FIELDS_RECEIVE} from '../../modules/fields/index';
import {
    receiveInitialState,
    INITIAL_STATE_RECEIVE
} from '../../modules/data/index';
import {requestCompleted} from "../actions";
import {REQUEST_COMPLETED} from "../constants";
import {ITEMS_RECEIVE} from "../../modules/items/constants";
import {receiveItems} from "../../modules/items/actions";

export const Socket = {
    ws: null,
    searchResults: {},
    searchTimeouts: {},

    /**
     * Sometimes we receive many items from the server in quick succession of
     * each other. This is not good, because then we would need to calculate the
     * graph for each time we receive it (so multiple times per second).
     *
     * Instead, we wait for 500ms and bundle all of the items together.
     *
     * @param newResults
     * @param query
     * @param dispatch
     */
    searchReceive: (newResults, query, dispatch) => {
        if (newResults === null) {
            return;
        }

        let results = Socket.searchResults[query] || [];

        for ( var i = 0; i < newResults.length; i++ ) {
            let result = newResults[i];

            let index = results.findIndex((item) => (item.id == result.id));

            if (index == -1) {
                results.push(result);
                continue;
            }

            // already exists, update count
            results[index].count = result.count;
        };

        Socket.searchResults[query] = results;

        clearTimeout(Socket.searchTimeouts[query]);

        Socket.searchTimeouts[query] = setTimeout(() => {
            dispatch(searchReceive({
                results: Socket.searchResults[query],
                query: query,
            }));

            delete Socket.searchResults[query];
        }, 500);
    },
    wsDispatcher: (message, dispatch) => {
        if (message.error) {
            return dispatch(error(message.error.message));
        }

        switch (message.type) {
            case SEARCH_RECEIVE:
                Socket.searchReceive(message.results, message.query, dispatch);
                break;

            case 'LIVE_RECEIVE':
                Socket.searchReceive(message.graphs, message.datasource, dispatch);
                break;

            case FIELDS_RECEIVE:
                dispatch(receiveFields(message.fields));
                break;

            case INITIAL_STATE_RECEIVE:
                dispatch(receiveInitialState({
                    datasources: message.datasources,
                    version: message.version
                }));
                break;

            case REQUEST_COMPLETED:
                dispatch(requestCompleted(message['request-id']));
                break;

            case ITEMS_RECEIVE:
                dispatch(receiveItems(message.items));
                break;
        }
    },
    startWS: (dispatch) => {
        if (!!Socket.ws) {
            return;
        }

        let url;

        if (process.env.WEBSOCKET_URI) {
            url = process.env.WEBSOCKET_URI;
        } else {
            const {location} = window;
            url = ((location.protocol === "https:") ? "wss://" : "ws://") + location.host + "/ws";
        }

        console.log('%cMarija', 'font-size:40px;color:#blue;text-shadow:0 1px 0 #ccc,0 2px 0 #c9c9c9,0 3px 0 #bbb,0 4px 0 #b9b9b9,0 5px 0 #aaa,0 6px 1px rgba(0,0,0,.1),0 0 5px rgba(0,0,0,.1),0 1px 3px rgba(0,0,0,.3),0 3px 5px rgba(0,0,0,.2),0 5px 10px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.2),0 20px 20px rgba(0,0,0,.15);');
        console.log(`Using backend: ${ url }`);
        try {
            Socket.ws = new FlowWS(url, null, Socket.wsDispatcher, dispatch);
        } catch (e) {
            dispatch(error(e));
        }
    }
};
<|endoftext|>


<commit_before>// Requirements
const mqtt = require('mqtt')
const Redis = require('redis')
const express = require('express')
    // const Influx = require('../../')

const logging = require('./homeautomation-js-lib/logging.js')
require('./homeautomation-js-lib/devices.js')
require('./homeautomation-js-lib/redis_helpers.js')
require('./homeautomation-js-lib/mqtt_helpers.js')

// Config
const port = process.env.LISTENING_PORT

const redis = Redis.setupClient(null)
const client = mqtt.setupClient(function() {
    client.subscribe('#')
}, null)

// const influx = new Influx.InfluxDB({
//     host: 'localhost',
//     database: 'express_response_db',
//     schema: [{
//         measurement: 'response_times',
//         fields: {
//             path: Influx.FieldType.STRING,
//             duration: Influx.FieldType.INTEGER
//         },
//         tags: [
//             'host'
//         ]
//     }]
// })

client.on('message', (topic, message) => {
    if (topic.startsWith('happy')) {
        return
    }

    redis.valueForTopic(topic, function(err, result) {
        if (err !== null) return

        if (result !== null) {
            //logging.log('topic: ' + topic + ' value: ' + result)
            logging.info(JSON.stringify({ topic: topic, value: ('' + message) }))
        } else {
            logging.log('adding: ' + topic + ' value: ' + message)
        }
        redis.set(topic, message)
    })
})

// Express
const app = express()

app.get('/', function(req, res) {
    redis.keys('*', function(err, result) {
        logging.log('keys: ' + result)
        const keys = result.sort()
        redis.mget(keys, function(err, values) {
            var html = ''

            html += '<!DOCTYPE html>\n'
            html += '\n'
            html += '\n'

            html += ''

            html += '\n'
            html += '\n'
            html += '
All Devices
\n'
            html += '<form action="/action" method="post" form="main_form">'
            html += '<input type="submit" value="Generate Device File">'

            html += '
\n'

            html += '
\n'
            html += '
\n'
            html += '\n'
            html += ''
            html += '	\n'

            html += '\n'
            html += 'Name'
            html += '	\n'

            html += '\n'
            html += 'Other Topic'
            html += '	\n'

            html += '\n'
            html += 'Topic'
            html += '	\n'
            html += '\n'
            html += 'Value'
            html += '	\n'

            html += '
\n'
            html += '
\n'

            html += '
\n'

            for (var index = 0; index < keys.length; index++) {
                var key = keys[index]
                var value = values[index]

                if (key.endsWith('/set')) continue
                if (key.startsWith('/homeseer/action/')) continue
                if (key.startsWith('happy')) continue

                html += '
\n'
                html += '\n'
                html += '<input type="checkbox" name="enabled_' + key + '" value="checked" id="enabled" form="main_form"/>'
                html += '	\n'

                html += '\n'
                html += '<input type="text" name="name_' + key + '" id="name" form="main_form"/>'
                html += '	\n'

                html += '\n'
                html += '<input type="text" name="topic_' + key + '" id="alttopic" form="main_form"/>'
                html += '	\n'

                html += '\n'
                html += key
                html += '	\n'
                html += '\n'
                html += value
                html += '	\n'

                html += '
\n'
            }
            html += '
\n'

            html += '
\n'

            html += '</form>\n'

            html += '\n'
            html += ''

            res.send(html)
        })
    })
})

app.get('/device-file/', function(req, res) {
    redis.keys('*', function(err, result) {
        if (result === null) {
            res.send('err' + err)
            return
        }
        logging.log('keys: ' + result)
        const keys = result.sort()
        redis.mget(keys, function(err, values) {
            var html = ''
            for (var index = 0; index < keys.length; index++) {
                var key = keys[index]

                if (key.endsWith('/set')) continue
                if (key.startsWith('/homeseer/action/')) continue
                if (key.startsWith('happy')) continue

                var components = key.split('/')
                var lastComponents = components.slice(components.length - 2, components.length)
                var baseString = lastComponents.join(' ')
                var name = baseString.replace(/\//g, ' ').replace(/_/g, ' ')

                var title = baseString.replace(/\//g, '_').replace(/ /g, '_')
                if (title.startsWith('_')) title = title.slice(1, title.length)
                if (name.startsWith(' ')) name = name.slice(1, name.length)
                html += ''
                html += '' + title + ':
'
                html += '  name: "' + name + '"
'
                html += '  topic: "' + key + '"
'
                html += '  change_topic: "' + key + '"
'
                html += '  voice_control: false
'
                html += ''
            }

            res.send(html)
        })
    })
})

// Add headers
app.configure(function() {
    app.use(function(req, res, next) {
        res.header('Access-Control-Allow-Origin', '*')
        res.header('Access-Control-Allow-Headers', 'X-Requested-With')
        next()

        // Pass to next layer of middleware
        next()
    })
})

app.get('/json/', function(req, res) {
    redis.keys('*', function(err, result) {
        if (result === null) {
            res.send('err' + err)
            return
        }
        logging.log('keys: ' + result)
        const keys = result.sort()
        redis.mget(keys, function(err, values) {
            var devices = {}
            for (var index = 0; index < keys.length; index++) {
                var key = keys[index]
                if (key.length == 1) continue
                if (key.endsWith('/set')) continue
                if (key.startsWith('happy')) continue
                devices[key] = values[index]

            }

            res.send(JSON.stringify(devices))
        })
    })
})

app.listen(port, function() {
    logging.log('MQTT Store listening on port: ', port)
})<commit_msg>Double next<commit_after>// Requirements
const mqtt = require('mqtt')
const Redis = require('redis')
const express = require('express')
    // const Influx = require('../../')

const logging = require('./homeautomation-js-lib/logging.js')
require('./homeautomation-js-lib/devices.js')
require('./homeautomation-js-lib/redis_helpers.js')
require('./homeautomation-js-lib/mqtt_helpers.js')

// Config
const port = process.env.LISTENING_PORT

const redis = Redis.setupClient(null)
const client = mqtt.setupClient(function() {
    client.subscribe('#')
}, null)

// const influx = new Influx.InfluxDB({
//     host: 'localhost',
//     database: 'express_response_db',
//     schema: [{
//         measurement: 'response_times',
//         fields: {
//             path: Influx.FieldType.STRING,
//             duration: Influx.FieldType.INTEGER
//         },
//         tags: [
//             'host'
//         ]
//     }]
// })

client.on('message', (topic, message) => {
    if (topic.startsWith('happy')) {
        return
    }

    redis.valueForTopic(topic, function(err, result) {
        if (err !== null) return

        if (result !== null) {
            //logging.log('topic: ' + topic + ' value: ' + result)
            logging.info(JSON.stringify({ topic: topic, value: ('' + message) }))
        } else {
            logging.log('adding: ' + topic + ' value: ' + message)
        }
        redis.set(topic, message)
    })
})

// Express
const app = express()

app.get('/', function(req, res) {
    redis.keys('*', function(err, result) {
        logging.log('keys: ' + result)
        const keys = result.sort()
        redis.mget(keys, function(err, values) {
            var html = ''

            html += '<!DOCTYPE html>\n'
            html += '\n'
            html += '\n'

            html += ''

            html += '\n'
            html += '\n'
            html += '
All Devices
\n'
            html += '<form action="/action" method="post" form="main_form">'
            html += '<input type="submit" value="Generate Device File">'

            html += '
\n'

            html += '
\n'
            html += '
\n'
            html += '\n'
            html += ''
            html += '	\n'

            html += '\n'
            html += 'Name'
            html += '	\n'

            html += '\n'
            html += 'Other Topic'
            html += '	\n'

            html += '\n'
            html += 'Topic'
            html += '	\n'
            html += '\n'
            html += 'Value'
            html += '	\n'

            html += '
\n'
            html += '
\n'

            html += '
\n'

            for (var index = 0; index < keys.length; index++) {
                var key = keys[index]
                var value = values[index]

                if (key.endsWith('/set')) continue
                if (key.startsWith('/homeseer/action/')) continue
                if (key.startsWith('happy')) continue

                html += '
\n'
                html += '\n'
                html += '<input type="checkbox" name="enabled_' + key + '" value="checked" id="enabled" form="main_form"/>'
                html += '	\n'

                html += '\n'
                html += '<input type="text" name="name_' + key + '" id="name" form="main_form"/>'
                html += '	\n'

                html += '\n'
                html += '<input type="text" name="topic_' + key + '" id="alttopic" form="main_form"/>'
                html += '	\n'

                html += '\n'
                html += key
                html += '	\n'
                html += '\n'
                html += value
                html += '	\n'

                html += '
\n'
            }
            html += '
\n'

            html += '
\n'

            html += '</form>\n'

            html += '\n'
            html += ''

            res.send(html)
        })
    })
})

app.get('/device-file/', function(req, res) {
    redis.keys('*', function(err, result) {
        if (result === null) {
            res.send('err' + err)
            return
        }
        logging.log('keys: ' + result)
        const keys = result.sort()
        redis.mget(keys, function(err, values) {
            var html = ''
            for (var index = 0; index < keys.length; index++) {
                var key = keys[index]

                if (key.endsWith('/set')) continue
                if (key.startsWith('/homeseer/action/')) continue
                if (key.startsWith('happy')) continue

                var components = key.split('/')
                var lastComponents = components.slice(components.length - 2, components.length)
                var baseString = lastComponents.join(' ')
                var name = baseString.replace(/\//g, ' ').replace(/_/g, ' ')

                var title = baseString.replace(/\//g, '_').replace(/ /g, '_')
                if (title.startsWith('_')) title = title.slice(1, title.length)
                if (name.startsWith(' ')) name = name.slice(1, name.length)
                html += ''
                html += '' + title + ':
'
                html += '  name: "' + name + '"
'
                html += '  topic: "' + key + '"
'
                html += '  change_topic: "' + key + '"
'
                html += '  voice_control: false
'
                html += ''
            }

            res.send(html)
        })
    })
})

// Add headers
app.configure(function() {
    app.use(function(req, res, next) {
        res.header('Access-Control-Allow-Origin', '*')
        res.header('Access-Control-Allow-Headers', 'X-Requested-With')
        next()

    })
})

app.get('/json/', function(req, res) {
    redis.keys('*', function(err, result) {
        if (result === null) {
            res.send('err' + err)
            return
        }
        logging.log('keys: ' + result)
        const keys = result.sort()
        redis.mget(keys, function(err, values) {
            var devices = {}
            for (var index = 0; index < keys.length; index++) {
                var key = keys[index]
                if (key.length == 1) continue
                if (key.endsWith('/set')) continue
                if (key.startsWith('happy')) continue
                devices[key] = values[index]

            }

            res.send(JSON.stringify(devices))
        })
    })
})

app.listen(port, function() {
    logging.log('MQTT Store listening on port: ', port)
})<|endoftext|>


<commit_before>import './_globalHooks';

import httpConfig from './support/httpConfig';
import eventstore from '../index';
import assert from 'assert';
import uuid from 'uuid';
import _ from 'lodash';

describe('Http Client - Check Stream Exist', () => {
	it('Should return true when a stream exist', async() => {
		const client = eventstore.http(httpConfig);

		const testStream = `TestStream-${uuid.v4()}`;
		await client.writeEvent(testStream, 'TestEventType', {
			something: '123'
		});

		assert.equal(await client.checkStreamExists(testStream), true);
	});

	it('Should return false when a stream does not exist', async() => {
		const client = eventstore.http(httpConfig);
		assert.equal(await client.checkStreamExists('Non_existentStream'), false);
	});

	it('Should return rejected promise when the request error is anything other than a 404', callback => {
		const config = _.cloneDeep(httpConfig);
		config.port = 1;
		const client = eventstore.http(config);

		client.checkStreamExists('Non_existentStream_wrong_port_config').then(() => {
			callback('Should not have returned successful promise');
		}).catch(err => {
			assert(err, 'No error received');
			assert(err.message.includes('ECONNREFUSED'), 'Connection refused error expected');
			callback();
		});
	});

	it('Should throw an exception when timeout is reached', callback => {
		const clonedConfig = _.cloneDeep(httpConfig);
		clonedConfig.timeout = 20;

		const client = eventstore.http(clonedConfig);
		const testStream = `TestStream-${uuid.v4()}`;
		client.writeEvent(testStream, 'TestEventType', {
			something: '123'
		}).then(() => client.checkStreamExists(testStream).then(() => {
			callback('Expected to fail');
		})).catch(err => {
			if (err.message.includes('TIMEDOUT')) callback();
			else callback('Time out error expected');
		});
	});
});<commit_msg>[test] make time out a fraction in timeout test<commit_after>import './_globalHooks';

import httpConfig from './support/httpConfig';
import eventstore from '../index';
import assert from 'assert';
import uuid from 'uuid';
import _ from 'lodash';

describe('Http Client - Check Stream Exist', () => {
	it('Should return true when a stream exist', async() => {
		const client = eventstore.http(httpConfig);

		const testStream = `TestStream-${uuid.v4()}`;
		await client.writeEvent(testStream, 'TestEventType', {
			something: '123'
		});

		assert.equal(await client.checkStreamExists(testStream), true);
	});

	it('Should return false when a stream does not exist', async() => {
		const client = eventstore.http(httpConfig);
		assert.equal(await client.checkStreamExists('Non_existentStream'), false);
	});

	it('Should return rejected promise when the request error is anything other than a 404', callback => {
		const config = _.cloneDeep(httpConfig);
		config.port = 1;
		const client = eventstore.http(config);

		client.checkStreamExists('Non_existentStream_wrong_port_config').then(() => {
			callback('Should not have returned successful promise');
		}).catch(err => {
			assert(err, 'No error received');
			assert(err.message.includes('ECONNREFUSED'), 'Connection refused error expected');
			callback();
		});
	});

	it('Should throw an exception when timeout is reached', callback => {
		const clonedConfig = _.cloneDeep(httpConfig);
		clonedConfig.timeout = 0.00001;

		const client = eventstore.http(clonedConfig);
		const testStream = `TestStream-${uuid.v4()}`;
		client.writeEvent(testStream, 'TestEventType', {
			something: '123'
		}).then(() => client.checkStreamExists(testStream).then(() => {
			callback('Expected to fail');
		})).catch(err => {
			if (err.message.includes('TIMEDOUT')) callback();
			else callback('Time out error expected');
		});
	});
});<|endoftext|>


<commit_before><commit_msg>Use more arguments instead of member variables.<commit_after><|endoftext|>


<commit_before>(function() {

  /**
   * Error
   *
   */
  var Error = function(paramName, paramValue, validatorName, validatorValue, message) {

    var properties = [
      'paramName',
      'paramValue',
      'validatorName',
      'validatorValue',
      'message'
    ];

    for (var i = 0, len = arguments.length; i < len; i++) {
      this[properties[i]] = arguments[i];
    }

    return this;

  };

  /**
   * Each
   *
   * @param {object} list
   * @param {function} iterator
   * @param {function} callback
   */
  var each = function(list, iterator, callback) {

    var queue = [];

    /**
     * AddToQueue
     *
     * @param {string} key
     * @param {string|object} value
     */
    var addToQueue = function(key, value) {
      var index = queue.length + 1;
      queue.push(function() {

        var next = function(error) {
          var fn = queue[index];
          if ( !error && fn ) {
            return fn();
          } else if ( !error && !fn ) {
            return callback(null);
          } else {
            return callback(error);
          }
        };

        return ( key && value ) ? iterator(key, value, next) : iterator(value, next);

      });
    };

    // If the list is an array
    if ( Object.prototype.toString.call(list) === '[object Array]' ) {
      for (var i = 0, len = list.length; i < len; i++) {
        addToQueue(undefined, list[i]);
      }
    }

    // If the list is an object
    if ( Object.prototype.toString.call(list) === '[object Object]' ) {
      for (var key in list) {
        if (list.hasOwnProperty(key)) {
          addToQueue(key, list[key]);
        }
      }
    }

    // And go!
    return queue[0]();

  };

  /**
   * Validators
   *
   * List of validators.
   */
  var validators = {};

  /**
   * ValidatorsList
   *
   * List of validators names, the order is important.
   */
  var validatorsList = [];

  /**
   * ValidateParam
   *
   * @param {string} paramName
   * @param {object} paramValidators
   * @param {string|object} paramValue
   * @param {function} callback
   */
  var validateParam = function(paramName, paramValidators, paramValue, callback) {

    /**
     * Iterator
     *
     * @param {string} validatorName
     * @param {function} callback
     */
    var iterator = function(validatorName, callback) {
      if (paramValidators[validatorName]) {
        var fn = validators[validatorName];
        fn(paramName, paramValue, paramValidators[validatorName], paramValidators, callback);
      } else {
        return callback(null);
      }
    };

    if (paramValidators.required === false && paramValue === undefined) {
      return callback(null);
    } else {
      return each(validatorsList, iterator, callback);
    }

  };

  /**
   * ValidateSchema
   *
   * @param {object} instance
   * @param {object} schema
   * @param {function} callback
   */
  var validateSchema = function(instance, schema, callback) {

    if (schema.required === false && instance === undefined) {
      return callback(null);
    } else {

      /**
       * {
       *   type: 'object',
       *   properties: {
       *     ...
       *   }
       * }
       */
      if (['object', 'array'].indexOf(schema.type) !== -1) {
        return validateParam(undefined, schema, instance, function(error) {
          if (error) {
            return callback(error);
          } else {

            /**
             * {
             *   type: 'object',
             *   properties: {
             *     ...
             *   }
             * }
             */
            if (schema.properties) {
              return each(schema.properties, function(paramName, paramValidators, callback) {
                if ((paramValidators.type === 'object' && paramValidators.properties) || paramValidators.type === 'array' )  {
                  return validateSchema(instance[paramName], schema.properties[paramName], callback);
                } else {
                  return validateParam(paramName, paramValidators, instance[paramName], callback);
                }
              }, callback);

            /**
             * {
             *   type: 'array',
             *   items: {
             *     type: 'string'
             *     ...
             *   }
             * }
             */
            } else if (schema.items) {
              if (['object', 'array'].indexOf(schema.items.type) !== -1) {
                return each(instance, function(item, callback) {
                  return validateSchema(item, schema.items, callback);
                }, callback);
              } else {
                return each(instance, function(item, callback) {
                  return validateParam(undefined, schema.items, item, callback);
                }, callback);
              }

            /**
             * {
             *   type: 'array'
             * }
             */
            } else {
              return callback(null);
            }

          }
        });

      /**
       * {
       *   type: 'string',
       *   length: ...
       * }
       */
      } else {
        return validateParam(undefined, schema, instance, callback);
      }

    }

  };

  /**
   * amanda
   *
   */
  var amanda = {

    /**
     * Validate
     *
     * @param {object} structure
     */
    validate: function() {
      return validateSchema.apply(this, arguments);
    },

    /**
     * AddValidator
     *
     * @param {string} validatorName
     * @param {function} validatorFn
     */
    addValidator: function(validatorName, validatorFn) {
      validatorsList.push(validatorName);
      validators[validatorName] = validatorFn;
    }

  };

  /**
   * Type
   */
  amanda.addValidator('type', (function() {

    /**
     * Types
     */
    var types = {
      'array': function(input) {
        return Array.isArray(input);
      },
      'alpha': function(input) {
        return (typeof input === 'string' && input.match(/^[a-zA-Z]+$/));
      },
      'alphanumeric': function(input) {
        return (typeof input === 'string' && /^[a-zA-Z0-9]+$/.test(input)) || typeof input === 'number';
      },
      'ipv4': function(input) {
        return typeof input === 'string' && input.match(/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/);
      },
      'ipv6': function(input) {
        return typeof input === 'string' && input.match(/(?:(?:[a-f\d]{1,4}:)*(?:[a-f\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?:(?:[a-f\d]{1,4}:)*[a-f\d]{1,4})?::(?:(?:[a-f\d]{1,4}:)*(?:[a-f\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))?)/);
      },
      'ip': function(input) {
        return types.ipv4(input) || types.ipv6;
      },
      'email': function(input) {
        return typeof input === 'string' && input.match(/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/);
      },
      'url': function(input) {
        return typeof input === 'string' && input.match(/^(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?:\w+:\w+@)?((?:(?:[-\w\d{1-3}]+\.)+(?:com|org|cat|coop|int|pro|tel|xxx|net|gov|mil|biz|info|mobi|name|aero|jobs|edu|co\.uk|ac\.uk|it|fr|tv|museum|asia|local|travel|[a-z]{2})?)|((\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)(\.(\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)){3}))(?::[\d]{1,5})?(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?:#(?:[-\w~!$ |\/.,*:;=]|%[a-f\d]{2})*)?$/);
      },
      'date': function(input) {
        return Object.prototype.toString.call(input) === '[object Date]';
      },
      'decimal': function(input) {
        return /^[0-9]+(\.[0-9]{1,2})?$/.test(input);
      },
      'int': function(input) {
        return /^-?[0-9]+$/.test(input);
      },
      'percentage': function(input) {
        return (typeof input === 'string' && input.match(/^-?[0-9]{0,2}(\.[0-9]{1,2})?$|^-?(100)(\.[0]{1,2})?$/));
      },
      'port': function(input) {
        return /\:\d+/.test(input);
      },
      'regexp': function(input) {
        return input && input.test && input.exec;
      },
      'unsignedInt':function(input) {
        return /^[0-9]+$/.test(input);
      }
    };

    // Generate the rest of type checkers
    [
      'string',
      'number',
      'function',
      'object',
      'boolean'
    ].forEach(function(type) {
      types[type] = function() {
        return ( typeof arguments[0] === type ) ? true : false;
      };
    });

    return function(paramName, paramValue, validator, validators, callback) {
      if ( !types[validator](paramValue) ) {
        return callback(new Error(
          paramName,
          paramValue,
          'type',
          validator,
          null
        ));
      } else {
        return callback(null);
      }
    };

  }()));

  /**
   * Length
   */
  amanda.addValidator('length', function(paramName, paramValue, validator, validators, callback) {

    // Check the length only if the type of ‘paramValue’ is string
    if (typeof paramValue === 'string') {

      // If the length is specified as an array (for instance ‘[2, 45]’)
      if (Array.isArray(validator) && (paramValue.length < validator[0] || paramValue.length > validator[1])) {
        return callback(new Error(
          paramName,
          paramValue,
          'length',
          validator,
          null
        ));
      }

      // If the length is specified as a string (for instance ‘2’)
      if (typeof validator === 'number' && paramValue.length !== validator) {
        return callback(new Error(
          paramName,
          paramValue,
          'length',
          validator,
          null
        ));
      }

      return callback(null);

    }

    return callback(null);

  });

  /**
   * Values
   */
  amanda.addValidator('values', function(paramName, paramValue, validator, validators, callback) {
    if (validator.indexOf( paramValue ) === -1) {
      return callback(new Error(
        paramName,
        paramValue,
        'values',
        validator,
        null
      ));
    } else {
      return callback(null);
    }
  });

  /**
   * Except
   */
  amanda.addValidator('except', function(paramName, paramValue, validator, validators, callback) {
    if (validator.indexOf( paramValue ) !== -1) {
      return callback(new Error(
        paramName,
        paramValue,
        'except',
        validator,
        null
      ));
    } else {
      return callback(null);
    }
  });

  /**
   * Min
   */
  amanda.addValidator('min', function(paramName, paramValue, validator, validators, callback) {
    if (typeof paramValue === 'number' && paramValue < validator) {
      return callback(new Error(
        paramName,
        paramValue,
        'min',
        validator,
        null
      ));
    } else {
      return callback(null);
    }
  });

  /**
   * Max
   */
  amanda.addValidator('max', function(paramName, paramValue, validator, validators, callback) {
    if (typeof paramValue === 'number' && paramValue > validator) {
      return callback(new Error(
        paramName,
        paramValue,
        'max',
        validator,
        null
      ));
    } else {
      return callback(null);
    }
  });

  /**
   * Pattern
   */
  amanda.addValidator('pattern', function(paramName, paramValue, validator, validators, callback) {
    if (typeof paramValue === 'string' && !paramValue.match(validator)) {
      return callback(new Error(
        paramName,
        paramValue,
        'pattern',
        validator,
        null
      ));
    } else {
      return callback(null);
    }
  });

  // Export
  if ( typeof module !== 'undefined' && module.exports ) {
    module.exports = amanda;
  } else if ( typeof define !== 'undefined' ) {
    define(function() {
      return amanda;
    });
  } else {
    this.amanda = amanda;
  }

}());<commit_msg>Added a new method ‘getVersion’<commit_after>(function() {

  /**
   * Error
   *
   */
  var Error = function(paramName, paramValue, validatorName, validatorValue, message) {

    var properties = [
      'paramName',
      'paramValue',
      'validatorName',
      'validatorValue',
      'message'
    ];

    for (var i = 0, len = arguments.length; i < len; i++) {
      this[properties[i]] = arguments[i];
    }

    return this;

  };

  /**
   * Each
   *
   * @param {object} list
   * @param {function} iterator
   * @param {function} callback
   */
  var each = function(list, iterator, callback) {

    var queue = [];

    /**
     * AddToQueue
     *
     * @param {string} key
     * @param {string|object} value
     */
    var addToQueue = function(key, value) {
      var index = queue.length + 1;
      queue.push(function() {

        var next = function(error) {
          var fn = queue[index];
          if ( !error && fn ) {
            return fn();
          } else if ( !error && !fn ) {
            return callback(null);
          } else {
            return callback(error);
          }
        };

        return ( key && value ) ? iterator(key, value, next) : iterator(value, next);

      });
    };

    // If the list is an array
    if ( Object.prototype.toString.call(list) === '[object Array]' ) {
      for (var i = 0, len = list.length; i < len; i++) {
        addToQueue(undefined, list[i]);
      }
    }

    // If the list is an object
    if ( Object.prototype.toString.call(list) === '[object Object]' ) {
      for (var key in list) {
        if (list.hasOwnProperty(key)) {
          addToQueue(key, list[key]);
        }
      }
    }

    // And go!
    return queue[0]();

  };

  /**
   * Validators
   *
   * List of validators.
   */
  var validators = {};

  /**
   * ValidatorsList
   *
   * List of validators names, the order is important.
   */
  var validatorsList = [];

  /**
   * ValidateParam
   *
   * @param {string} paramName
   * @param {object} paramValidators
   * @param {string|object} paramValue
   * @param {function} callback
   */
  var validateParam = function(paramName, paramValidators, paramValue, callback) {

    /**
     * Iterator
     *
     * @param {string} validatorName
     * @param {function} callback
     */
    var iterator = function(validatorName, callback) {
      if (paramValidators[validatorName]) {
        var fn = validators[validatorName];
        fn(paramName, paramValue, paramValidators[validatorName], paramValidators, callback);
      } else {
        return callback(null);
      }
    };

    if (paramValidators.required === false && paramValue === undefined) {
      return callback(null);
    } else {
      return each(validatorsList, iterator, callback);
    }

  };

  /**
   * ValidateSchema
   *
   * @param {object} instance
   * @param {object} schema
   * @param {function} callback
   */
  var validateSchema = function(instance, schema, callback) {

    if (schema.required === false && instance === undefined) {
      return callback(null);
    } else {

      /**
       * {
       *   type: 'object',
       *   properties: {
       *     ...
       *   }
       * }
       */
      if (['object', 'array'].indexOf(schema.type) !== -1) {
        return validateParam(undefined, schema, instance, function(error) {
          if (error) {
            return callback(error);
          } else {

            /**
             * {
             *   type: 'object',
             *   properties: {
             *     ...
             *   }
             * }
             */
            if (schema.properties) {
              return each(schema.properties, function(paramName, paramValidators, callback) {
                if ((paramValidators.type === 'object' && paramValidators.properties) || paramValidators.type === 'array' )  {
                  return validateSchema(instance[paramName], schema.properties[paramName], callback);
                } else {
                  return validateParam(paramName, paramValidators, instance[paramName], callback);
                }
              }, callback);

            /**
             * {
             *   type: 'array',
             *   items: {
             *     type: 'string'
             *     ...
             *   }
             * }
             */
            } else if (schema.items) {
              if (['object', 'array'].indexOf(schema.items.type) !== -1) {
                return each(instance, function(item, callback) {
                  return validateSchema(item, schema.items, callback);
                }, callback);
              } else {
                return each(instance, function(item, callback) {
                  return validateParam(undefined, schema.items, item, callback);
                }, callback);
              }

            /**
             * {
             *   type: 'array'
             * }
             */
            } else {
              return callback(null);
            }

          }
        });

      /**
       * {
       *   type: 'string',
       *   length: ...
       * }
       */
      } else {
        return validateParam(undefined, schema, instance, callback);
      }

    }

  };

  /**
   * amanda
   *
   */
  var amanda = {

    /**
     * Validate
     *
     * @param {object} structure
     */
    validate: function() {
      return validateSchema.apply(this, arguments);
    },

    /**
     * AddValidator
     *
     * @param {string} validatorName
     * @param {function} validatorFn
     */
    addValidator: function(validatorName, validatorFn) {
      validatorsList.push(validatorName);
      validators[validatorName] = validatorFn;
    },

    /**
     * getVersion
     */
    getVersion: function() {
      return [0, 0, 1].join('.');
    }

  };

  /**
   * Type
   */
  amanda.addValidator('type', (function() {

    /**
     * Types
     */
    var types = {
      'array': function(input) {
        return Array.isArray(input);
      },
      'alpha': function(input) {
        return (typeof input === 'string' && input.match(/^[a-zA-Z]+$/));
      },
      'alphanumeric': function(input) {
        return (typeof input === 'string' && /^[a-zA-Z0-9]+$/.test(input)) || typeof input === 'number';
      },
      'ipv4': function(input) {
        return typeof input === 'string' && input.match(/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/);
      },
      'ipv6': function(input) {
        return typeof input === 'string' && input.match(/(?:(?:[a-f\d]{1,4}:)*(?:[a-f\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?:(?:[a-f\d]{1,4}:)*[a-f\d]{1,4})?::(?:(?:[a-f\d]{1,4}:)*(?:[a-f\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))?)/);
      },
      'ip': function(input) {
        return types.ipv4(input) || types.ipv6;
      },
      'email': function(input) {
        return typeof input === 'string' && input.match(/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/);
      },
      'url': function(input) {
        return typeof input === 'string' && input.match(/^(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?:\w+:\w+@)?((?:(?:[-\w\d{1-3}]+\.)+(?:com|org|cat|coop|int|pro|tel|xxx|net|gov|mil|biz|info|mobi|name|aero|jobs|edu|co\.uk|ac\.uk|it|fr|tv|museum|asia|local|travel|[a-z]{2})?)|((\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)(\.(\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)){3}))(?::[\d]{1,5})?(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?:#(?:[-\w~!$ |\/.,*:;=]|%[a-f\d]{2})*)?$/);
      },
      'date': function(input) {
        return Object.prototype.toString.call(input) === '[object Date]';
      },
      'decimal': function(input) {
        return /^[0-9]+(\.[0-9]{1,2})?$/.test(input);
      },
      'int': function(input) {
        return /^-?[0-9]+$/.test(input);
      },
      'percentage': function(input) {
        return (typeof input === 'string' && input.match(/^-?[0-9]{0,2}(\.[0-9]{1,2})?$|^-?(100)(\.[0]{1,2})?$/));
      },
      'port': function(input) {
        return /\:\d+/.test(input);
      },
      'regexp': function(input) {
        return input && input.test && input.exec;
      },
      'unsignedInt':function(input) {
        return /^[0-9]+$/.test(input);
      }
    };

    // Generate the rest of type checkers
    [
      'string',
      'number',
      'function',
      'object',
      'boolean'
    ].forEach(function(type) {
      types[type] = function() {
        return ( typeof arguments[0] === type ) ? true : false;
      };
    });

    return function(paramName, paramValue, validator, validators, callback) {
      if ( !types[validator](paramValue) ) {
        return callback(new Error(
          paramName,
          paramValue,
          'type',
          validator,
          null
        ));
      } else {
        return callback(null);
      }
    };

  }()));

  /**
   * Length
   */
  amanda.addValidator('length', function(paramName, paramValue, validator, validators, callback) {

    // Check the length only if the type of ‘paramValue’ is string
    if (typeof paramValue === 'string') {

      // If the length is specified as an array (for instance ‘[2, 45]’)
      if (Array.isArray(validator) && (paramValue.length < validator[0] || paramValue.length > validator[1])) {
        return callback(new Error(
          paramName,
          paramValue,
          'length',
          validator,
          null
        ));
      }

      // If the length is specified as a string (for instance ‘2’)
      if (typeof validator === 'number' && paramValue.length !== validator) {
        return callback(new Error(
          paramName,
          paramValue,
          'length',
          validator,
          null
        ));
      }

      return callback(null);

    }

    return callback(null);

  });

  /**
   * Values
   */
  amanda.addValidator('values', function(paramName, paramValue, validator, validators, callback) {
    if (validator.indexOf( paramValue ) === -1) {
      return callback(new Error(
        paramName,
        paramValue,
        'values',
        validator,
        null
      ));
    } else {
      return callback(null);
    }
  });

  /**
   * Except
   */
  amanda.addValidator('except', function(paramName, paramValue, validator, validators, callback) {
    if (validator.indexOf( paramValue ) !== -1) {
      return callback(new Error(
        paramName,
        paramValue,
        'except',
        validator,
        null
      ));
    } else {
      return callback(null);
    }
  });

  /**
   * Min
   */
  amanda.addValidator('min', function(paramName, paramValue, validator, validators, callback) {
    if (typeof paramValue === 'number' && paramValue < validator) {
      return callback(new Error(
        paramName,
        paramValue,
        'min',
        validator,
        null
      ));
    } else {
      return callback(null);
    }
  });

  /**
   * Max
   */
  amanda.addValidator('max', function(paramName, paramValue, validator, validators, callback) {
    if (typeof paramValue === 'number' && paramValue > validator) {
      return callback(new Error(
        paramName,
        paramValue,
        'max',
        validator,
        null
      ));
    } else {
      return callback(null);
    }
  });

  /**
   * Pattern
   */
  amanda.addValidator('pattern', function(paramName, paramValue, validator, validators, callback) {
    if (typeof paramValue === 'string' && !paramValue.match(validator)) {
      return callback(new Error(
        paramName,
        paramValue,
        'pattern',
        validator,
        null
      ));
    } else {
      return callback(null);
    }
  });

  // Export
  if ( typeof module !== 'undefined' && module.exports ) {
    module.exports = amanda;
  } else if ( typeof define !== 'undefined' ) {
    define(function() {
      return amanda;
    });
  } else {
    this.amanda = amanda;
  }

}());<|endoftext|>


<commit_before><commit_msg>Remove unnecessary js file.<commit_after><|endoftext|>