core/datastore/networkstore.js
import { Promise } from 'es6-promise';
import isString from 'lodash/isString';
import isArray from 'lodash/isArray';
import assign from 'lodash/assign';
import { KinveyError } from '../errors';
import { Query } from '../query';
import { Client } from '../client';
import { isDefined, isPromiseLike, isObservable } from '../utils';
import { wrapInObservable } from '../observable';
import { Aggregation } from '../aggregation';
import { getLiveCollectionManager } from '../live';
import { Operation, OperationType } from './operations';
import { processorFactory } from './processors';
/**
* @private
* The NetworkStore class is used to find, create, update, remove, count and group entities over the network.
*/
export class NetworkStore {
/** @type {NetworkDataProcessor} */
_processor;
constructor(collection, processor, options = {}) {
this._processor = processor || processorFactory.getNetworkProcessor();
if (collection && !isString(collection)) {
throw new KinveyError('Collection must be a string.');
}
/**
* @type {string}
*/
this.collection = collection;
/**
* @type {Client}
*/
this.client = options.client;
}
/**
* The client for the store.
* @return {Client} Client
*/
get client() {
if (isDefined(this._client)) {
return this._client;
}
return Client.sharedInstance();
}
/**
* Set the client for the store
* @param {Client} [client] Client
*/
set client(client) {
if (client instanceof Client) {
this._client = client;
} else {
this._client = null;
}
}
/**
* The pathname for the store.
* @return {string} Pathname
*/
get pathname() {
let pathname = `/appdata/${this.client.appKey}`;
if (this.collection) {
pathname = `${pathname}/${this.collection}`;
}
return pathname;
}
/**
* Find all entities in the data store. A query can be optionally provided to return
* a subset of all entities in a collection or omitted to return all entities in
* a collection. The number of entities returned adheres to the limits specified
* at http://devcenter.kinvey.com/rest/guides/datastore#queryrestrictions.
*
* @param {Query} [query] Query used to filter entities.
* @param {Object} [options] Options
* @param {Properties} [options.properties] Custom properties to send with
* the request.
* @param {Number} [options.timeout] Timeout for the request.
* @param {Boolean} [options.useDeltaSet] Turn on or off the use of delta fetch.
* @return {Observable} Observable.
*/
find(query, options = {}) {
const errPromise = this._validateQuery(query);
if (errPromise) {
return wrapInObservable(errPromise);
}
options = assign({ useDeltaSet: this.useDeltaSet }, options);
const operation = this._buildOperationObject(OperationType.Read, query);
const opPromise = this._executeOperation(operation, options);
return this._ensureObservable(opPromise);
}
/**
* Find a single entity in the data store by id.
*
* @param {string} id Entity by id to find.
* @param {Object} [options] Options
* @param {Properties} [options.properties] Custom properties to send with
* the request.
* @param {Number} [options.timeout] Timeout for the request.
* @param {Boolean} [options.useDeltaSet] Turn on or off the use of delta fetch.
* @return {Observable} Observable.
*/
findById(id, options = {}) {
if (!id) {
return wrapInObservable((observer) => {
observer.next(undefined); // TODO: decide on this behaviour
});
}
const operation = this._buildOperationObject(OperationType.ReadById, null, null, id);
const entityPromise = this._executeOperation(operation, options);
return this._ensureObservable(entityPromise);
}
/**
* Group entities.
*
* @param {Aggregation} aggregationQuery Aggregation used to group entities.
* @param {Object} [options] Options
* @param {Properties} [options.properties] Custom properties to send with
* the request.
* @param {Number} [options.timeout] Timeout for the request.
* @return {Observable} Observable.
*/
group(aggregationQuery, options = {}) {
const validationError = this._validateAggregationQuery(aggregationQuery);
if (validationError) {
return this._ensureObservable(validationError);
}
const operation = this._buildOperationObject(OperationType.Group, aggregationQuery);
const resultPromise = this._executeOperation(operation, options);
return this._ensureObservable(resultPromise);
}
/**
* Count all entities in the data store. A query can be optionally provided to return
* a subset of all entities in a collection or omitted to return all entities in
* a collection. The number of entities returned adheres to the limits specified
* at http://devcenter.kinvey.com/rest/guides/datastore#queryrestrictions.
*
* @param {Query} [query] Query used to filter entities.
* @param {Object} [options] Options
* @param {Properties} [options.properties] Custom properties to send with
* the request.
* @param {Number} [options.timeout] Timeout for the request.
* @return {Observable} Observable.
*/
count(query, options = {}) {
const errPromise = this._validateQuery(query);
if (errPromise) {
return wrapInObservable(errPromise);
}
const operation = this._buildOperationObject(OperationType.Count, query);
const opPromise = this._executeOperation(operation, options);
return this._ensureObservable(opPromise);
}
/**
* Create a single entity on the data store.
*
* @param {Object} data Data that you want to create on the data store.
* @param {Object} [options] Options
* @param {Properties} [options.properties] Custom properties to send with
* the request.
* @param {Number} [options.timeout] Timeout for the request.
* @return {Promise} Promise.
*/
create(entity, options = {}) {
// TODO: decide on this behaviour
if (!isDefined(entity)) {
return Promise.resolve(null);
}
if (isArray(entity)) {
return Promise.reject(new KinveyError(
'Unable to create an array of entities.',
'Please create entities one by one.'
));
}
const operation = this._buildOperationObject(OperationType.Create, null, entity);
return this._executeOperation(operation, options);
}
/**
* Update a single entity on the data store.
*
* @param {Object} data Data that you want to update on the data store.
* @param {Object} [options] Options
* @param {Properties} [options.properties] Custom properties to send with
* the request.
* @param {Number} [options.timeout] Timeout for the request.
* @return {Promise} Promise.
*/
update(entity, options = {}) {
if (!isDefined(entity)) {
return Promise.resolve(null); // TODO: really?
}
if (isArray(entity)) {
const err = new KinveyError('Unable to update an array of entities.', 'Please update entities one by one.');
return Promise.reject(err);
}
if (!isDefined(entity._id)) {
const errMsg = 'The entity provided does not contain an _id. An _id is required to update the entity.';
return Promise.reject(new KinveyError(errMsg, entity));
}
const operation = this._buildOperationObject(OperationType.Update, null, entity);
const opPromise = this._executeOperation(operation, options);
return this._ensurePromise(opPromise);
}
/**
* Save a single entity on the data store.
*
* @param {Object} data Data that you want to save on the data store.
* @param {Object} [options] Options
* @param {Properties} [options.properties] Custom properties to send with
* the request.
* @param {Number} [options.timeout] Timeout for the request.
* @return {Promise} Promise.
*/
save(entity, options) {
if (entity._id) {
return this.update(entity, options);
}
return this.create(entity, options);
}
/**
* Remove all entities in the data store. A query can be optionally provided to remove
* a subset of all entities in a collection or omitted to remove all entities in
* a collection. The number of entities removed adheres to the limits specified
* at http://devcenter.kinvey.com/rest/guides/datastore#queryrestrictions.
*
* @param {Query} [query] Query used to filter entities.
* @param {Object} [options] Options
* @param {Properties} [options.properties] Custom properties to send with
* the request.
* @param {Number} [options.timeout] Timeout for the request.
* @return {Promise} Promise.
*/
remove(query, options = {}) {
const errPromise = this._validateQuery(query);
if (errPromise) {
return errPromise;
}
const operation = this._buildOperationObject(OperationType.Delete, query);
const opPromise = this._executeOperation(operation, options)
.then(count => ({ count }));
return this._ensurePromise(opPromise);
}
/**
* Remove a single entity in the data store by id.
*
* @param {string} id Entity by id to remove.
* @param {Object} [options] Options
* @param {Properties} [options.properties] Custom properties to send with
* the request.
* @param {Number} [options.timeout] Timeout for the request.
* @return {Promise} Promise.
*/
removeById(id, options = {}) {
// TODO: this should be the behaviour, I think
// if (!id) {
// return Promise.reject(new KinveyError('Invalid or missing id'));
// }
if (!isDefined(id)) {
return Promise.resolve(undefined);
}
const operation = this._buildOperationObject(OperationType.DeleteById, null, null, id);
return this._executeOperation(operation, options)
.then(count => ({ count }));
}
/**
* Subscribes to the live stream for the collection
*/
subscribe(receiver) {
const manager = getLiveCollectionManager();
return manager.subscribeCollection(this.collection, receiver);
}
/**
* Unsubscribes from the live stream for the collection
*/
unsubscribe() {
const manager = getLiveCollectionManager();
return manager.unsubscribeCollection(this.collection);
}
// protected
_validateQuery(query) {
if (query && !(query instanceof Query)) {
return Promise.reject(new KinveyError('Invalid query. It must be an instance of the Query class.'));
}
return null;
}
_validateAggregationQuery(aggregationQuery) {
if (!(aggregationQuery instanceof Aggregation)) {
return Promise.reject(new KinveyError('Invalid aggregation. It must be an instance of the Aggregation class.'));
}
return null;
}
_buildOperationObject(type, query, data, id) {
return new Operation(type, this.collection, query, data, id);
}
_executeOperation(operation, options) {
return this._processor.process(operation, options);
}
// private
_ensureObservable(promiseOrObservable) {
if (isPromiseLike(promiseOrObservable)) {
return wrapInObservable(promiseOrObservable);
} else if (isObservable(promiseOrObservable)) {
return promiseOrObservable;
}
throw new KinveyError('Unexpected result type.'); // should not happen
}
_ensurePromise(object) {
if (isPromiseLike(object)) {
return object;
}
throw new KinveyError('Unexpected result type.'); // should not happen
}
}