├── test ├── mocha.opts └── test.js ├── .gitignore ├── .travis.yml ├── LICENSE ├── package.json ├── CONTRIBUTING.md ├── README.md └── index.js /test/mocha.opts: -------------------------------------------------------------------------------- 1 | -R spec -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | services: 3 | - mongodb 4 | node_js: 5 | - "0.10" 6 | before_script: 7 | - npm install 8 | notifications: 9 | email: false 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (C) 2013 Alex Ford and contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongoose-auto-increment", 3 | "version": "3.0.8", 4 | "description": "This plugin allows you to auto-increment any field on any mongoose schema that you wish.", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/codetunnel/mongoose-auto-increment.git" 8 | }, 9 | "dependencies": { 10 | "extend": "^1.3.0" 11 | }, 12 | "peerDependencies": { 13 | "mongoose": "~3.8.12" 14 | }, 15 | "devDependencies": { 16 | "async": "*", 17 | "chai": "*", 18 | "mocha": "*", 19 | "mongoose": "~3.8.12" 20 | }, 21 | "keywords": [ 22 | "mongoose", 23 | "plugin", 24 | "auto-increment", 25 | "auto", 26 | "increment", 27 | "automatic", 28 | "autoincrement", 29 | "auto_increment", 30 | "autoinc", 31 | "auto-inc", 32 | "auto_inc", 33 | "pureautoinc", 34 | "mongoose-pureautoinc" 35 | ], 36 | "author": { 37 | "name": "Alex Ford (Chevex)", 38 | "email": "alex.ford@codetunnel.com", 39 | "url": "http://CodeTunnel.com" 40 | }, 41 | "contributors": [ 42 | { 43 | "name": "Nassor Paulino da Silva (rossan)", 44 | "email": "nassor@gmail.com" 45 | }, 46 | { 47 | "name": "Misha Koryak (mkoryak)", 48 | "email": "mkoryak@gmail.com", 49 | "url": "http://ExhibitionNest.com" 50 | }, 51 | { 52 | "name": "Christopher Hiller (boneskull)", 53 | "url": "http://boneskull.github.io/" 54 | }, 55 | { 56 | "name": "tomaskavka", 57 | "url": "https://github.com/tomaskavka" 58 | } 59 | ], 60 | "scripts": { 61 | "test": "node_modules/mocha/bin/mocha" 62 | }, 63 | "bugs": { 64 | "url": "https://github.com/codetunnel/mongoose-auto-increment/issues" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Submitting Issues 2 | 3 | If you experience any issues with this plugin please feel free to submit them to the [issue tracker](https://github.com/Chevex/mongoose-auto-increment/issues) for this project. One of the projects contributors will eventually take a look at it. Please be patient. 4 | 5 | ## Guidelines 6 | 7 | * Search the issue tracker first! If there is already a question similar to yours then please comment on that issue instead. 8 | * Please include steps to reproduce the issue. It will take us WAY longer to fix the issue (if we even can) if you don't include this information. 9 | 10 | ## Contributing to Source Code 11 | 12 | The source of this plugin is licensed using the MIT license. 13 | 14 | > The MIT License 15 | > 16 | > Copyright (C) 2013 Alex Ford and contributors. 17 | > 18 | > Permission is hereby granted, free of charge, to any person obtaining a copy of 19 | > this software and associated documentation files (the "Software"), to deal in 20 | > the Software without restriction, including without limitation the rights to 21 | > use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 22 | > of the Software, and to permit persons to whom the Software is furnished to do 23 | > so, subject to the following conditions: 24 | > 25 | > The above copyright notice and this permission notice shall be included in all 26 | > copies or substantial portions of the Software. 27 | > 28 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 30 | > FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 31 | > COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 32 | > IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 33 | > CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 34 | 35 | To contribute changes to this project please submit them as a [pull request](https://github.com/Chevex/mongoose-auto-increment/pulls). If you don't know how to submit a pull request then see [this article](https://help.github.com/articles/using-pull-requests). 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mongoose-auto-increment 2 | 3 | [![Build Status](https://travis-ci.org/codetunnel/mongoose-auto-increment.png?branch=master)](https://travis-ci.org/codetunnel/mongoose-auto-increment) 4 | [![Dependencies Status](https://gemnasium.com/codetunnel/mongoose-auto-increment.png)](https://gemnasium.com/codetunnel/mongoose-auto-increment) 5 | [![NPM version](https://badge.fury.io/js/mongoose-auto-increment.png)](http://badge.fury.io/js/mongoose-auto-increment) 6 | 7 | > Mongoose plugin that auto-increments any ID field on your schema every time a document is saved. 8 | 9 | --- 10 | 11 | > This is the module used by [mongoose-simpledb](https://github.com/codetunnel/mongoose-simpledb) to increment Number IDs. You are perfectly able to use this module by itself if you would like. However, if you're looking to make your life easier when using [mongoose](http://mongoosejs.com/) then I highly recommend you check out simpledb. It's a small wrapper around mongoose but it makes it extremely easy to deal with your models and draws a clear path for how to use mongoose in your application. 12 | 13 | ## Getting Started 14 | 15 | > npm install mongoose-auto-increment 16 | 17 | Once you have the plugin installed it is very simple to use. Just get reference to it, initialize it by passing in your 18 | mongoose connection and pass `autoIncrement.plugin` to the `plugin()` function on your schema. 19 | 20 | > Note: You only need to initialize MAI once. 21 | 22 | ````js 23 | var mongoose = require('mongoose'), 24 | Schema = mongoose.Schema, 25 | autoIncrement = require('mongoose-auto-increment'); 26 | 27 | var connection = mongoose.createConnection("mongodb://localhost/myDatabase"); 28 | 29 | autoIncrement.initialize(connection); 30 | 31 | var bookSchema = new Schema({ 32 | author: { type: Schema.Types.ObjectId, ref: 'Author' }, 33 | title: String, 34 | genre: String, 35 | publishDate: Date 36 | }); 37 | 38 | bookSchema.plugin(autoIncrement.plugin, 'Book'); 39 | var Book = connection.model('Book', bookSchema); 40 | ```` 41 | 42 | That's it. Now you can create book entities at will and they will have an `_id` field added of type `Number` and will automatically increment with each new document. Even declaring references is easy, just remember to change the reference property's type to `Number` instead of `ObjectId` if the referenced model is also using the plugin. 43 | 44 | ````js 45 | var authorSchema = new mongoose.Schema({ 46 | name: String 47 | }); 48 | 49 | var bookSchema = new Schema({ 50 | author: { type: Number, ref: 'Author' }, 51 | title: String, 52 | genre: String, 53 | publishDate: Date 54 | }); 55 | 56 | bookSchema.plugin(autoIncrement.plugin, 'Book'); 57 | authorSchema.plugin(autoIncrement.plugin, 'Author'); 58 | ```` 59 | 60 | ### Want a field other than `_id`? 61 | 62 | ````js 63 | bookSchema.plugin(autoIncrement.plugin, { model: 'Book', field: 'bookId' }); 64 | ```` 65 | 66 | ### Want that field to start at a different number than zero or increment by more than one? 67 | 68 | ````js 69 | bookSchema.plugin(autoIncrement.plugin, { 70 | model: 'Book', 71 | field: 'bookId', 72 | startAt: 100, 73 | incrementBy: 100 74 | }); 75 | ```` 76 | 77 | Your first book document would have a `bookId` equal to `100`. Your second book document would have a `bookId` equal to `200`, and so on. 78 | 79 | ### Want to know the next number coming up? 80 | 81 | ````js 82 | var Book = connection.model('Book', bookSchema); 83 | Book.nextCount(function(err, count) { 84 | 85 | // count === 0 -> true 86 | 87 | var book = new Book(); 88 | book.save(function(err) { 89 | 90 | // book._id === 0 -> true 91 | 92 | book.nextCount(function(err, count) { 93 | 94 | // count === 1 -> true 95 | 96 | }); 97 | }); 98 | }); 99 | ```` 100 | 101 | nextCount is both a static method on the model (`Book.nextCount(...)`) and an instance method on the document (`book.nextCount(...)`). 102 | 103 | ### Want to reset counter back to the start value? 104 | 105 | ````js 106 | bookSchema.plugin(autoIncrement.plugin, { 107 | model: 'Book', 108 | field: 'bookId', 109 | startAt: 100 110 | }); 111 | 112 | var Book = connection.model('Book', bookSchema), 113 | book = new Book(); 114 | 115 | book.save(function (err) { 116 | 117 | // book._id === 100 -> true 118 | 119 | book.nextCount(function(err, count) { 120 | 121 | // count === 101 -> true 122 | 123 | book.resetCount(function(err, nextCount) { 124 | 125 | // nextCount === 100 -> true 126 | 127 | }); 128 | 129 | }); 130 | 131 | }); 132 | ```` 133 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Module Scope 2 | var mongoose = require('mongoose'), 3 | extend = require('extend'), 4 | counterSchema, 5 | IdentityCounter; 6 | 7 | // Initialize plugin by creating counter collection in database. 8 | exports.initialize = function (connection) { 9 | try { 10 | IdentityCounter = connection.model('IdentityCounter'); 11 | } catch (ex) { 12 | if (ex.name === 'MissingSchemaError') { 13 | // Create new counter schema. 14 | counterSchema = new mongoose.Schema({ 15 | model: { type: String, require: true }, 16 | field: { type: String, require: true }, 17 | count: { type: Number, default: 0 } 18 | }); 19 | 20 | // Create a unique index using the "field" and "model" fields. 21 | counterSchema.index({ field: 1, model: 1 }, { unique: true, required: true, index: -1 }); 22 | 23 | // Create model using new schema. 24 | IdentityCounter = connection.model('IdentityCounter', counterSchema); 25 | } 26 | else 27 | throw ex; 28 | } 29 | }; 30 | 31 | // The function to use when invoking the plugin on a custom schema. 32 | exports.plugin = function (schema, options) { 33 | 34 | // If we don't have reference to the counterSchema or the IdentityCounter model then the plugin was most likely not 35 | // initialized properly so throw an error. 36 | if (!counterSchema || !IdentityCounter) throw new Error("mongoose-auto-increment has not been initialized"); 37 | 38 | // Default settings and plugin scope variables. 39 | var settings = { 40 | model: null, // The model to configure the plugin for. 41 | field: '_id', // The field the plugin should track. 42 | startAt: 0, // The number the count should start at. 43 | incrementBy: 1 // The number by which to increment the count each time. 44 | }, 45 | fields = {}, // A hash of fields to add properties to in Mongoose. 46 | ready = false; // True if the counter collection has been updated and the document is ready to be saved. 47 | 48 | switch (typeof(options)) { 49 | // If string, the user chose to pass in just the model name. 50 | case 'string': 51 | settings.model = options; 52 | break; 53 | // If object, the user passed in a hash of options. 54 | case 'object': 55 | extend(settings, options); 56 | break; 57 | } 58 | 59 | // Add properties for field in schema. 60 | fields[settings.field] = { 61 | type: Number, 62 | unique: true, 63 | require: true 64 | }; 65 | schema.add(fields); 66 | 67 | // Find the counter for this model and the relevant field. 68 | IdentityCounter.findOne( 69 | { model: settings.model, field: settings.field }, 70 | function (err, counter) { 71 | if (!counter) { 72 | // If no counter exists then create one and save it. 73 | counter = new IdentityCounter({ model: settings.model, field: settings.field, count: settings.startAt - settings.incrementBy }); 74 | counter.save(function () { 75 | ready = true; 76 | }); 77 | } 78 | else 79 | ready = true; 80 | } 81 | ); 82 | 83 | // Declare a function to get the next counter for the model/schema. 84 | var nextCount = function (callback) { 85 | IdentityCounter.findOne({ 86 | model: settings.model, 87 | field: settings.field 88 | }, function (err, counter) { 89 | if (err) return callback(err); 90 | callback(null, counter === null ? settings.startAt : counter.count + settings.incrementBy); 91 | }); 92 | }; 93 | // Add nextCount as both a method on documents and a static on the schema for convenience. 94 | schema.method('nextCount', nextCount); 95 | schema.static('nextCount', nextCount); 96 | 97 | // Declare a function to reset counter at the start value - increment value. 98 | var resetCount = function (callback) { 99 | IdentityCounter.findOneAndUpdate( 100 | { model: settings.model, field: settings.field }, 101 | { count: settings.startAt - settings.incrementBy }, 102 | { new: true }, // new: true specifies that the callback should get the updated counter. 103 | function (err) { 104 | if (err) return callback(err); 105 | callback(null, settings.startAt); 106 | } 107 | ); 108 | }; 109 | // Add resetCount as both a method on documents and a static on the schema for convenience. 110 | schema.method('resetCount', resetCount); 111 | schema.static('resetCount', resetCount); 112 | 113 | // Every time documents in this schema are saved, run this logic. 114 | schema.pre('save', function (next) { 115 | // Get reference to the document being saved. 116 | var doc = this; 117 | 118 | // If the document already has the field we're interested in and that field is a number then run this logic 119 | // to give the document a number ID that is incremented by the amount specified in relation to whatever the 120 | // last generated document ID was. 121 | if (typeof(doc[settings.field]) !== 'number') { 122 | // Declare self-invoking save function. 123 | (function save() { 124 | // If ready, run increment logic. 125 | // Note: ready is true when an existing counter collection is found or after it is created for the 126 | // first time. 127 | if (ready) { 128 | // Find the counter collection entry for this model and field and update it. 129 | IdentityCounter.findOneAndUpdate( 130 | // IdentityCounter documents are identified by the model and field that the plugin was invoked for. 131 | { model: settings.model, field: settings.field }, 132 | // Increment the count by `incrementBy`. 133 | { $inc: { count: settings.incrementBy } }, 134 | // new:true specifies that the callback should get the counter AFTER it is updated (incremented). 135 | { new: true }, 136 | // Receive the updated counter. 137 | function (err, updatedIdentityCounter) { 138 | if (err) return next(err); 139 | // If there are no errors then go ahead and set the document's field to the current count. 140 | doc[settings.field] = updatedIdentityCounter.count; 141 | // Continue with default document save functionality. 142 | next(); 143 | } 144 | ); 145 | } 146 | // If not ready then set a 5 millisecond timer and try to save again. It will keep doing this until 147 | // the counter collection is ready. 148 | else 149 | setTimeout(save, 5); 150 | })(); 151 | } 152 | // If the document does not have the field we're interested in or that field isn't a number AND the user did 153 | // not specify that we should increment on updates, then just continue the save without any increment logic. 154 | else 155 | next(); 156 | }); 157 | }; 158 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var async = require('async'), 2 | should = require('chai').should(), 3 | mongoose = require('mongoose'), 4 | autoIncrement = require('..'), 5 | conn; 6 | 7 | before(function (done) { 8 | conn = mongoose.createConnection('mongodb://127.0.0.1/mongoose-auto-increment-test'); 9 | conn.on('error', console.error.bind(console)); 10 | conn.once('open', function () { 11 | autoIncrement.initialize(conn); 12 | done(); 13 | }); 14 | }); 15 | 16 | after(function (done) { 17 | conn.db.executeDbCommand({ dropDatabase: 1 }, function () { 18 | conn.close(done); 19 | }); 20 | }); 21 | 22 | afterEach(function (done) { 23 | conn.model('User').collection.drop(function () { 24 | delete conn.models.User; 25 | return conn.model('IdentityCounter').collection.drop(done); 26 | }); 27 | }); 28 | 29 | describe('mongoose-auto-increment', function () { 30 | 31 | it('should increment the _id field on save', function (done) { 32 | 33 | // Arrange 34 | var userSchema = new mongoose.Schema({ 35 | name: String, 36 | dept: String 37 | }); 38 | userSchema.plugin(autoIncrement.plugin, 'User'); 39 | var User = conn.model('User', userSchema), 40 | user1 = new User({ name: 'Charlie', dept: 'Support' }), 41 | user2 = new User({ name: 'Charlene', dept: 'Marketing' }); 42 | 43 | // Act 44 | async.series({ 45 | user1: function (cb) { 46 | user1.save(cb); 47 | }, 48 | user2: function (cb) { 49 | user2.save(cb); 50 | } 51 | }, assert); 52 | 53 | // Assert 54 | function assert(err, results) { 55 | should.not.exist(err); 56 | results.user1[0].should.have.property('_id', 0); 57 | results.user2[0].should.have.property('_id', 1); 58 | done(); 59 | } 60 | 61 | }); 62 | 63 | it('should increment the specified field instead (Test 2)', function(done) { 64 | 65 | // Arrange 66 | var userSchema = new mongoose.Schema({ 67 | name: String, 68 | dept: String 69 | }); 70 | userSchema.plugin(autoIncrement.plugin, { model: 'User', field: 'userId' }); 71 | var User = conn.model('User', userSchema), 72 | user1 = new User({ name: 'Charlie', dept: 'Support' }), 73 | user2 = new User({ name: 'Charlene', dept: 'Marketing' }); 74 | 75 | // Act 76 | async.series({ 77 | user1: function (cb) { 78 | user1.save(cb); 79 | }, 80 | user2: function (cb) { 81 | user2.save(cb); 82 | } 83 | }, assert); 84 | 85 | // Assert 86 | function assert(err, results) { 87 | should.not.exist(err); 88 | results.user1[0].should.have.property('userId', 0); 89 | results.user2[0].should.have.property('userId', 1); 90 | done(); 91 | } 92 | 93 | }); 94 | 95 | 96 | it('should start counting at specified number (Test 3)', function (done) { 97 | 98 | // Arrange 99 | var userSchema = new mongoose.Schema({ 100 | name: String, 101 | dept: String 102 | }); 103 | userSchema.plugin(autoIncrement.plugin, { model: 'User', startAt: 3 }); 104 | var User = conn.model('User', userSchema), 105 | user1 = new User({ name: 'Charlie', dept: 'Support' }), 106 | user2 = new User({ name: 'Charlene', dept: 'Marketing' }); 107 | 108 | // Act 109 | async.series({ 110 | user1: function (cb) { 111 | user1.save(cb); 112 | }, 113 | user2: function (cb) { 114 | user2.save(cb); 115 | } 116 | }, assert); 117 | 118 | // Assert 119 | function assert(err, results) { 120 | should.not.exist(err); 121 | results.user1[0].should.have.property('_id', 3); 122 | results.user2[0].should.have.property('_id', 4); 123 | done(); 124 | } 125 | 126 | }); 127 | 128 | it('should increment by the specified amount (Test 4)', function (done) { 129 | 130 | // Arrange 131 | var userSchema = new mongoose.Schema({ 132 | name: String, 133 | dept: String 134 | }); 135 | userSchema.plugin(autoIncrement.plugin, { model: 'User', incrementBy: 5 }); 136 | var User = conn.model('User', userSchema), 137 | user1 = new User({ name: 'Charlie', dept: 'Support' }), 138 | user2 = new User({ name: 'Charlene', dept: 'Marketing' }); 139 | 140 | // Act 141 | async.series({ 142 | user1: function (cb) { 143 | user1.save(cb); 144 | }, 145 | user2: function (cb) { 146 | user2.save(cb); 147 | } 148 | }, assert); 149 | 150 | 151 | // Assert 152 | function assert(err, results) { 153 | should.not.exist(err); 154 | results.user1[0].should.have.property('_id', 0); 155 | results.user2[0].should.have.property('_id', 5); 156 | done(); 157 | } 158 | 159 | }); 160 | 161 | describe('helper function', function () { 162 | 163 | it('nextCount should return the next count for the model and field (Test 5)', function (done) { 164 | 165 | // Arrange 166 | var userSchema = new mongoose.Schema({ 167 | name: String, 168 | dept: String 169 | }); 170 | userSchema.plugin(autoIncrement.plugin, 'User'); 171 | var User = conn.model('User', userSchema), 172 | user1 = new User({ name: 'Charlie', dept: 'Support' }), 173 | user2 = new User({ name: 'Charlene', dept: 'Marketing' });; 174 | 175 | // Act 176 | async.series({ 177 | count1: function (cb) { 178 | user1.nextCount(cb); 179 | }, 180 | user1: function (cb) { 181 | user1.save(cb); 182 | }, 183 | count2: function (cb) { 184 | user1.nextCount(cb); 185 | }, 186 | user2: function (cb) { 187 | user2.save(cb); 188 | }, 189 | count3: function (cb) { 190 | user2.nextCount(cb); 191 | } 192 | }, assert); 193 | 194 | // Assert 195 | function assert(err, results) { 196 | should.not.exist(err); 197 | results.count1.should.equal(0); 198 | results.user1[0].should.have.property('_id', 0); 199 | results.count2.should.equal(1); 200 | results.user2[0].should.have.property('_id', 1); 201 | results.count3.should.equal(2); 202 | done(); 203 | } 204 | 205 | }); 206 | 207 | it('resetCount should cause the count to reset as if there were no documents yet.', function (done) { 208 | 209 | // Arrange 210 | var userSchema = new mongoose.Schema({ 211 | name: String, 212 | dept: String 213 | }); 214 | userSchema.plugin(autoIncrement.plugin, 'User'); 215 | var User = conn.model('User', userSchema), 216 | user = new User({name: 'Charlie', dept: 'Support'}); 217 | 218 | // Act 219 | async.series({ 220 | user: function (cb) { 221 | user.save(cb); 222 | }, 223 | count1: function (cb) { 224 | user.nextCount(cb); 225 | }, 226 | reset: function (cb) { 227 | user.resetCount(cb); 228 | }, 229 | count2: function (cb) { 230 | user.nextCount(cb); 231 | } 232 | }, assert); 233 | 234 | // Assert 235 | function assert(err, results) { 236 | should.not.exist(err); 237 | results.user[0].should.have.property('_id', 0); 238 | results.count1.should.equal(1); 239 | results.reset.should.equal(0); 240 | results.count2.should.equal(0); 241 | done(); 242 | } 243 | 244 | }); 245 | 246 | }); 247 | }); 248 | --------------------------------------------------------------------------------