gun-flint
Version:
Micro-framework for building Gun adapters
423 lines (345 loc) • 12.5 kB
JavaScript
'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;