UNPKG

gun-flint

Version:

Micro-framework for building Gun adapters

423 lines (345 loc) 12.5 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); var _inherits2 = require('babel-runtime/helpers/inherits'); var _inherits3 = _interopRequireDefault(_inherits2); var _baseExtension = require('./../base-extension'); var _baseExtension2 = _interopRequireDefault(_baseExtension); var _adapterContext = require('./adapter-context'); var _adapterContext2 = _interopRequireDefault(_adapterContext); var _baseMixin = require('./../Mixin/base-mixin'); var _baseMixin2 = _interopRequireDefault(_baseMixin); var _util = require('./../util'); var _util2 = _interopRequireDefault(_util); var _union = require('./union'); var _union2 = _interopRequireDefault(_union); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * The base class for all adapters * * Children must, at a minimum extend two methods: * * <code> * <ul> * <li>read: handle results from a `get`</li> * <li>write: handle a `put` request</li> * </ul> * </code> * * Additionally, the following methods can be overwritten: * * <code> * <ul> * <li>get</li> * <li>afterRead</li> * <li>afterWrite</li> * </ul> * </code> * * Here is the flow through the BaseAdapter for a `get` and `put`: * * Gun.on('get') -> _read -> get -> _get [Adapter does work] -> read -> afterRead * Gun.on('put') -> _write -> write -> _put [Adapter does work] -> afterWrite * * @class * @extends BaseExtension */ var BaseAdapter = function (_BaseExtension) { (0, _inherits3.default)(BaseAdapter, _BaseExtension); /* lifecyle */ /** * Build the adapter. This takes an object as its only parameter. * * @param {object} adapter */ function BaseAdapter(adapter) { var _ret; (0, _classCallCheck3.default)(this, BaseAdapter); var _this2 = (0, _possibleConstructorReturn3.default)(this, (BaseAdapter.__proto__ || (0, _getPrototypeOf2.default)(BaseAdapter)).call(this)); _this2.outerContext = _adapterContext2.default.make(_this2); // Bind the three adapter methods to the `this` context _this2._opt = adapter.opt ? adapter.opt.bind(_this2.outerContext) : _util2.default.noop; _this2._get = adapter.get ? adapter.get.bind(_this2.outerContext) : _util2.default.noop; _this2._put = adapter.put ? adapter.put.bind(_this2.outerContext) : _util2.default.noop; // Bind all adapter methods to the outer context for (var methodName in adapter) { if ({}.hasOwnProperty.call(adapter, methodName) && ['opt', 'get', 'put', 'on'].indexOf(methodName === -1)) { if (typeof adapter[methodName] === 'function') { _this2.outerContext[methodName] = adapter[methodName].bind(_this2.outerContext); } else { _this2.outerContext[methodName] = adapter[methodName]; } } } // Prepare a dedupid hash _this2.__dedupIds = {}; // Bind context for read and write methods // These receive context from Gun when they are called _this2._read = _this2._read.bind(_this2); _this2._write = _this2._write.bind(_this2); // Apply Mixins if (adapter.mixins && adapter.mixins.length) { adapter.mixins.forEach(function (mixin) { if (mixin && mixin.prototype instanceof _baseMixin2.default) { new mixin(_this2); } }); } // finish return _ret = _this2, (0, _possibleConstructorReturn3.default)(_this2, _ret); } /* public */ /** * Bootstrap the adapter. Flint calls this method when the adapter is registered * * @instance * @public * * @param {Gun} Gun The Gun constructor */ (0, _createClass3.default)(BaseAdapter, [{ key: 'bootstrap', value: function bootstrap(Gun) { this.Gun = Gun || require('gun/gun'); this.outerContext.Gun = Gun; if (!this.Gun) { throw "Unable to retrieve a Gun instance. This is probably because you tried to import Gun after this Gun adapter. Makes sure that you import all adapter after you've imported Gun."; } var _this = this; this.Gun.on('opt', function (context) { this.to.next(context); _this.opt(context); if (context.once) { return; } // Allows other plugins to respond concurrently. var pluginInterop = function pluginInterop(middleware) { return function (context) { this.to.next(context); return middleware(context); }; }; // Register the driver. context.on('get', pluginInterop(_this._read)); context.on('put', pluginInterop(_this._write)); }); return this.Gun; } /** * Handle Gun `opt` event * * @instance * @public * * @param {object} context The gun context firing the event * * @returns {void} */ }, { key: 'opt', value: function opt(context) { this.context = context; this._opt(context, context.opt || {}, !!context.once); } /** * Handle a read result from an adapter. * * @instance * @public * * @param {mixed} context Results from adapter read * @param {done} callback Callback to call with err, results as params * * @returns {void} */ }, { key: 'read', value: function read(results, done) { throw "Adapter implementations must extend the `read` method"; } /** * Pass a write event to the adaper; formatting if necessary * * @public * @instance * * @param {object} delta A Gun write-delta * @param {done} callback Callback to call with err (if any) as param * * @returns {void} */ }, { key: 'write', value: function write(delta, done) { throw "Adapter implementations must extend the `write` method"; } /** * Pass the results of a read request into Gun * * @param {string} dedupId Dedup ID passed by Gun during read request * @param {Error} [err] Error (if any) that occured during read * @param {object} data A node formatted in a reconizable format for Gun * * @returns {void} */ }, { key: 'afterRead', value: function afterRead(dedupId, err, data) { this._recordGet(dedupId); this.context.on('in', { '@': dedupId, put: this.Gun.graph.node(data), err: err }); } /** * Pass the results of a read request into Gun * * @public * @instance * * @param {string} dedupId Dedup ID passed by Gun during read request * @param {Error} [err] Error (if any) that occured during read * * @returns {void} */ }, { key: 'afterWrite', value: function afterWrite(dedupId, err) { // Report whether it succeeded. this.context.on('in', { '@': dedupId, ok: !err, err: err }); } /** * @instance * @public * * @param {string} key The UUID for the node to retrieve * @param {string} [field] If supplied, get a single field; otherwise full node is requested * @param {callback} done Callback after retrieval is finished */ }, { key: 'get', value: function get(key, field, done) { this._get(key, field, done); } /* private api */ /** * Handle Gun 'get' events * * @instance * @private * * @param {Object} context - A gun request context. * @returns {void} */ }, { key: '_read', value: function _read(context) { var _this3 = this; var dedupId = context['#'], get = context.get; var key = get['#']; // When field === '_', the entire node // is requested; otherwise, the property // name is used. Flint will simply pass // null if entire node is requested. var field = get['.'] !== '_' ? get['.'] : null; // Read from adapter. var _this = this; return this.get(key, field, function (err, result) { // Error handling. if (err) { if (err.code() === _this3.outerContext.errors.codes.lost) { // Tell gun nothing was found. _this3.afterRead(dedupId, null, null); } else { _this3.afterRead(dedupId, err, null); } } else { // Pass the result to child implementations // Children should know how to handle results // And return a valid GUN object _this.read(result, _this3.afterRead.bind(_this, dedupId)); } }); } /** * Handle Gun 'put' events * * @instance * @private * * @param {object} context Gun write context */ }, { key: '_write', value: function _write(context) { var delta = context.put, dedupId = context['#']; // Filter out returns from the `get` requests if (this._shouldWrite(context['@'])) { // Pass to child implementation return this.write(delta, this.afterWrite.bind(this, dedupId)); } else { // Acknowledge write this.afterWrite(dedupId, null); } } /** * Check the dedup hash to ensure that anything that was * just pulled from the adapter is passed back in as a write * * @instance * @private * * @param {string} dedupId The request dedup * @return {boolean} Whether or not the `PUT` linked to this dedupid should be written */ }, { key: '_shouldWrite', value: function _shouldWrite(dedupId) { if (!dedupId) { return true; } var shouldWrite = !Boolean(this.__dedupIds[dedupId]); if (this.__dedupIds[dedupId]) { this.__dedupIds[dedupId]--; if (this.__dedupIds[dedupId] === 0) { delete this.__dedupIds[dedupId]; } } return shouldWrite; } /** * Every get should record its dedupId during retrieval in the hash. * * @instance * @private * * @param {string} dedupId */ }, { key: '_recordGet', value: function _recordGet(dedupId) { if (!this.__dedupIds[dedupId]) { this.__dedupIds[dedupId] = 0; } this.__dedupIds[dedupId]++; } }]); return BaseAdapter; }(_baseExtension2.default); exports.default = BaseAdapter;