|
1 | 1 | import { EventEmitter } from 'events' |
2 | 2 | import { inherits } from 'util' |
3 | | -import uuid from 'node-uuid'; |
| 3 | +import { v4 as uuid } from 'uuid'; |
4 | 4 |
|
5 | | -export default function Bucket( name, storeProvider ) { |
| 5 | +const callbackAsPromise = ( callback, task ) => new Promise( ( resolve, reject ) => { |
| 6 | + task( ( error, result ) => { |
| 7 | + if ( error ) { |
| 8 | + reject( error ); |
| 9 | + return; |
| 10 | + } |
| 11 | + resolve( result ); |
| 12 | + } ); |
| 13 | +} ); |
| 14 | + |
| 15 | +const deprecateCallback = ( callback, promise ) => { |
| 16 | + if ( typeof callback === 'function' ) { |
| 17 | + // TODO: warn about deprecating callback API |
| 18 | + // and convert to promises |
| 19 | + return promise.then( |
| 20 | + result => { |
| 21 | + callback( null, result ); |
| 22 | + return result; |
| 23 | + }, |
| 24 | + error => { |
| 25 | + callback( error ); |
| 26 | + return error; |
| 27 | + } |
| 28 | + ); |
| 29 | + } |
| 30 | + return promise; |
| 31 | +}; |
| 32 | + |
| 33 | +const promiseAPI = store => ( { |
| 34 | + get: ( id, callback ) => |
| 35 | + callbackAsPromise( callback, store.get.bind( store, id ) ), |
| 36 | + update: ( id, object, isIndexing, callback ) => |
| 37 | + callbackAsPromise( callback, store.update.bind( store, id, object, isIndexing ) ), |
| 38 | + remove: ( id, callback ) => |
| 39 | + callbackAsPromise( callback, store.remove.bind( store, id ) ), |
| 40 | + find: ( query, callback ) => |
| 41 | + callbackAsPromise( callback, store.find.bind( store, query ) ) |
| 42 | +} ); |
| 43 | + |
| 44 | +export default function Bucket( name, storeProvider, channel ) { |
6 | 45 | EventEmitter.call( this ); |
7 | 46 | this.name = name; |
8 | 47 | this.store = storeProvider( this ); |
| 48 | + this.storeAPI = promiseAPI( this.store ); |
9 | 49 | this.isIndexing = false; |
| 50 | + this.channel = channel; |
| 51 | + |
| 52 | + channel |
| 53 | + // forward the index and error events from the channel |
| 54 | + .on( 'index', ( ... args ) => this.emit( 'index', ... args ) ) |
| 55 | + .on( 'error', ( ... args ) => this.emit( 'error', ... args ) ) |
| 56 | + // when the channel updates or removes data, the bucket should apply |
| 57 | + // the same updates |
| 58 | + .on( 'update', ( id, data ) => { |
| 59 | + this.update( id, data, { sync: false } ); |
| 60 | + } ) |
| 61 | + .on( 'indexingStateChange', ( isIndexing ) => { |
| 62 | + this.isIndexing = isIndexing; |
| 63 | + if ( isIndexing ) { |
| 64 | + this.emit( 'indexing' ); |
| 65 | + } |
| 66 | + } ) |
| 67 | + .on( 'remove', ( id ) => { |
| 68 | + // TODO, there needs te be a way to remove without telling the |
| 69 | + // channel to do it |
| 70 | + this.remove( id ); |
| 71 | + } ); |
10 | 72 | } |
11 | 73 |
|
12 | 74 | inherits( Bucket, EventEmitter ); |
13 | 75 |
|
14 | 76 | Bucket.prototype.reload = function() { |
15 | | - this.emit( 'reload' ); |
| 77 | + this.channel.reload(); |
16 | 78 | }; |
17 | 79 |
|
| 80 | +/** |
| 81 | + * Stores an object in the bucket and syncs it to simperium. Generates an |
| 82 | + * object ID to represent the object in simperium. |
| 83 | + * |
| 84 | + * @param {Object} object - plain js object literal to be saved/synced |
| 85 | + * @param {Function} callback - runs when object has been saved |
| 86 | + * @return {Promise<Object>} data stored in the bucket |
| 87 | + */ |
18 | 88 | Bucket.prototype.add = function( object, callback ) { |
19 | | - var id = uuid.v4(); |
| 89 | + var id = uuid(); |
20 | 90 | return this.update( id, object, callback ); |
21 | 91 | }; |
22 | 92 |
|
| 93 | +/** |
| 94 | + * Requests the object data stored in the bucket for the given id. |
| 95 | + * |
| 96 | + * @param {String} id - bucket object id |
| 97 | + * @param {Function} callback - with the data stored in the bucket |
| 98 | + * @return {Promise<Object>} the object data for the given id |
| 99 | + */ |
23 | 100 | Bucket.prototype.get = function( id, callback ) { |
24 | | - return this.store.get( id, callback ); |
| 101 | + return deprecateCallback( callback, this.storeAPI.get( id ) ); |
25 | 102 | }; |
26 | 103 |
|
| 104 | +/** |
| 105 | + * Update the bucket object of `id` with the given data. |
| 106 | + * |
| 107 | + * @param {String} id - the bucket id for the object to update |
| 108 | + * @param {Object} data - object literal to replace the object data with |
| 109 | + * @param {Object} [options] - optional settings |
| 110 | + * @param {Boolean} [options.sync=true] - false if object should not be synced with this update |
| 111 | + * @param {Function} callback - executed when object is updated localy |
| 112 | + * @returns {Promise<Object>} - update data |
| 113 | + */ |
27 | 114 | Bucket.prototype.update = function( id, data, options, callback ) { |
28 | 115 | if ( typeof options === 'function' ) { |
29 | 116 | callback = options; |
| 117 | + options = { sync: true }; |
| 118 | + } |
| 119 | + |
| 120 | + if ( !! options === false ) { |
| 121 | + options = { sync: true }; |
30 | 122 | } |
31 | | - return this.store.update( id, data, this.isIndexing, callback ); |
| 123 | + |
| 124 | + const task = this.storeAPI.update( id, data, this.isIndexing ) |
| 125 | + .then( bucketObject => { |
| 126 | + this.emit( 'update', id, bucketObject.data ); |
| 127 | + this.channel.update( bucketObject, options.sync ); |
| 128 | + return bucketObject; |
| 129 | + } ); |
| 130 | + return deprecateCallback( callback, task ); |
32 | 131 | }; |
33 | 132 |
|
34 | 133 | Bucket.prototype.hasLocalChanges = function( callback ) { |
35 | | - callback( null, false ); |
| 134 | + return deprecateCallback( callback, this.channel.hasLocalChanges() ); |
36 | 135 | }; |
37 | 136 |
|
38 | 137 | Bucket.prototype.getVersion = function( id, callback ) { |
39 | | - callback( null, 0 ); |
| 138 | + return deprecateCallback( callback, this.channel.getVersion( id ) ); |
40 | 139 | }; |
41 | 140 |
|
42 | 141 | Bucket.prototype.touch = function( id, callback ) { |
43 | | - return this.store.get( id, ( e, object ) => { |
44 | | - if ( e ) return callback( e ); |
45 | | - this.update( object.id, object.data, callback ); |
46 | | - } ); |
| 142 | + const task = this.storeAPI.get( id ) |
| 143 | + .then( object => this.update( object.id, object.data ) ); |
| 144 | + |
| 145 | + return deprecateCallback( callback, task ); |
47 | 146 | }; |
48 | 147 |
|
49 | 148 | Bucket.prototype.remove = function( id, callback ) { |
50 | | - return this.store.remove( id, callback ); |
| 149 | + const task = this.storeAPI.remove( id ) |
| 150 | + .then( ( result ) => { |
| 151 | + this.emit( 'remove', id ); |
| 152 | + this.channel.remove( id ); |
| 153 | + return result; |
| 154 | + } ) |
| 155 | + return deprecateCallback( callback, task ); |
51 | 156 | }; |
52 | 157 |
|
53 | 158 | Bucket.prototype.find = function( query, callback ) { |
54 | | - return this.store.find( query, callback ); |
| 159 | + return deprecateCallback( callback, this.storeAPI.find( query ) ); |
55 | 160 | }; |
56 | 161 |
|
57 | 162 | Bucket.prototype.getRevisions = function( id, callback ) { |
58 | | - // Overridden in Channel |
59 | | - callback( new Error( 'Failed to fetch revisions for' + id ) ); |
| 163 | + return deprecateCallback( callback, this.channel.getRevisions( id ) ); |
60 | 164 | } |
0 commit comments