├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── dist ├── pouchdb.migrate.js └── pouchdb.migrate.min.js ├── index.js ├── lib ├── checkpointer.js ├── md5.js └── migrate.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: false 8 | node_js: 9 | - 4 10 | - iojs-v3 11 | - iojs-v2 12 | - iojs-v1 13 | - '0.12' 14 | - '0.10' 15 | before_install: 16 | - npm i -g npm@^2.0.0 17 | before_script: 18 | - npm prune 19 | after_success: 20 | - npm run build 21 | - npm run semantic-release 22 | branches: 23 | except: 24 | # ignore git tags created by semantic-release, like "v1.2.3" 25 | - /^v\d+\.\d+\.\d+$/ 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Licensed under the Apache License, Version 2.0 (the "License"); you 2 | may not use this file except in compliance with the License. You may 3 | obtain a copy of the License at 4 | 5 | http://www.apache.org/licenses/LICENSE-2.0 6 | 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 10 | implied. See the License for the specific language governing 11 | permissions and limitations under the License. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pouchdb-migrate 2 | PouchDB plugin for running migrations. 3 | 4 | [![Build 5 | Status](https://travis-ci.org/eHealthAfrica/pouchdb-migrate.svg?branch=master)](https://travis-ci.org/eHealthAfrica/pouchdb-migrate) 6 | 7 | ## Setup 8 | ```html 9 | 10 | 11 | ``` 12 | 13 | Or to use it in Node.js, just npm install it: 14 | 15 | ``` 16 | npm install pouchdb-migrate 17 | ``` 18 | 19 | And then attach it to the `PouchDB` object: 20 | 21 | ```js 22 | var PouchDB = require('pouchdb'); 23 | PouchDB.plugin(require('pouchdb-migrate')); 24 | ``` 25 | 26 | ## Usage 27 | ```js 28 | var db = new PouchDB('mydb') 29 | 30 | // Migration script 31 | // * Return falsy value to skip the doc 32 | // * Make sure to prevent from loops 33 | var migration = function(doc) { 34 | if ('foo' in doc) return 35 | 36 | doc.foo = 'bar' 37 | return [doc] 38 | } 39 | 40 | db.migrate(migration) 41 | .then //... every doc has `foo` now 42 | ``` 43 | 44 | ## Testing 45 | Run the tests with 46 | ```sh 47 | npm test 48 | ``` 49 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pouchdb-migrate", 3 | "main": "dist/pouchdb.migrate.js", 4 | "version": "1.1.1", 5 | "homepage": "https://github.com/eHealthAfrica/pouchdb-migrate", 6 | "authors": [ 7 | "Johannes J. Schmidt " 8 | ], 9 | "description": "PouchDB plugin for running migrations.", 10 | "moduleType": [ 11 | "node" 12 | ], 13 | "keywords": [ 14 | "pouchdb" 15 | ], 16 | "license": "Apache-2.0", 17 | "ignore": [ 18 | "**/.*", 19 | "node_modules", 20 | "bower_components", 21 | "test", 22 | "tests" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /dist/pouchdb.migrate.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o>> 8) & 0xff), 159 | ((int >>> 16) & 0xff), 160 | ((int >>> 24) & 0xff) 161 | ]; 162 | return bytes.map(function (byte) { 163 | return String.fromCharCode(byte); 164 | }).join(''); 165 | } 166 | 167 | // convert an array of 64-bit ints into 168 | // a base64-encoded string 169 | /* istanbul ignore next */ 170 | function rawToBase64(raw) { 171 | var res = ''; 172 | for (var i = 0; i < raw.length; i++) { 173 | res += intToString(raw[i]); 174 | } 175 | return global.btoa(res); 176 | } 177 | 178 | /* istanbul ignore next */ 179 | module.exports = function (data, callback) { 180 | if (!process.browser) { 181 | var base64 = crypto.createHash('md5').update(data).digest('base64'); 182 | callback(null, base64); 183 | return; 184 | } 185 | var inputIsString = typeof data === 'string'; 186 | var len = inputIsString ? data.length : data.byteLength; 187 | var chunkSize = Math.min(MD5_CHUNK_SIZE, len); 188 | var chunks = Math.ceil(len / chunkSize); 189 | var currentChunk = 0; 190 | var buffer = inputIsString ? new Md5() : new Md5.ArrayBuffer(); 191 | 192 | function append(buffer, data, start, end) { 193 | if (inputIsString) { 194 | buffer.appendBinary(data.substring(start, end)); 195 | } else { 196 | buffer.append(sliceShim(data, start, end)); 197 | } 198 | } 199 | 200 | function loadNextChunk() { 201 | var start = currentChunk * chunkSize; 202 | var end = start + chunkSize; 203 | if ((start + chunkSize) >= data.size) { 204 | end = data.size; 205 | } 206 | currentChunk++; 207 | if (currentChunk < chunks) { 208 | append(buffer, data, start, end); 209 | setImmediateShim(loadNextChunk); 210 | } else { 211 | append(buffer, data, start, end); 212 | var raw = buffer.end(true); 213 | var base64 = rawToBase64(raw); 214 | callback(null, base64); 215 | buffer.destroy(); 216 | } 217 | } 218 | loadNextChunk(); 219 | }; 220 | 221 | }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 222 | },{"_process":12,"crypto":6,"spark-md5":13}],4:[function(require,module,exports){ 223 | var Promise = require('pouchdb/extras/promise') 224 | var extend = require('pouchdb-extend') 225 | var async = require('async') 226 | 227 | module.exports = function migrate(db, checkpointer, migration, options) { 228 | return checkpointer.get() 229 | .then(function(since) { 230 | return new Promise(function(resolve, reject) { 231 | var docs = [] 232 | 233 | var queue = async.queue(function(result, next) { 234 | if (!result) { 235 | return checkpointer 236 | .set(change.seq) 237 | .then(next.bind({}, null)) 238 | .catch(next) 239 | } 240 | 241 | db 242 | .bulkDocs({ docs: result }) 243 | .then(function(response) { 244 | return checkpointer.set(change.seq) 245 | }) 246 | .then(next.bind({}, null)) 247 | .catch(next) 248 | }, 1) 249 | 250 | 251 | var feed = db.changes(extend({}, options, { 252 | include_docs: true, 253 | since: since 254 | })) 255 | 256 | feed.on('change', function(change) { 257 | var result = migration(change.doc) 258 | 259 | if (!options.live) { 260 | docs = result ? docs.concat(result) : docs 261 | return 262 | } 263 | 264 | queue.push(result) 265 | }) 266 | 267 | feed.on('complete', function(info) { 268 | if (options.live) { return resolve(info) } 269 | 270 | db 271 | .bulkDocs({ docs: docs }) 272 | .then(function(response) { 273 | return checkpointer.set(info.last_seq) 274 | }) 275 | .then(function() { 276 | return db.info() 277 | }) 278 | .then(function(dbinfo) { 279 | if (dbinfo.update_seq > info.last_seq) { 280 | return migrate(db, checkpointer, migration, options) 281 | } 282 | return info 283 | }) 284 | .then(resolve) 285 | }) 286 | 287 | feed.on('error', reject) 288 | }) 289 | }) 290 | } 291 | 292 | },{"async":5,"pouchdb-extend":9,"pouchdb/extras/promise":10}],5:[function(require,module,exports){ 293 | (function (process,global){ 294 | /*! 295 | * async 296 | * https://github.com/caolan/async 297 | * 298 | * Copyright 2010-2014 Caolan McMahon 299 | * Released under the MIT license 300 | */ 301 | (function () { 302 | 303 | var async = {}; 304 | function noop() {} 305 | function identity(v) { 306 | return v; 307 | } 308 | function toBool(v) { 309 | return !!v; 310 | } 311 | function notId(v) { 312 | return !v; 313 | } 314 | 315 | // global on the server, window in the browser 316 | var previous_async; 317 | 318 | // Establish the root object, `window` (`self`) in the browser, `global` 319 | // on the server, or `this` in some virtual machines. We use `self` 320 | // instead of `window` for `WebWorker` support. 321 | var root = typeof self === 'object' && self.self === self && self || 322 | typeof global === 'object' && global.global === global && global || 323 | this; 324 | 325 | if (root != null) { 326 | previous_async = root.async; 327 | } 328 | 329 | async.noConflict = function () { 330 | root.async = previous_async; 331 | return async; 332 | }; 333 | 334 | function only_once(fn) { 335 | return function() { 336 | if (fn === null) throw new Error("Callback was already called."); 337 | fn.apply(this, arguments); 338 | fn = null; 339 | }; 340 | } 341 | 342 | function _once(fn) { 343 | return function() { 344 | if (fn === null) return; 345 | fn.apply(this, arguments); 346 | fn = null; 347 | }; 348 | } 349 | 350 | //// cross-browser compatiblity functions //// 351 | 352 | var _toString = Object.prototype.toString; 353 | 354 | var _isArray = Array.isArray || function (obj) { 355 | return _toString.call(obj) === '[object Array]'; 356 | }; 357 | 358 | // Ported from underscore.js isObject 359 | var _isObject = function(obj) { 360 | var type = typeof obj; 361 | return type === 'function' || type === 'object' && !!obj; 362 | }; 363 | 364 | function _isArrayLike(arr) { 365 | return _isArray(arr) || ( 366 | // has a positive integer length property 367 | typeof arr.length === "number" && 368 | arr.length >= 0 && 369 | arr.length % 1 === 0 370 | ); 371 | } 372 | 373 | function _each(coll, iterator) { 374 | return _isArrayLike(coll) ? 375 | _arrayEach(coll, iterator) : 376 | _forEachOf(coll, iterator); 377 | } 378 | 379 | function _arrayEach(arr, iterator) { 380 | var index = -1, 381 | length = arr.length; 382 | 383 | while (++index < length) { 384 | iterator(arr[index], index, arr); 385 | } 386 | } 387 | 388 | function _map(arr, iterator) { 389 | var index = -1, 390 | length = arr.length, 391 | result = Array(length); 392 | 393 | while (++index < length) { 394 | result[index] = iterator(arr[index], index, arr); 395 | } 396 | return result; 397 | } 398 | 399 | function _range(count) { 400 | return _map(Array(count), function (v, i) { return i; }); 401 | } 402 | 403 | function _reduce(arr, iterator, memo) { 404 | _arrayEach(arr, function (x, i, a) { 405 | memo = iterator(memo, x, i, a); 406 | }); 407 | return memo; 408 | } 409 | 410 | function _forEachOf(object, iterator) { 411 | _arrayEach(_keys(object), function (key) { 412 | iterator(object[key], key); 413 | }); 414 | } 415 | 416 | function _indexOf(arr, item) { 417 | for (var i = 0; i < arr.length; i++) { 418 | if (arr[i] === item) return i; 419 | } 420 | return -1; 421 | } 422 | 423 | var _keys = Object.keys || function (obj) { 424 | var keys = []; 425 | for (var k in obj) { 426 | if (obj.hasOwnProperty(k)) { 427 | keys.push(k); 428 | } 429 | } 430 | return keys; 431 | }; 432 | 433 | function _keyIterator(coll) { 434 | var i = -1; 435 | var len; 436 | var keys; 437 | if (_isArrayLike(coll)) { 438 | len = coll.length; 439 | return function next() { 440 | i++; 441 | return i < len ? i : null; 442 | }; 443 | } else { 444 | keys = _keys(coll); 445 | len = keys.length; 446 | return function next() { 447 | i++; 448 | return i < len ? keys[i] : null; 449 | }; 450 | } 451 | } 452 | 453 | // Similar to ES6's rest param (http://ariya.ofilabs.com/2013/03/es6-and-rest-parameter.html) 454 | // This accumulates the arguments passed into an array, after a given index. 455 | // From underscore.js (https://github.com/jashkenas/underscore/pull/2140). 456 | function _restParam(func, startIndex) { 457 | startIndex = startIndex == null ? func.length - 1 : +startIndex; 458 | return function() { 459 | var length = Math.max(arguments.length - startIndex, 0); 460 | var rest = Array(length); 461 | for (var index = 0; index < length; index++) { 462 | rest[index] = arguments[index + startIndex]; 463 | } 464 | switch (startIndex) { 465 | case 0: return func.call(this, rest); 466 | case 1: return func.call(this, arguments[0], rest); 467 | } 468 | // Currently unused but handle cases outside of the switch statement: 469 | // var args = Array(startIndex + 1); 470 | // for (index = 0; index < startIndex; index++) { 471 | // args[index] = arguments[index]; 472 | // } 473 | // args[startIndex] = rest; 474 | // return func.apply(this, args); 475 | }; 476 | } 477 | 478 | function _withoutIndex(iterator) { 479 | return function (value, index, callback) { 480 | return iterator(value, callback); 481 | }; 482 | } 483 | 484 | //// exported async module functions //// 485 | 486 | //// nextTick implementation with browser-compatible fallback //// 487 | 488 | // capture the global reference to guard against fakeTimer mocks 489 | var _setImmediate = typeof setImmediate === 'function' && setImmediate; 490 | 491 | var _delay = _setImmediate ? function(fn) { 492 | // not a direct alias for IE10 compatibility 493 | _setImmediate(fn); 494 | } : function(fn) { 495 | setTimeout(fn, 0); 496 | }; 497 | 498 | if (typeof process === 'object' && typeof process.nextTick === 'function') { 499 | async.nextTick = process.nextTick; 500 | } else { 501 | async.nextTick = _delay; 502 | } 503 | async.setImmediate = _setImmediate ? _delay : async.nextTick; 504 | 505 | 506 | async.forEach = 507 | async.each = function (arr, iterator, callback) { 508 | return async.eachOf(arr, _withoutIndex(iterator), callback); 509 | }; 510 | 511 | async.forEachSeries = 512 | async.eachSeries = function (arr, iterator, callback) { 513 | return async.eachOfSeries(arr, _withoutIndex(iterator), callback); 514 | }; 515 | 516 | 517 | async.forEachLimit = 518 | async.eachLimit = function (arr, limit, iterator, callback) { 519 | return _eachOfLimit(limit)(arr, _withoutIndex(iterator), callback); 520 | }; 521 | 522 | async.forEachOf = 523 | async.eachOf = function (object, iterator, callback) { 524 | callback = _once(callback || noop); 525 | object = object || []; 526 | var size = _isArrayLike(object) ? object.length : _keys(object).length; 527 | var completed = 0; 528 | if (!size) { 529 | return callback(null); 530 | } 531 | _each(object, function (value, key) { 532 | iterator(object[key], key, only_once(done)); 533 | }); 534 | function done(err) { 535 | if (err) { 536 | callback(err); 537 | } 538 | else { 539 | completed += 1; 540 | if (completed >= size) { 541 | callback(null); 542 | } 543 | } 544 | } 545 | }; 546 | 547 | async.forEachOfSeries = 548 | async.eachOfSeries = function (obj, iterator, callback) { 549 | callback = _once(callback || noop); 550 | obj = obj || []; 551 | var nextKey = _keyIterator(obj); 552 | var key = nextKey(); 553 | function iterate() { 554 | var sync = true; 555 | if (key === null) { 556 | return callback(null); 557 | } 558 | iterator(obj[key], key, only_once(function (err) { 559 | if (err) { 560 | callback(err); 561 | } 562 | else { 563 | key = nextKey(); 564 | if (key === null) { 565 | return callback(null); 566 | } else { 567 | if (sync) { 568 | async.nextTick(iterate); 569 | } else { 570 | iterate(); 571 | } 572 | } 573 | } 574 | })); 575 | sync = false; 576 | } 577 | iterate(); 578 | }; 579 | 580 | 581 | 582 | async.forEachOfLimit = 583 | async.eachOfLimit = function (obj, limit, iterator, callback) { 584 | _eachOfLimit(limit)(obj, iterator, callback); 585 | }; 586 | 587 | function _eachOfLimit(limit) { 588 | 589 | return function (obj, iterator, callback) { 590 | callback = _once(callback || noop); 591 | obj = obj || []; 592 | var nextKey = _keyIterator(obj); 593 | if (limit <= 0) { 594 | return callback(null); 595 | } 596 | var done = false; 597 | var running = 0; 598 | var errored = false; 599 | 600 | (function replenish () { 601 | if (done && running <= 0) { 602 | return callback(null); 603 | } 604 | 605 | while (running < limit && !errored) { 606 | var key = nextKey(); 607 | if (key === null) { 608 | done = true; 609 | if (running <= 0) { 610 | callback(null); 611 | } 612 | return; 613 | } 614 | running += 1; 615 | iterator(obj[key], key, only_once(function (err) { 616 | running -= 1; 617 | if (err) { 618 | callback(err); 619 | errored = true; 620 | } 621 | else { 622 | replenish(); 623 | } 624 | })); 625 | } 626 | })(); 627 | }; 628 | } 629 | 630 | 631 | function doParallel(fn) { 632 | return function (obj, iterator, callback) { 633 | return fn(async.eachOf, obj, iterator, callback); 634 | }; 635 | } 636 | function doParallelLimit(fn) { 637 | return function (obj, limit, iterator, callback) { 638 | return fn(_eachOfLimit(limit), obj, iterator, callback); 639 | }; 640 | } 641 | function doSeries(fn) { 642 | return function (obj, iterator, callback) { 643 | return fn(async.eachOfSeries, obj, iterator, callback); 644 | }; 645 | } 646 | 647 | function _asyncMap(eachfn, arr, iterator, callback) { 648 | callback = _once(callback || noop); 649 | var results = []; 650 | eachfn(arr, function (value, index, callback) { 651 | iterator(value, function (err, v) { 652 | results[index] = v; 653 | callback(err); 654 | }); 655 | }, function (err) { 656 | callback(err, results); 657 | }); 658 | } 659 | 660 | async.map = doParallel(_asyncMap); 661 | async.mapSeries = doSeries(_asyncMap); 662 | async.mapLimit = doParallelLimit(_asyncMap); 663 | 664 | // reduce only has a series version, as doing reduce in parallel won't 665 | // work in many situations. 666 | async.inject = 667 | async.foldl = 668 | async.reduce = function (arr, memo, iterator, callback) { 669 | async.eachOfSeries(arr, function (x, i, callback) { 670 | iterator(memo, x, function (err, v) { 671 | memo = v; 672 | callback(err); 673 | }); 674 | }, function (err) { 675 | callback(err || null, memo); 676 | }); 677 | }; 678 | 679 | async.foldr = 680 | async.reduceRight = function (arr, memo, iterator, callback) { 681 | var reversed = _map(arr, identity).reverse(); 682 | async.reduce(reversed, memo, iterator, callback); 683 | }; 684 | 685 | function _filter(eachfn, arr, iterator, callback) { 686 | var results = []; 687 | eachfn(arr, function (x, index, callback) { 688 | iterator(x, function (v) { 689 | if (v) { 690 | results.push({index: index, value: x}); 691 | } 692 | callback(); 693 | }); 694 | }, function () { 695 | callback(_map(results.sort(function (a, b) { 696 | return a.index - b.index; 697 | }), function (x) { 698 | return x.value; 699 | })); 700 | }); 701 | } 702 | 703 | async.select = 704 | async.filter = doParallel(_filter); 705 | 706 | async.selectLimit = 707 | async.filterLimit = doParallelLimit(_filter); 708 | 709 | async.selectSeries = 710 | async.filterSeries = doSeries(_filter); 711 | 712 | function _reject(eachfn, arr, iterator, callback) { 713 | _filter(eachfn, arr, function(value, cb) { 714 | iterator(value, function(v) { 715 | cb(!v); 716 | }); 717 | }, callback); 718 | } 719 | async.reject = doParallel(_reject); 720 | async.rejectLimit = doParallelLimit(_reject); 721 | async.rejectSeries = doSeries(_reject); 722 | 723 | function _createTester(eachfn, check, getResult) { 724 | return function(arr, limit, iterator, cb) { 725 | function done() { 726 | if (cb) cb(getResult(false, void 0)); 727 | } 728 | function iteratee(x, _, callback) { 729 | if (!cb) return callback(); 730 | iterator(x, function (v) { 731 | if (cb && check(v)) { 732 | cb(getResult(true, x)); 733 | cb = iterator = false; 734 | } 735 | callback(); 736 | }); 737 | } 738 | if (arguments.length > 3) { 739 | eachfn(arr, limit, iteratee, done); 740 | } else { 741 | cb = iterator; 742 | iterator = limit; 743 | eachfn(arr, iteratee, done); 744 | } 745 | }; 746 | } 747 | 748 | async.any = 749 | async.some = _createTester(async.eachOf, toBool, identity); 750 | 751 | async.someLimit = _createTester(async.eachOfLimit, toBool, identity); 752 | 753 | async.all = 754 | async.every = _createTester(async.eachOf, notId, notId); 755 | 756 | async.everyLimit = _createTester(async.eachOfLimit, notId, notId); 757 | 758 | function _findGetResult(v, x) { 759 | return x; 760 | } 761 | async.detect = _createTester(async.eachOf, identity, _findGetResult); 762 | async.detectSeries = _createTester(async.eachOfSeries, identity, _findGetResult); 763 | async.detectLimit = _createTester(async.eachOfLimit, identity, _findGetResult); 764 | 765 | async.sortBy = function (arr, iterator, callback) { 766 | async.map(arr, function (x, callback) { 767 | iterator(x, function (err, criteria) { 768 | if (err) { 769 | callback(err); 770 | } 771 | else { 772 | callback(null, {value: x, criteria: criteria}); 773 | } 774 | }); 775 | }, function (err, results) { 776 | if (err) { 777 | return callback(err); 778 | } 779 | else { 780 | callback(null, _map(results.sort(comparator), function (x) { 781 | return x.value; 782 | })); 783 | } 784 | 785 | }); 786 | 787 | function comparator(left, right) { 788 | var a = left.criteria, b = right.criteria; 789 | return a < b ? -1 : a > b ? 1 : 0; 790 | } 791 | }; 792 | 793 | async.auto = function (tasks, callback) { 794 | callback = _once(callback || noop); 795 | var keys = _keys(tasks); 796 | var remainingTasks = keys.length; 797 | if (!remainingTasks) { 798 | return callback(null); 799 | } 800 | 801 | var results = {}; 802 | 803 | var listeners = []; 804 | function addListener(fn) { 805 | listeners.unshift(fn); 806 | } 807 | function removeListener(fn) { 808 | var idx = _indexOf(listeners, fn); 809 | if (idx >= 0) listeners.splice(idx, 1); 810 | } 811 | function taskComplete() { 812 | remainingTasks--; 813 | _arrayEach(listeners.slice(0), function (fn) { 814 | fn(); 815 | }); 816 | } 817 | 818 | addListener(function () { 819 | if (!remainingTasks) { 820 | callback(null, results); 821 | } 822 | }); 823 | 824 | _arrayEach(keys, function (k) { 825 | var task = _isArray(tasks[k]) ? tasks[k]: [tasks[k]]; 826 | var taskCallback = _restParam(function(err, args) { 827 | if (args.length <= 1) { 828 | args = args[0]; 829 | } 830 | if (err) { 831 | var safeResults = {}; 832 | _forEachOf(results, function(val, rkey) { 833 | safeResults[rkey] = val; 834 | }); 835 | safeResults[k] = args; 836 | callback(err, safeResults); 837 | } 838 | else { 839 | results[k] = args; 840 | async.setImmediate(taskComplete); 841 | } 842 | }); 843 | var requires = task.slice(0, task.length - 1); 844 | // prevent dead-locks 845 | var len = requires.length; 846 | var dep; 847 | while (len--) { 848 | if (!(dep = tasks[requires[len]])) { 849 | throw new Error('Has inexistant dependency'); 850 | } 851 | if (_isArray(dep) && _indexOf(dep, k) >= 0) { 852 | throw new Error('Has cyclic dependencies'); 853 | } 854 | } 855 | function ready() { 856 | return _reduce(requires, function (a, x) { 857 | return (a && results.hasOwnProperty(x)); 858 | }, true) && !results.hasOwnProperty(k); 859 | } 860 | if (ready()) { 861 | task[task.length - 1](taskCallback, results); 862 | } 863 | else { 864 | addListener(listener); 865 | } 866 | function listener() { 867 | if (ready()) { 868 | removeListener(listener); 869 | task[task.length - 1](taskCallback, results); 870 | } 871 | } 872 | }); 873 | }; 874 | 875 | 876 | 877 | async.retry = function(times, task, callback) { 878 | var DEFAULT_TIMES = 5; 879 | var DEFAULT_INTERVAL = 0; 880 | 881 | var attempts = []; 882 | 883 | var opts = { 884 | times: DEFAULT_TIMES, 885 | interval: DEFAULT_INTERVAL 886 | }; 887 | 888 | function parseTimes(acc, t){ 889 | if(typeof t === 'number'){ 890 | acc.times = parseInt(t, 10) || DEFAULT_TIMES; 891 | } else if(typeof t === 'object'){ 892 | acc.times = parseInt(t.times, 10) || DEFAULT_TIMES; 893 | acc.interval = parseInt(t.interval, 10) || DEFAULT_INTERVAL; 894 | } else { 895 | throw new Error('Unsupported argument type for \'times\': ' + typeof t); 896 | } 897 | } 898 | 899 | var length = arguments.length; 900 | if (length < 1 || length > 3) { 901 | throw new Error('Invalid arguments - must be either (task), (task, callback), (times, task) or (times, task, callback)'); 902 | } else if (length <= 2 && typeof times === 'function') { 903 | callback = task; 904 | task = times; 905 | } 906 | if (typeof times !== 'function') { 907 | parseTimes(opts, times); 908 | } 909 | opts.callback = callback; 910 | opts.task = task; 911 | 912 | function wrappedTask(wrappedCallback, wrappedResults) { 913 | function retryAttempt(task, finalAttempt) { 914 | return function(seriesCallback) { 915 | task(function(err, result){ 916 | seriesCallback(!err || finalAttempt, {err: err, result: result}); 917 | }, wrappedResults); 918 | }; 919 | } 920 | 921 | function retryInterval(interval){ 922 | return function(seriesCallback){ 923 | setTimeout(function(){ 924 | seriesCallback(null); 925 | }, interval); 926 | }; 927 | } 928 | 929 | while (opts.times) { 930 | 931 | var finalAttempt = !(opts.times-=1); 932 | attempts.push(retryAttempt(opts.task, finalAttempt)); 933 | if(!finalAttempt && opts.interval > 0){ 934 | attempts.push(retryInterval(opts.interval)); 935 | } 936 | } 937 | 938 | async.series(attempts, function(done, data){ 939 | data = data[data.length - 1]; 940 | (wrappedCallback || opts.callback)(data.err, data.result); 941 | }); 942 | } 943 | 944 | // If a callback is passed, run this as a controll flow 945 | return opts.callback ? wrappedTask() : wrappedTask; 946 | }; 947 | 948 | async.waterfall = function (tasks, callback) { 949 | callback = _once(callback || noop); 950 | if (!_isArray(tasks)) { 951 | var err = new Error('First argument to waterfall must be an array of functions'); 952 | return callback(err); 953 | } 954 | if (!tasks.length) { 955 | return callback(); 956 | } 957 | function wrapIterator(iterator) { 958 | return _restParam(function (err, args) { 959 | if (err) { 960 | callback.apply(null, [err].concat(args)); 961 | } 962 | else { 963 | var next = iterator.next(); 964 | if (next) { 965 | args.push(wrapIterator(next)); 966 | } 967 | else { 968 | args.push(callback); 969 | } 970 | ensureAsync(iterator).apply(null, args); 971 | } 972 | }); 973 | } 974 | wrapIterator(async.iterator(tasks))(); 975 | }; 976 | 977 | function _parallel(eachfn, tasks, callback) { 978 | callback = callback || noop; 979 | var results = _isArrayLike(tasks) ? [] : {}; 980 | 981 | eachfn(tasks, function (task, key, callback) { 982 | task(_restParam(function (err, args) { 983 | if (args.length <= 1) { 984 | args = args[0]; 985 | } 986 | results[key] = args; 987 | callback(err); 988 | })); 989 | }, function (err) { 990 | callback(err, results); 991 | }); 992 | } 993 | 994 | async.parallel = function (tasks, callback) { 995 | _parallel(async.eachOf, tasks, callback); 996 | }; 997 | 998 | async.parallelLimit = function(tasks, limit, callback) { 999 | _parallel(_eachOfLimit(limit), tasks, callback); 1000 | }; 1001 | 1002 | async.series = function(tasks, callback) { 1003 | _parallel(async.eachOfSeries, tasks, callback); 1004 | }; 1005 | 1006 | async.iterator = function (tasks) { 1007 | function makeCallback(index) { 1008 | function fn() { 1009 | if (tasks.length) { 1010 | tasks[index].apply(null, arguments); 1011 | } 1012 | return fn.next(); 1013 | } 1014 | fn.next = function () { 1015 | return (index < tasks.length - 1) ? makeCallback(index + 1): null; 1016 | }; 1017 | return fn; 1018 | } 1019 | return makeCallback(0); 1020 | }; 1021 | 1022 | async.apply = _restParam(function (fn, args) { 1023 | return _restParam(function (callArgs) { 1024 | return fn.apply( 1025 | null, args.concat(callArgs) 1026 | ); 1027 | }); 1028 | }); 1029 | 1030 | function _concat(eachfn, arr, fn, callback) { 1031 | var result = []; 1032 | eachfn(arr, function (x, index, cb) { 1033 | fn(x, function (err, y) { 1034 | result = result.concat(y || []); 1035 | cb(err); 1036 | }); 1037 | }, function (err) { 1038 | callback(err, result); 1039 | }); 1040 | } 1041 | async.concat = doParallel(_concat); 1042 | async.concatSeries = doSeries(_concat); 1043 | 1044 | async.whilst = function (test, iterator, callback) { 1045 | callback = callback || noop; 1046 | if (test()) { 1047 | var next = _restParam(function(err, args) { 1048 | if (err) { 1049 | callback(err); 1050 | } else if (test.apply(this, args)) { 1051 | iterator(next); 1052 | } else { 1053 | callback(null); 1054 | } 1055 | }); 1056 | iterator(next); 1057 | } else { 1058 | callback(null); 1059 | } 1060 | }; 1061 | 1062 | async.doWhilst = function (iterator, test, callback) { 1063 | var calls = 0; 1064 | return async.whilst(function() { 1065 | return ++calls <= 1 || test.apply(this, arguments); 1066 | }, iterator, callback); 1067 | }; 1068 | 1069 | async.until = function (test, iterator, callback) { 1070 | return async.whilst(function() { 1071 | return !test.apply(this, arguments); 1072 | }, iterator, callback); 1073 | }; 1074 | 1075 | async.doUntil = function (iterator, test, callback) { 1076 | return async.doWhilst(iterator, function() { 1077 | return !test.apply(this, arguments); 1078 | }, callback); 1079 | }; 1080 | 1081 | async.during = function (test, iterator, callback) { 1082 | callback = callback || noop; 1083 | 1084 | var next = _restParam(function(err, args) { 1085 | if (err) { 1086 | callback(err); 1087 | } else { 1088 | args.push(check); 1089 | test.apply(this, args); 1090 | } 1091 | }); 1092 | 1093 | var check = function(err, truth) { 1094 | if (err) { 1095 | callback(err); 1096 | } else if (truth) { 1097 | iterator(next); 1098 | } else { 1099 | callback(null); 1100 | } 1101 | }; 1102 | 1103 | test(check); 1104 | }; 1105 | 1106 | async.doDuring = function (iterator, test, callback) { 1107 | var calls = 0; 1108 | async.during(function(next) { 1109 | if (calls++ < 1) { 1110 | next(null, true); 1111 | } else { 1112 | test.apply(this, arguments); 1113 | } 1114 | }, iterator, callback); 1115 | }; 1116 | 1117 | function _queue(worker, concurrency, payload) { 1118 | if (concurrency == null) { 1119 | concurrency = 1; 1120 | } 1121 | else if(concurrency === 0) { 1122 | throw new Error('Concurrency must not be zero'); 1123 | } 1124 | function _insert(q, data, pos, callback) { 1125 | if (callback != null && typeof callback !== "function") { 1126 | throw new Error("task callback must be a function"); 1127 | } 1128 | q.started = true; 1129 | if (!_isArray(data)) { 1130 | data = [data]; 1131 | } 1132 | if(data.length === 0 && q.idle()) { 1133 | // call drain immediately if there are no tasks 1134 | return async.setImmediate(function() { 1135 | q.drain(); 1136 | }); 1137 | } 1138 | _arrayEach(data, function(task) { 1139 | var item = { 1140 | data: task, 1141 | callback: callback || noop 1142 | }; 1143 | 1144 | if (pos) { 1145 | q.tasks.unshift(item); 1146 | } else { 1147 | q.tasks.push(item); 1148 | } 1149 | 1150 | if (q.tasks.length === q.concurrency) { 1151 | q.saturated(); 1152 | } 1153 | }); 1154 | async.setImmediate(q.process); 1155 | } 1156 | function _next(q, tasks) { 1157 | return function(){ 1158 | workers -= 1; 1159 | var args = arguments; 1160 | _arrayEach(tasks, function (task) { 1161 | task.callback.apply(task, args); 1162 | }); 1163 | if (q.tasks.length + workers === 0) { 1164 | q.drain(); 1165 | } 1166 | q.process(); 1167 | }; 1168 | } 1169 | 1170 | var workers = 0; 1171 | var q = { 1172 | tasks: [], 1173 | concurrency: concurrency, 1174 | payload: payload, 1175 | saturated: noop, 1176 | empty: noop, 1177 | drain: noop, 1178 | started: false, 1179 | paused: false, 1180 | push: function (data, callback) { 1181 | _insert(q, data, false, callback); 1182 | }, 1183 | kill: function () { 1184 | q.drain = noop; 1185 | q.tasks = []; 1186 | }, 1187 | unshift: function (data, callback) { 1188 | _insert(q, data, true, callback); 1189 | }, 1190 | process: function () { 1191 | if (!q.paused && workers < q.concurrency && q.tasks.length) { 1192 | while(workers < q.concurrency && q.tasks.length){ 1193 | var tasks = q.payload ? 1194 | q.tasks.splice(0, q.payload) : 1195 | q.tasks.splice(0, q.tasks.length); 1196 | 1197 | var data = _map(tasks, function (task) { 1198 | return task.data; 1199 | }); 1200 | 1201 | if (q.tasks.length === 0) { 1202 | q.empty(); 1203 | } 1204 | workers += 1; 1205 | var cb = only_once(_next(q, tasks)); 1206 | worker(data, cb); 1207 | } 1208 | } 1209 | }, 1210 | length: function () { 1211 | return q.tasks.length; 1212 | }, 1213 | running: function () { 1214 | return workers; 1215 | }, 1216 | idle: function() { 1217 | return q.tasks.length + workers === 0; 1218 | }, 1219 | pause: function () { 1220 | q.paused = true; 1221 | }, 1222 | resume: function () { 1223 | if (q.paused === false) { return; } 1224 | q.paused = false; 1225 | var resumeCount = Math.min(q.concurrency, q.tasks.length); 1226 | // Need to call q.process once per concurrent 1227 | // worker to preserve full concurrency after pause 1228 | for (var w = 1; w <= resumeCount; w++) { 1229 | async.setImmediate(q.process); 1230 | } 1231 | } 1232 | }; 1233 | return q; 1234 | } 1235 | 1236 | async.queue = function (worker, concurrency) { 1237 | var q = _queue(function (items, cb) { 1238 | worker(items[0], cb); 1239 | }, concurrency, 1); 1240 | 1241 | return q; 1242 | }; 1243 | 1244 | async.priorityQueue = function (worker, concurrency) { 1245 | 1246 | function _compareTasks(a, b){ 1247 | return a.priority - b.priority; 1248 | } 1249 | 1250 | function _binarySearch(sequence, item, compare) { 1251 | var beg = -1, 1252 | end = sequence.length - 1; 1253 | while (beg < end) { 1254 | var mid = beg + ((end - beg + 1) >>> 1); 1255 | if (compare(item, sequence[mid]) >= 0) { 1256 | beg = mid; 1257 | } else { 1258 | end = mid - 1; 1259 | } 1260 | } 1261 | return beg; 1262 | } 1263 | 1264 | function _insert(q, data, priority, callback) { 1265 | if (callback != null && typeof callback !== "function") { 1266 | throw new Error("task callback must be a function"); 1267 | } 1268 | q.started = true; 1269 | if (!_isArray(data)) { 1270 | data = [data]; 1271 | } 1272 | if(data.length === 0) { 1273 | // call drain immediately if there are no tasks 1274 | return async.setImmediate(function() { 1275 | q.drain(); 1276 | }); 1277 | } 1278 | _arrayEach(data, function(task) { 1279 | var item = { 1280 | data: task, 1281 | priority: priority, 1282 | callback: typeof callback === 'function' ? callback : noop 1283 | }; 1284 | 1285 | q.tasks.splice(_binarySearch(q.tasks, item, _compareTasks) + 1, 0, item); 1286 | 1287 | if (q.tasks.length === q.concurrency) { 1288 | q.saturated(); 1289 | } 1290 | async.setImmediate(q.process); 1291 | }); 1292 | } 1293 | 1294 | // Start with a normal queue 1295 | var q = async.queue(worker, concurrency); 1296 | 1297 | // Override push to accept second parameter representing priority 1298 | q.push = function (data, priority, callback) { 1299 | _insert(q, data, priority, callback); 1300 | }; 1301 | 1302 | // Remove unshift function 1303 | delete q.unshift; 1304 | 1305 | return q; 1306 | }; 1307 | 1308 | async.cargo = function (worker, payload) { 1309 | return _queue(worker, 1, payload); 1310 | }; 1311 | 1312 | function _console_fn(name) { 1313 | return _restParam(function (fn, args) { 1314 | fn.apply(null, args.concat([_restParam(function (err, args) { 1315 | if (typeof console === 'object') { 1316 | if (err) { 1317 | if (console.error) { 1318 | console.error(err); 1319 | } 1320 | } 1321 | else if (console[name]) { 1322 | _arrayEach(args, function (x) { 1323 | console[name](x); 1324 | }); 1325 | } 1326 | } 1327 | })])); 1328 | }); 1329 | } 1330 | async.log = _console_fn('log'); 1331 | async.dir = _console_fn('dir'); 1332 | /*async.info = _console_fn('info'); 1333 | async.warn = _console_fn('warn'); 1334 | async.error = _console_fn('error');*/ 1335 | 1336 | async.memoize = function (fn, hasher) { 1337 | var memo = {}; 1338 | var queues = {}; 1339 | hasher = hasher || identity; 1340 | var memoized = _restParam(function memoized(args) { 1341 | var callback = args.pop(); 1342 | var key = hasher.apply(null, args); 1343 | if (key in memo) { 1344 | async.nextTick(function () { 1345 | callback.apply(null, memo[key]); 1346 | }); 1347 | } 1348 | else if (key in queues) { 1349 | queues[key].push(callback); 1350 | } 1351 | else { 1352 | queues[key] = [callback]; 1353 | fn.apply(null, args.concat([_restParam(function (args) { 1354 | memo[key] = args; 1355 | var q = queues[key]; 1356 | delete queues[key]; 1357 | for (var i = 0, l = q.length; i < l; i++) { 1358 | q[i].apply(null, args); 1359 | } 1360 | })])); 1361 | } 1362 | }); 1363 | memoized.memo = memo; 1364 | memoized.unmemoized = fn; 1365 | return memoized; 1366 | }; 1367 | 1368 | async.unmemoize = function (fn) { 1369 | return function () { 1370 | return (fn.unmemoized || fn).apply(null, arguments); 1371 | }; 1372 | }; 1373 | 1374 | function _times(mapper) { 1375 | return function (count, iterator, callback) { 1376 | mapper(_range(count), iterator, callback); 1377 | }; 1378 | } 1379 | 1380 | async.times = _times(async.map); 1381 | async.timesSeries = _times(async.mapSeries); 1382 | async.timesLimit = function (count, limit, iterator, callback) { 1383 | return async.mapLimit(_range(count), limit, iterator, callback); 1384 | }; 1385 | 1386 | async.seq = function (/* functions... */) { 1387 | var fns = arguments; 1388 | return _restParam(function (args) { 1389 | var that = this; 1390 | 1391 | var callback = args[args.length - 1]; 1392 | if (typeof callback == 'function') { 1393 | args.pop(); 1394 | } else { 1395 | callback = noop; 1396 | } 1397 | 1398 | async.reduce(fns, args, function (newargs, fn, cb) { 1399 | fn.apply(that, newargs.concat([_restParam(function (err, nextargs) { 1400 | cb(err, nextargs); 1401 | })])); 1402 | }, 1403 | function (err, results) { 1404 | callback.apply(that, [err].concat(results)); 1405 | }); 1406 | }); 1407 | }; 1408 | 1409 | async.compose = function (/* functions... */) { 1410 | return async.seq.apply(null, Array.prototype.reverse.call(arguments)); 1411 | }; 1412 | 1413 | 1414 | function _applyEach(eachfn) { 1415 | return _restParam(function(fns, args) { 1416 | var go = _restParam(function(args) { 1417 | var that = this; 1418 | var callback = args.pop(); 1419 | return eachfn(fns, function (fn, _, cb) { 1420 | fn.apply(that, args.concat([cb])); 1421 | }, 1422 | callback); 1423 | }); 1424 | if (args.length) { 1425 | return go.apply(this, args); 1426 | } 1427 | else { 1428 | return go; 1429 | } 1430 | }); 1431 | } 1432 | 1433 | async.applyEach = _applyEach(async.eachOf); 1434 | async.applyEachSeries = _applyEach(async.eachOfSeries); 1435 | 1436 | 1437 | async.forever = function (fn, callback) { 1438 | var done = only_once(callback || noop); 1439 | var task = ensureAsync(fn); 1440 | function next(err) { 1441 | if (err) { 1442 | return done(err); 1443 | } 1444 | task(next); 1445 | } 1446 | next(); 1447 | }; 1448 | 1449 | function ensureAsync(fn) { 1450 | return _restParam(function (args) { 1451 | var callback = args.pop(); 1452 | args.push(function () { 1453 | var innerArgs = arguments; 1454 | if (sync) { 1455 | async.setImmediate(function () { 1456 | callback.apply(null, innerArgs); 1457 | }); 1458 | } else { 1459 | callback.apply(null, innerArgs); 1460 | } 1461 | }); 1462 | var sync = true; 1463 | fn.apply(this, args); 1464 | sync = false; 1465 | }); 1466 | } 1467 | 1468 | async.ensureAsync = ensureAsync; 1469 | 1470 | async.constant = _restParam(function(values) { 1471 | var args = [null].concat(values); 1472 | return function (callback) { 1473 | return callback.apply(this, args); 1474 | }; 1475 | }); 1476 | 1477 | async.wrapSync = 1478 | async.asyncify = function asyncify(func) { 1479 | return _restParam(function (args) { 1480 | var callback = args.pop(); 1481 | var result; 1482 | try { 1483 | result = func.apply(this, args); 1484 | } catch (e) { 1485 | return callback(e); 1486 | } 1487 | // if result is Promise object 1488 | if (_isObject(result) && typeof result.then === "function") { 1489 | result.then(function(value) { 1490 | callback(null, value); 1491 | })["catch"](function(err) { 1492 | callback(err.message ? err : new Error(err)); 1493 | }); 1494 | } else { 1495 | callback(null, result); 1496 | } 1497 | }); 1498 | }; 1499 | 1500 | // Node.js 1501 | if (typeof module === 'object' && module.exports) { 1502 | module.exports = async; 1503 | } 1504 | // AMD / RequireJS 1505 | else if (typeof define === 'function' && define.amd) { 1506 | define([], function () { 1507 | return async; 1508 | }); 1509 | } 1510 | // included directly via