@koehlerb/feathers-datastore
Version:
Feathers service for Google Datastore
497 lines (396 loc) • 16.6 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
exports.default = init;
var _datastore = require('@google-cloud/datastore');
var _datastore2 = _interopRequireDefault(_datastore);
var _debug = require('debug');
var _debug2 = _interopRequireDefault(_debug);
var _uberproto = require('uberproto');
var _uberproto2 = _interopRequireDefault(_uberproto);
var _feathersErrors = require('feathers-errors');
var _object = require('lodash/fp/object');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var MAX_INDEX_SIZE = 1500;
var getNotFound = function getNotFound(id) {
return new _feathersErrors.NotFound('No record found for id \'' + id + '\'');
},
debug = (0, _debug2.default)('feathers-datastore'),
identity = function identity(i) {
return i;
};
var Datastore = function () {
function Datastore() {
var _this = this;
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
_classCallCheck(this, Datastore);
var datastoreOpt = { projectId: options.projectId };
if (options.credentials) {
datastoreOpt.credentials = options.credentials;
}
this.store = (0, _datastore2.default)(datastoreOpt);
this.id = options.id || 'id';
this.kind = options.kind;
this.events = options.events;
this.autoIndex = options.autoIndex || false;
// NOTE: This isn't nice, but it's the only way to give internal methods full
// unrestricted (no hooks) access to all methods
['find', 'get', 'create', 'update', 'patch', 'remove'].forEach(function (method) {
_this[method] = function () {
return _this['_' + method].apply(_this, arguments);
};
});
}
_createClass(Datastore, [{
key: 'extend',
value: function extend(obj) {
return _uberproto2.default.extend(obj, this);
}
}, {
key: '_get',
value: function _get(id) {
var _this2 = this;
var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var _ref = params.query || {},
$select = _ref.$select,
key = this.makeKey(id, params),
check404 = function check404(entity) {
return entity ? entity : Promise.reject(getNotFound(id));
},
retainOnlySelected = identity;
if ($select) {
$select = [this.id].concat(_toConsumableArray($select));
retainOnlySelected = (0, _object.pick)($select);
}
return this.store.get(key).then(function (_ref2) {
var _ref3 = _slicedToArray(_ref2, 1),
entity = _ref3[0];
return entity;
}).then(function (entity) {
return _this2.entityToPlain(entity, true);
}).then(check404).then(retainOnlySelected);
}
}, {
key: '_create',
value: function _create(data) {
var _this3 = this;
var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var entities = [],
key = void 0,
wasArray = void 0;
wasArray = Array.isArray(data);
data = wasArray ? data : [data];
data.map(function (item) {
if (item.hasOwnProperty(_this3.id)) {
key = _this3.makeKey(item[_this3.id], params);
} else {
key = _this3.makeKey(undefined, params);
}
entities.push({
key: key,
data: item
});
});
// unbox if data param was an object
if (!wasArray) {
entities = entities[0];
}
// Convert entities to explicit format, to allow for indexing
entities = this.makeExplicitEntity(entities, params);
return this.store.insert(entities).then(function () {
return entities;
}).then(function (entity) {
return _this3.entityToPlain(entity);
});
}
}, {
key: '_update',
value: function _update(id, data) {
var _this4 = this;
var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var key = this.makeKey(id, params),
entity = { key: key, data: data },
_params$query = params.query,
query = _params$query === undefined ? {} : _params$query,
method = query.create ? 'upsert' : 'update';
entity = this.makeExplicitEntity(entity, params);
return this.store[method](entity).then(function () {
return entity;
}).then(function (entity) {
return _this4.entityToPlain(entity);
}).catch(function (err) {
// NOTE: Updating a not found entity will result in a bad request, rather than
// a not found, this gets around that, though in future should be made more
// secure
if (err.code === 400 && err.message === 'no entity to update') {
return getNotFound(id);
}
throw err;
});
}
}, {
key: '_patch',
value: function _patch(id, data) {
var _this5 = this;
var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var get = Datastore.prototype._get.bind(this),
find = Datastore.prototype._find.bind(this);
return Promise.resolve().then(function () {
return id ? get(id, params) : find(params);
}).then(function (results) {
var entities = void 0,
makeNewEntity = function makeNewEntity(current, update) {
return {
key: _this5.makeKey(current[_this5.id], params),
data: Object.assign({}, current, update)
};
};
if (Array.isArray(results)) {
entities = results.map(function (current) {
return makeNewEntity(current, data);
});
} else {
entities = makeNewEntity(results, data);
}
entities = _this5.makeExplicitEntity(entities, params);
return _this5.store.update(entities).then(function () {
return entities;
});
}).then(function (entity) {
return _this5.entityToPlain(entity);
});
}
}, {
key: '_find',
value: function _find() {
var _this6 = this;
var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
params.query = params.query || {};
var _params$query2 = params.query,
ancestor = _params$query2.ancestor,
namespace = _params$query2.namespace,
_params$query2$kind = _params$query2.kind,
kind = _params$query2$kind === undefined ? this.kind : _params$query2$kind,
$select = _params$query2.$select,
$limit = _params$query2.$limit,
$sort = _params$query2.$sort,
query = _objectWithoutProperties(_params$query2, ['ancestor', 'namespace', 'kind', '$select', '$limit', '$sort']),
dsQuery = this.store.createQuery(namespace, kind),
retainOnlySelected = identity,
filterOutAncestor = identity,
filters = void 0;
if ($select) {
var toSelected = (0, _object.pick)([this.id].concat(_toConsumableArray($select)));
retainOnlySelected = function retainOnlySelected(data) {
return data.map(toSelected);
};
}
if (ancestor) {
var ancestorKey = this.makeKey(ancestor, params);
dsQuery = dsQuery.hasAncestor(ancestorKey);
filterOutAncestor = function filterOutAncestor(data) {
return data.filter(function (_ref4) {
var id = _ref4.id;
return id !== ancestor;
});
};
}
filters = Object.entries(query).reduce(function (filters, _ref5) {
var _ref6 = _slicedToArray(_ref5, 2),
key = _ref6[0],
value = _ref6[1];
var opMap = {
$gt: '>',
$gte: '>=',
$lt: '<',
$lte: '<=',
'=': '='
};
var special = void 0;
// Normalize
if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) !== 'object' || value instanceof _this6.store.key().constructor || value === null) {
value = { '=': value };
}
special = Object.entries(value).filter(function (_ref7) {
var _ref8 = _slicedToArray(_ref7, 1),
op = _ref8[0];
return opMap[op];
}).map(function (_ref9) {
var _ref10 = _slicedToArray(_ref9, 2),
op = _ref10[0],
val = _ref10[1];
return [key, opMap[op], val];
});
return [].concat(_toConsumableArray(filters), _toConsumableArray(special));
}, []);
dsQuery = filters.reduce(function (q, filter) {
return q.filter.apply(q, _toConsumableArray(filter));
}, dsQuery);
if ($limit) {
dsQuery = dsQuery.limit($limit);
}
if ($sort) {
for (var prop in $sort) {
if ($sort.hasOwnProperty(prop)) {
if ($sort[prop] == 1) {
dsQuery = dsQuery.order(prop);
} else {
dsQuery = dsQuery.order(prop, { descending: true });
}
}
}
}
return dsQuery.run().then(function (_ref11) {
var _ref12 = _slicedToArray(_ref11, 1),
e = _ref12[0];
return e;
}).then(function (entity) {
return _this6.entityToPlain(entity, true);
}).then(filterOutAncestor).then(retainOnlySelected);
}
}, {
key: '_remove',
value: function _remove(id) {
var _this7 = this;
var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var get = Datastore.prototype._get.bind(this),
find = Datastore.prototype._find.bind(this);
return Promise.resolve().then(function () {
return id ? get(id, params) : find(params);
}).then(function (results) {
var keys = void 0;
if (results === null) {
return null;
}
if (Array.isArray(results)) {
keys = results.map(function (_ref13) {
var id = _ref13[_this7.id];
return _this7.makeKey(id, params);
});
} else {
keys = _this7.makeKey(results[_this7.id], params);
}
return _this7.store.delete(keys).then(function () {
return results;
});
});
}
}, {
key: 'makeKey',
value: function makeKey(id) {
var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var _params$query3 = params.query,
query = _params$query3 === undefined ? {} : _params$query3,
key = void 0;
if (Array.isArray(id) || (typeof id === 'undefined' ? 'undefined' : _typeof(id)) === 'object') {
key = this.store.key(id);
} else {
// Try fetching a number
var idAsNum = parseInt(id);
if (!isNaN(idAsNum)) {
id = idAsNum;
}
key = this.store.key([this.kind, id]);
}
if (query.namespace) {
key.namespace = query.namespace;
}
return key;
}
}, {
key: 'entityToPlain',
value: function entityToPlain(entity) {
var _this8 = this;
var alreadyFlat = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var ID_PROP = this.id;
var data = void 0;
if (Array.isArray(entity)) {
return entity.map(function (e) {
return _this8.entityToPlain(e, alreadyFlat);
});
}
if (!entity) {
return entity;
}
data = alreadyFlat ? entity : entity.data;
if (Array.isArray(data)) {
// In explicit syntax, should deconstruct
data = data.reduce(function (flat, _ref14) {
var name = _ref14.name,
value = _ref14.value;
flat[name] = value;
return flat;
}, {});
}
return Object.assign({}, data, _defineProperty({}, ID_PROP, Datastore.getKey(entity).path.slice(-1)[0]));
}
}, {
key: 'makeExplicitEntity',
value: function makeExplicitEntity(entity) {
var _this9 = this;
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var _ref15 = options.query || {},
_ref15$dontIndex = _ref15.dontIndex,
dontIndex = _ref15$dontIndex === undefined ? [] : _ref15$dontIndex,
_ref15$autoIndex = _ref15.autoIndex,
autoIndex = _ref15$autoIndex === undefined ? this.autoIndex : _ref15$autoIndex;
function isBig(value) {
var valueType = typeof value === 'undefined' ? 'undefined' : _typeof(value);
if (valueType === 'string' || value instanceof Buffer) {
return Buffer.from(value).length > MAX_INDEX_SIZE;
} else if (valueType === 'object' && value !== null) {
// Must be an object, recursively build response
return Object.keys(value).map(function (key) {
return value[key];
}).some(isBig);
}
// Number, boolean, undefined and null we either don't care about or are
// guaranteed < 1500
return false;
}
function expandData(data) {
var toBasicResponse = function toBasicResponse(name) {
return { name: name, value: data[name] };
},
addExclusions = function addExclusions(response) {
if (dontIndex.includes(response.name)) {
response.excludeFromIndexes = true;
} else if (autoIndex) {
response.excludeFromIndexes = isBig(response.value);
}
return response;
};
return Object.keys(data).map(toBasicResponse).map(addExclusions);
}
if (Array.isArray(entity)) {
return entity.map(function (e) {
return _this9.makeExplicitEntity(e, options);
}, this);
}
return {
key: Datastore.getKey(entity),
data: expandData(entity.data)
};
}
}], [{
key: 'getKey',
value: function getKey(entity) {
return entity[_datastore2.default.KEY] || entity.key;
}
}]);
return Datastore;
}();
function init(options) {
debug('Initializing feathers-datastore plugin');
return new Datastore(options);
}
init.Service = Datastore;
module.exports = exports['default'];