UNPKG

@koehlerb/feathers-datastore

Version:
497 lines (396 loc) 16.6 kB
'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'];