lib/database.js
/**
* Copyright 2015 Oursky Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const _ = require('lodash');
import Cache from './cache';
import Asset from './asset';
import Record from './record';
import Query from './query';
import QueryResult from './query_result';
export default class Database {
constructor(dbID, container) {
if (dbID !== '_public' && dbID !== '_private') {
throw new Error('Invalid database_id');
}
this.dbID = dbID;
this.container = container;
this._cacheStore = new Cache(this.dbID);
}
getRecordByID(id) {
let [recordType, recordId] = Record.parseID(id);
let query = new Query(Record.extend(recordType)).equalTo('_id', recordId);
return new Promise((resolve, reject) => {
this.query(query).then((users) => {
if (users.length === 1) {
resolve(users[0]);
} else {
reject(new Error(id + ' does not exist'));
}
}, reject);
});
}
query(query, cacheCallback = false) {
let remoteReturned = false;
let cacheStore = this.cacheStore;
let Cls = query.recordCls;
let queryJSON = query.toJSON();
if (!queryJSON.offset && queryJSON.page > 0) {
queryJSON.offset = queryJSON.limit * (queryJSON.page - 1);
}
let payload = _.assign({
database_id: this.dbID //eslint-disable-line
}, queryJSON);
if (cacheCallback) {
cacheStore.get(query.hash).then(function (body) {
if (remoteReturned) {
return;
}
let records = _.map(body.result, function (attrs) {
return new Cls(attrs);
});
let result = QueryResult.createFromResult(records, body.info);
cacheCallback(result, true);
}, function (err) {
console.log('No cache found', err);
});
}
return new Promise(function (resolve, reject) {
this.container.makeRequest('record:query', payload).then(function (body) {
let records = _.map(body.result, function (attrs) {
return new Cls(attrs);
});
let result = QueryResult.createFromResult(records, body.info);
remoteReturned = true;
cacheStore.set(query.hash, body);
resolve(result);
}, function (err) {
reject(err);
});
}.bind(this));
}
_presaveAssetTask(key, asset) {
if (asset.file) {
return new Promise(function (resolve, reject) {
this.container.makeUploadAssetRequest(asset).then(function (a) {
resolve([key, a]);
}, function (err) {
reject(err);
});
}.bind(this));
} else {
return Promise.resolve([key, asset]);
}
}
_presave(record) {
let self = this;
return new Promise(function (resolve, reject) {
// for every (key, value) pair, process the pair in a Promise
// the Promise should be resolved by the transformed [key, value] pair
let tasks = _.map(record, function (value, key) {
if (value instanceof Asset) {
return self._presaveAssetTask(key, value);
} else {
return Promise.resolve([key, value]);
}
});
Promise.all(tasks).then(function (keyvalues) {
_.each(keyvalues, function ([key, value]) {
record[key] = value;
});
resolve(record);
}, function (err) {
reject(err);
});
});
}
del(record) {
return this.delete(record);
}
save(_records, options = {}) {
let self = this;
let records = _records;
if (!_.isArray(records)) {
records = [records];
}
return new Promise(function (resolve, reject) {
_.map(records, function (r) {
if (r === undefined || r === null) {
reject('Invalid input, unable to save undefined and null');
}
});
const presaveTasks = _.map(records, self._presave.bind(self));
Promise.all(presaveTasks)
.then(function (processedRecords) {
let payload = {
database_id: self.dbID //eslint-disable-line
};
if (options.atomic) {
payload.atomic = true;
}
payload.records = _.map(processedRecords, (perRecord) => {
return perRecord.toJSON();
});
return self.container.makeRequest('record:save', payload);
}).then(function (body) {
let results = body.result;
let savedRecords = [];
let errors = [];
_.forEach(results, (perResult, idx) => {
if (perResult._type === 'error') {
savedRecords[idx] = undefined;
errors[idx] = perResult;
} else {
records[idx].update(perResult);
records[idx].updateTransient(perResult._transient, true);
savedRecords[idx] = records[idx];
errors[idx] = undefined;
}
});
if (records.length === 1) {
if (errors[0]) {
reject(errors[0]);
} else {
resolve(savedRecords[0]);
}
} else {
resolve({savedRecords, errors});
}
}, function (err) {
reject(err);
}).catch(function (err) {
reject(err);
});
});
}
delete(_records) {
let self = this;
let records = _records;
if (!_.isArray(records)) {
records = [records];
}
let ids = _.map(records, perRecord => perRecord.id);
let payload = {
database_id: this.dbID, //eslint-disable-line
ids: ids
};
return new Promise(function (resolve, reject) {
self.container.makeRequest('record:delete', payload)
.then(function (body) {
let results = body.result;
let errors = [];
_.forEach(results, (perResult, idx) => {
if (perResult._type === 'error') {
errors[idx] = perResult;
} else {
errors[idx] = undefined;
}
});
if (records.length === 1) {
if (errors[0]) {
reject(errors[0]);
} else {
resolve();
}
} else {
resolve(errors);
}
}, function (err) {
reject(err);
});
});
}
get cacheStore() {
return this._cacheStore;
}
clearCache() {
return this._cacheStore.reset();
}
}