bcoin: start switching to class syntax.
This commit is contained in:
parent
f9eba3f5a6
commit
a79c2b0b1a
File diff suppressed because it is too large
Load Diff
@ -690,7 +690,7 @@ Mempool.prototype.has = function has(hash) {
|
||||
*/
|
||||
|
||||
Mempool.prototype.exists = function exists(hash) {
|
||||
if (this.locker.hasPending(hash))
|
||||
if (this.locker.pending(hash))
|
||||
return true;
|
||||
|
||||
if (this.hasOrphan(hash))
|
||||
|
||||
@ -93,6 +93,14 @@ exports.MAX_BLOCK_SIGOPS = 1000000 / 50;
|
||||
|
||||
exports.MAX_BLOCK_SIGOPS_COST = 80000;
|
||||
|
||||
/**
|
||||
* Size of set to pick median time from.
|
||||
* @const {Number}
|
||||
* @default
|
||||
*/
|
||||
|
||||
exports.MEDIAN_TIMESPAN = 11;
|
||||
|
||||
/**
|
||||
* What bits to set in version
|
||||
* for versionbits blocks.
|
||||
|
||||
@ -9,388 +9,428 @@
|
||||
const assert = require('assert');
|
||||
|
||||
/**
|
||||
* Represents a promise-resolving event emitter.
|
||||
* Async Emitter
|
||||
* @alias module:utils.AsyncEmitter
|
||||
* @see EventEmitter
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
function AsyncEmitter() {
|
||||
if (!(this instanceof AsyncEmitter))
|
||||
return new AsyncEmitter();
|
||||
class AsyncEmitter {
|
||||
/**
|
||||
* Create an async emitter.
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
this._events = Object.create(null);
|
||||
constructor() {
|
||||
this._events = Object.create(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener.
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
*/
|
||||
|
||||
addListener(type, handler) {
|
||||
return this._push(type, handler, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener.
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
*/
|
||||
|
||||
on(type, handler) {
|
||||
return this.addListener(type, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener to execute once.
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
*/
|
||||
|
||||
once(type, handler) {
|
||||
return this._push(type, handler, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend a listener.
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
*/
|
||||
|
||||
prependListener(type, handler) {
|
||||
return this._unshift(type, handler, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend a listener to execute once.
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
*/
|
||||
|
||||
prependOnceListener(type, handler) {
|
||||
return this._unshift(type, handler, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a listener.
|
||||
* @private
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
* @param {Boolean} once
|
||||
*/
|
||||
|
||||
_push(type, handler, once) {
|
||||
assert(typeof type === 'string', '`type` must be a string.');
|
||||
|
||||
if (!this._events[type])
|
||||
this._events[type] = [];
|
||||
|
||||
this.emit('newListener', type, handler);
|
||||
|
||||
this._events[type].push(new Listener(handler, once));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unshift a listener.
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
* @param {Boolean} once
|
||||
*/
|
||||
|
||||
_unshift(type, handler, once) {
|
||||
assert(typeof type === 'string', '`type` must be a string.');
|
||||
|
||||
if (!this._events[type])
|
||||
this._events[type] = [];
|
||||
|
||||
this.emit('newListener', type, handler);
|
||||
|
||||
this._events[type].unshift(new Listener(handler, once));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a listener.
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
*/
|
||||
|
||||
removeListener(type, handler) {
|
||||
assert(typeof type === 'string', '`type` must be a string.');
|
||||
|
||||
const listeners = this._events[type];
|
||||
|
||||
if (!listeners)
|
||||
return;
|
||||
|
||||
let index = -1;
|
||||
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
const listener = listeners[i];
|
||||
if (listener.handler === handler) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index === -1)
|
||||
return;
|
||||
|
||||
splice(listeners, index);
|
||||
|
||||
if (listeners.length === 0)
|
||||
delete this._events[type];
|
||||
|
||||
this.emit('removeListener', type, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set max listeners.
|
||||
* @param {Number} max
|
||||
*/
|
||||
|
||||
setMaxListeners(max) {
|
||||
assert(typeof max === 'number', '`max` must be a number.');
|
||||
assert(max >= 0, '`max` must be non-negative.');
|
||||
assert(Number.isSafeInteger(max), '`max` must be an integer.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all listeners.
|
||||
* @param {String?} type
|
||||
*/
|
||||
|
||||
removeAllListeners(type) {
|
||||
if (arguments.length === 0) {
|
||||
this._events = Object.create(null);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(typeof type === 'string', '`type` must be a string.');
|
||||
|
||||
delete this._events[type];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get listeners array.
|
||||
* @param {String} type
|
||||
* @returns {Function[]}
|
||||
*/
|
||||
|
||||
listeners(type) {
|
||||
assert(typeof type === 'string', '`type` must be a string.');
|
||||
|
||||
const listeners = this._events[type];
|
||||
|
||||
if (!listeners)
|
||||
return [];
|
||||
|
||||
const result = [];
|
||||
|
||||
for (const {handler} of listeners)
|
||||
result.push(handler);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get listener count for an event.
|
||||
* @param {String} type
|
||||
*/
|
||||
|
||||
listenerCount(type) {
|
||||
assert(typeof type === 'string', '`type` must be a string.');
|
||||
|
||||
const listeners = this._events[type];
|
||||
|
||||
if (!listeners)
|
||||
return 0;
|
||||
|
||||
return listeners.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event names.
|
||||
* @returns {String[]}
|
||||
*/
|
||||
|
||||
eventNames() {
|
||||
return Object.keys(this._events);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an event synchronously.
|
||||
* @param {String} type
|
||||
* @param {...Object} args
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
emit(type) {
|
||||
try {
|
||||
this._emit.apply(this, arguments);
|
||||
} catch (e) {
|
||||
if (type === 'error')
|
||||
throw e;
|
||||
|
||||
this._emit('error', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an event synchronously.
|
||||
* @private
|
||||
* @param {String} type
|
||||
* @param {...Object} args
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
_emit(type) {
|
||||
assert(typeof type === 'string', '`type` must be a string.');
|
||||
|
||||
const listeners = this._events[type];
|
||||
|
||||
if (!listeners) {
|
||||
if (type === 'error') {
|
||||
const msg = arguments[1];
|
||||
|
||||
if (msg instanceof Error)
|
||||
throw msg;
|
||||
|
||||
const err = new Error(`Uncaught, unspecified "error" event. (${msg})`);
|
||||
err.context = msg;
|
||||
throw err;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert(listeners.length > 0);
|
||||
|
||||
let args = null;
|
||||
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
const listener = listeners[i];
|
||||
const handler = listener.handler;
|
||||
|
||||
if (listener.once) {
|
||||
splice(listeners, i);
|
||||
if (listeners.length === 0)
|
||||
delete this._events[type];
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
switch (arguments.length) {
|
||||
case 1:
|
||||
handler();
|
||||
break;
|
||||
case 2:
|
||||
handler(arguments[1]);
|
||||
break;
|
||||
case 3:
|
||||
handler(arguments[1], arguments[2]);
|
||||
break;
|
||||
case 4:
|
||||
handler(arguments[1], arguments[2], arguments[3]);
|
||||
break;
|
||||
default:
|
||||
if (!args) {
|
||||
args = new Array(arguments.length - 1);
|
||||
for (let j = 1; j < arguments.length; j++)
|
||||
args[j - 1] = arguments[j];
|
||||
}
|
||||
handler.apply(null, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an event. Wait for promises to resolve.
|
||||
* @method
|
||||
* @param {String} type
|
||||
* @param {...Object} args
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async emitAsync(type) {
|
||||
try {
|
||||
await this._emitAsync.apply(this, arguments);
|
||||
} catch (e) {
|
||||
if (type === 'error')
|
||||
throw e;
|
||||
|
||||
await this._emitAsync('error', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an event. Wait for promises to resolve.
|
||||
* @private
|
||||
* @param {String} type
|
||||
* @param {...Object} args
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async _emitAsync(type) {
|
||||
assert(typeof type === 'string', '`type` must be a string.');
|
||||
|
||||
const listeners = this._events[type];
|
||||
|
||||
if (!listeners) {
|
||||
if (type === 'error') {
|
||||
const msg = arguments[1];
|
||||
|
||||
if (msg instanceof Error)
|
||||
throw msg;
|
||||
|
||||
const err = new Error(`Uncaught, unspecified "error" event. (${msg})`);
|
||||
err.context = msg;
|
||||
throw err;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert(listeners.length > 0);
|
||||
|
||||
let args = null;
|
||||
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
const listener = listeners[i];
|
||||
const handler = listener.handler;
|
||||
|
||||
if (listener.once) {
|
||||
splice(listeners, i);
|
||||
if (listeners.length === 0)
|
||||
delete this._events[type];
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
switch (arguments.length) {
|
||||
case 1:
|
||||
await handler();
|
||||
break;
|
||||
case 2:
|
||||
await handler(arguments[1]);
|
||||
break;
|
||||
case 3:
|
||||
await handler(arguments[1], arguments[2]);
|
||||
break;
|
||||
case 4:
|
||||
await handler(arguments[1], arguments[2], arguments[3]);
|
||||
break;
|
||||
default:
|
||||
if (!args) {
|
||||
args = new Array(arguments.length - 1);
|
||||
for (let j = 1; j < arguments.length; j++)
|
||||
args[j - 1] = arguments[j];
|
||||
}
|
||||
await handler.apply(null, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener.
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype.addListener = function addListener(type, handler) {
|
||||
return this._push(type, handler, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a listener.
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype.on = function on(type, handler) {
|
||||
return this.addListener(type, handler);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a listener to execute once.
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype.once = function once(type, handler) {
|
||||
return this._push(type, handler, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepend a listener.
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype.prependListener = function prependListener(type, handler) {
|
||||
return this._unshift(type, handler, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepend a listener to execute once.
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype.prependOnceListener = function prependOnceListener(type, handler) {
|
||||
return this._unshift(type, handler, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Push a listener.
|
||||
* @private
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
* @param {Boolean} once
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype._push = function _push(type, handler, once) {
|
||||
assert(typeof type === 'string', '`type` must be a string.');
|
||||
|
||||
if (!this._events[type])
|
||||
this._events[type] = [];
|
||||
|
||||
this._events[type].push(new Listener(handler, once));
|
||||
|
||||
this.emit('newListener', type, handler);
|
||||
};
|
||||
|
||||
/**
|
||||
* Unshift a listener.
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
* @param {Boolean} once
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype._unshift = function _unshift(type, handler, once) {
|
||||
assert(typeof type === 'string', '`type` must be a string.');
|
||||
|
||||
if (!this._events[type])
|
||||
this._events[type] = [];
|
||||
|
||||
this._events[type].unshift(new Listener(handler, once));
|
||||
|
||||
this.emit('newListener', type, handler);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a listener.
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype.removeListener = function removeListener(type, handler) {
|
||||
assert(typeof type === 'string', '`type` must be a string.');
|
||||
|
||||
const listeners = this._events[type];
|
||||
|
||||
if (!listeners)
|
||||
return;
|
||||
|
||||
let index = -1;
|
||||
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
const listener = listeners[i];
|
||||
if (listener.handler === handler) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index === -1)
|
||||
return;
|
||||
|
||||
listeners.splice(index, 1);
|
||||
|
||||
if (listeners.length === 0)
|
||||
delete this._events[type];
|
||||
|
||||
this.emit('removeListener', type, handler);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set max listeners.
|
||||
* @param {Number} max
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype.setMaxListeners = function setMaxListeners(max) {
|
||||
assert(typeof max === 'number', '`max` must be a number.');
|
||||
assert(max >= 0, '`max` must be non-negative.');
|
||||
assert(Number.isSafeInteger(max), '`max` must be an integer.');
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove all listeners.
|
||||
* @param {String?} type
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype.removeAllListeners = function removeAllListeners(type) {
|
||||
if (arguments.length === 0) {
|
||||
this._events = Object.create(null);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(typeof type === 'string', '`type` must be a string.');
|
||||
|
||||
delete this._events[type];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get listeners array.
|
||||
* @param {String} type
|
||||
* @returns {Function[]}
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype.listeners = function listeners(type) {
|
||||
assert(typeof type === 'string', '`type` must be a string.');
|
||||
|
||||
const listeners = this._events[type];
|
||||
|
||||
if (!listeners)
|
||||
return [];
|
||||
|
||||
const result = [];
|
||||
|
||||
for (const {handler} of listeners)
|
||||
result.push(handler);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get listener count for an event.
|
||||
* @param {String} type
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype.listenerCount = function listenerCount(type) {
|
||||
assert(typeof type === 'string', '`type` must be a string.');
|
||||
|
||||
const listeners = this._events[type];
|
||||
|
||||
if (!listeners)
|
||||
return 0;
|
||||
|
||||
return listeners.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit an event synchronously.
|
||||
* @param {String} type
|
||||
* @param {...Object} args
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype.emit = function emit(type) {
|
||||
try {
|
||||
this._emit.apply(this, arguments);
|
||||
} catch (e) {
|
||||
if (type === 'error')
|
||||
throw e;
|
||||
|
||||
this._emit('error', e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit an event synchronously.
|
||||
* @private
|
||||
* @param {String} type
|
||||
* @param {...Object} args
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype._emit = function _emit(type) {
|
||||
assert(typeof type === 'string', '`type` must be a string.');
|
||||
|
||||
const listeners = this._events[type];
|
||||
|
||||
if (!listeners) {
|
||||
if (type === 'error') {
|
||||
const error = arguments[1];
|
||||
|
||||
if (error instanceof Error)
|
||||
throw error;
|
||||
|
||||
const err = new Error(`Uncaught, unspecified "error" event. (${error})`);
|
||||
err.context = error;
|
||||
throw err;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert(listeners.length > 0);
|
||||
|
||||
let args = null;
|
||||
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
const listener = listeners[i];
|
||||
const handler = listener.handler;
|
||||
|
||||
if (listener.once) {
|
||||
listeners.splice(i, 1);
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
switch (arguments.length) {
|
||||
case 1:
|
||||
handler();
|
||||
break;
|
||||
case 2:
|
||||
handler(arguments[1]);
|
||||
break;
|
||||
case 3:
|
||||
handler(arguments[1], arguments[2]);
|
||||
break;
|
||||
case 4:
|
||||
handler(arguments[1], arguments[2], arguments[3]);
|
||||
break;
|
||||
default:
|
||||
if (!args) {
|
||||
args = new Array(arguments.length - 1);
|
||||
for (let j = 1; j < arguments.length; j++)
|
||||
args[j - 1] = arguments[j];
|
||||
}
|
||||
handler.apply(null, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit an event. Wait for promises to resolve.
|
||||
* @method
|
||||
* @param {String} type
|
||||
* @param {...Object} args
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype.emitAsync = async function emitAsync(type) {
|
||||
try {
|
||||
await this._emitAsync.apply(this, arguments);
|
||||
} catch (e) {
|
||||
if (type === 'error')
|
||||
throw e;
|
||||
|
||||
await this._emitAsync('error', e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit an event. Wait for promises to resolve.
|
||||
* @private
|
||||
* @param {String} type
|
||||
* @param {...Object} args
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype._emitAsync = async function _emitAsync(type) {
|
||||
assert(typeof type === 'string', '`type` must be a string.');
|
||||
|
||||
const listeners = this._events[type];
|
||||
|
||||
if (!listeners) {
|
||||
if (type === 'error') {
|
||||
const error = arguments[1];
|
||||
|
||||
if (error instanceof Error)
|
||||
throw error;
|
||||
|
||||
const err = new Error(`Uncaught, unspecified "error" event. (${error})`);
|
||||
err.context = error;
|
||||
throw err;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert(listeners.length > 0);
|
||||
|
||||
let args = null;
|
||||
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
const listener = listeners[i];
|
||||
const handler = listener.handler;
|
||||
|
||||
if (listener.once) {
|
||||
listeners.splice(i, 1);
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
switch (arguments.length) {
|
||||
case 1:
|
||||
await handler();
|
||||
break;
|
||||
case 2:
|
||||
await handler(arguments[1]);
|
||||
break;
|
||||
case 3:
|
||||
await handler(arguments[1], arguments[2]);
|
||||
break;
|
||||
case 4:
|
||||
await handler(arguments[1], arguments[2], arguments[3]);
|
||||
break;
|
||||
default:
|
||||
if (!args) {
|
||||
args = new Array(arguments.length - 1);
|
||||
for (let j = 1; j < arguments.length; j++)
|
||||
args[j - 1] = arguments[j];
|
||||
}
|
||||
await handler.apply(null, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Event Listener
|
||||
* @constructor
|
||||
* @ignore
|
||||
* @param {Function} handler
|
||||
* @param {Boolean} once
|
||||
* @property {Function} handler
|
||||
* @property {Boolean} once
|
||||
*/
|
||||
|
||||
function Listener(handler, once) {
|
||||
assert(typeof handler === 'function', '`handler` must be a function.');
|
||||
assert(typeof once === 'boolean', '`once` must be a function.');
|
||||
this.handler = handler;
|
||||
this.once = once;
|
||||
class Listener {
|
||||
/**
|
||||
* Create an event listener.
|
||||
* @constructor
|
||||
* @param {Function} handler
|
||||
* @param {Boolean} once
|
||||
*/
|
||||
|
||||
constructor(handler, once) {
|
||||
assert(typeof handler === 'function', '`handler` must be a function.');
|
||||
assert(typeof once === 'boolean', '`once` must be a function.');
|
||||
this.handler = handler;
|
||||
this.once = once;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
function splice(list, i) {
|
||||
if (i === 0) {
|
||||
list.shift();
|
||||
return;
|
||||
}
|
||||
|
||||
let k = i + 1;
|
||||
|
||||
while (k < list.length)
|
||||
list[i++] = list[k++];
|
||||
|
||||
list.pop();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@ -79,7 +79,25 @@ exports.remove = function remove(items, item, compare) {
|
||||
if (i === -1)
|
||||
return false;
|
||||
|
||||
items.splice(i, 1);
|
||||
splice(items, i);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/*
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
function splice(list, i) {
|
||||
if (i === 0) {
|
||||
list.shift();
|
||||
return;
|
||||
}
|
||||
|
||||
let k = i + 1;
|
||||
|
||||
while (k < list.length)
|
||||
list[i++] = list[k++];
|
||||
|
||||
list.pop();
|
||||
}
|
||||
|
||||
@ -11,221 +11,224 @@ const assert = require('assert');
|
||||
/**
|
||||
* Binary Heap
|
||||
* @alias module:utils.Heap
|
||||
* @constructor
|
||||
* @param {Function?} compare
|
||||
*/
|
||||
|
||||
function Heap(compare) {
|
||||
if (!(this instanceof Heap))
|
||||
return new Heap(compare);
|
||||
class Heap {
|
||||
/**
|
||||
* Create a binary heap.
|
||||
* @constructor
|
||||
* @param {Function?} compare
|
||||
*/
|
||||
|
||||
this.compare = comparator;
|
||||
this.items = [];
|
||||
constructor(compare) {
|
||||
this.compare = comparator;
|
||||
this.items = [];
|
||||
|
||||
if (compare)
|
||||
this.set(compare);
|
||||
if (compare)
|
||||
this.set(compare);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize and sort heap.
|
||||
*/
|
||||
|
||||
init() {
|
||||
const n = this.items.length;
|
||||
|
||||
if (n <= 1)
|
||||
return;
|
||||
|
||||
for (let i = (n / 2 | 0) - 1; i >= 0; i--)
|
||||
this.down(i, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get heap size.
|
||||
* @returns {Number}
|
||||
*/
|
||||
|
||||
size() {
|
||||
return this.items.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set comparator.
|
||||
* @param {Function} compare
|
||||
*/
|
||||
|
||||
set(compare) {
|
||||
assert(typeof compare === 'function',
|
||||
'Comparator must be a function.');
|
||||
this.compare = compare;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push item onto heap.
|
||||
* @param {Object} item
|
||||
* @returns {Number}
|
||||
*/
|
||||
|
||||
insert(item) {
|
||||
this.items.push(item);
|
||||
this.up(this.items.length - 1);
|
||||
return this.items.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop next item off of heap.
|
||||
* @param {Object} item
|
||||
* @returns {Object}
|
||||
*/
|
||||
|
||||
shift() {
|
||||
if (this.items.length === 0)
|
||||
return null;
|
||||
|
||||
const n = this.items.length - 1;
|
||||
|
||||
this.swap(0, n);
|
||||
this.down(0, n);
|
||||
|
||||
return this.items.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove item from heap.
|
||||
* @param {Number} i
|
||||
* @returns {Object}
|
||||
*/
|
||||
|
||||
remove(i) {
|
||||
if (this.items.length === 0)
|
||||
return null;
|
||||
|
||||
const n = this.items.length - 1;
|
||||
|
||||
if (i < 0 || i > n)
|
||||
return null;
|
||||
|
||||
if (n !== i) {
|
||||
this.swap(i, n);
|
||||
this.down(i, n);
|
||||
this.up(i);
|
||||
}
|
||||
|
||||
return this.items.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap indicies.
|
||||
* @private
|
||||
* @param {Number} a
|
||||
* @param {Number} b
|
||||
*/
|
||||
|
||||
swap(a, b) {
|
||||
const x = this.items[a];
|
||||
const y = this.items[b];
|
||||
this.items[a] = y;
|
||||
this.items[b] = x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare indicies.
|
||||
* @private
|
||||
* @param {Number} i
|
||||
* @param {Number} j
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
less(i, j) {
|
||||
return this.compare(this.items[i], this.items[j]) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bubble item down.
|
||||
* @private
|
||||
* @param {Number} i
|
||||
* @param {Number} n
|
||||
*/
|
||||
|
||||
down(i, n) {
|
||||
for (;;) {
|
||||
const l = 2 * i + 1;
|
||||
|
||||
assert(l >= 0);
|
||||
|
||||
if (l < 0 || l >= n)
|
||||
break;
|
||||
|
||||
let j = l;
|
||||
const r = l + 1;
|
||||
|
||||
if (r < n && !this.less(l, r))
|
||||
j = r;
|
||||
|
||||
if (!this.less(j, i))
|
||||
break;
|
||||
|
||||
this.swap(i, j);
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bubble item up.
|
||||
* @private
|
||||
* @param {Number} i
|
||||
*/
|
||||
|
||||
up(i) {
|
||||
for (;;) {
|
||||
const j = (i - 1) / 2 | 0;
|
||||
|
||||
assert(j >= 0);
|
||||
|
||||
if (j < 0 || j === i)
|
||||
break;
|
||||
|
||||
if (!this.less(i, j))
|
||||
break;
|
||||
|
||||
this.swap(j, i);
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert heap to sorted array.
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
|
||||
toArray() {
|
||||
const heap = new Heap();
|
||||
const result = [];
|
||||
|
||||
heap.compare = this.compare;
|
||||
heap.items = this.items.slice();
|
||||
|
||||
while (heap.size() > 0)
|
||||
result.push(heap.shift());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate heap from array and comparator.
|
||||
* @param {Function} compare
|
||||
* @param {Object[]} items
|
||||
* @returns {Heap}
|
||||
*/
|
||||
|
||||
static fromArray(compare, items) {
|
||||
const heap = new Heap();
|
||||
heap.set(compare);
|
||||
heap.items = items;
|
||||
heap.init();
|
||||
return heap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize and sort heap.
|
||||
*/
|
||||
|
||||
Heap.prototype.init = function init() {
|
||||
const n = this.items.length;
|
||||
|
||||
if (n <= 1)
|
||||
return;
|
||||
|
||||
for (let i = (n / 2 | 0) - 1; i >= 0; i--)
|
||||
this.down(i, n);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get heap size.
|
||||
* @returns {Number}
|
||||
*/
|
||||
|
||||
Heap.prototype.size = function size() {
|
||||
return this.items.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set comparator.
|
||||
* @param {Function} compare
|
||||
*/
|
||||
|
||||
Heap.prototype.set = function set(compare) {
|
||||
assert(typeof compare === 'function',
|
||||
'Comparator must be a function.');
|
||||
this.compare = compare;
|
||||
};
|
||||
|
||||
/**
|
||||
* Push item onto heap.
|
||||
* @param {Object} item
|
||||
* @returns {Number}
|
||||
*/
|
||||
|
||||
Heap.prototype.insert = function insert(item) {
|
||||
this.items.push(item);
|
||||
this.up(this.items.length - 1);
|
||||
return this.items.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Pop next item off of heap.
|
||||
* @param {Object} item
|
||||
* @returns {Object}
|
||||
*/
|
||||
|
||||
Heap.prototype.shift = function shift() {
|
||||
if (this.items.length === 0)
|
||||
return null;
|
||||
|
||||
const n = this.items.length - 1;
|
||||
|
||||
this.swap(0, n);
|
||||
this.down(0, n);
|
||||
|
||||
return this.items.pop();
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove item from heap.
|
||||
* @param {Number} i
|
||||
* @returns {Object}
|
||||
*/
|
||||
|
||||
Heap.prototype.remove = function remove(i) {
|
||||
if (this.items.length === 0)
|
||||
return null;
|
||||
|
||||
const n = this.items.length - 1;
|
||||
|
||||
if (i < 0 || i > n)
|
||||
return null;
|
||||
|
||||
if (n !== i) {
|
||||
this.swap(i, n);
|
||||
this.down(i, n);
|
||||
this.up(i);
|
||||
}
|
||||
|
||||
return this.items.pop();
|
||||
};
|
||||
|
||||
/**
|
||||
* Swap indicies.
|
||||
* @private
|
||||
* @param {Number} a
|
||||
* @param {Number} b
|
||||
*/
|
||||
|
||||
Heap.prototype.swap = function swap(a, b) {
|
||||
const x = this.items[a];
|
||||
const y = this.items[b];
|
||||
this.items[a] = y;
|
||||
this.items[b] = x;
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare indicies.
|
||||
* @private
|
||||
* @param {Number} i
|
||||
* @param {Number} j
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
Heap.prototype.less = function less(i, j) {
|
||||
return this.compare(this.items[i], this.items[j]) < 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Bubble item down.
|
||||
* @private
|
||||
* @param {Number} i
|
||||
* @param {Number} n
|
||||
*/
|
||||
|
||||
Heap.prototype.down = function down(i, n) {
|
||||
for (;;) {
|
||||
const l = 2 * i + 1;
|
||||
|
||||
assert(l >= 0);
|
||||
|
||||
if (l < 0 || l >= n)
|
||||
break;
|
||||
|
||||
let j = l;
|
||||
const r = l + 1;
|
||||
|
||||
if (r < n && !this.less(l, r))
|
||||
j = r;
|
||||
|
||||
if (!this.less(j, i))
|
||||
break;
|
||||
|
||||
this.swap(i, j);
|
||||
i = j;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Bubble item up.
|
||||
* @private
|
||||
* @param {Number} i
|
||||
*/
|
||||
|
||||
Heap.prototype.up = function up(i) {
|
||||
for (;;) {
|
||||
const j = (i - 1) / 2 | 0;
|
||||
|
||||
assert(j >= 0);
|
||||
|
||||
if (j < 0 || j === i)
|
||||
break;
|
||||
|
||||
if (!this.less(i, j))
|
||||
break;
|
||||
|
||||
this.swap(j, i);
|
||||
i = j;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert heap to sorted array.
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
|
||||
Heap.prototype.toArray = function toArray() {
|
||||
const heap = new Heap();
|
||||
const result = [];
|
||||
|
||||
heap.compare = this.compare;
|
||||
heap.items = this.items.slice();
|
||||
|
||||
while (heap.size() > 0)
|
||||
result.push(heap.shift());
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate heap from array and comparator.
|
||||
* @param {Function} compare
|
||||
* @param {Object[]} items
|
||||
* @returns {Heap}
|
||||
*/
|
||||
|
||||
Heap.fromArray = function fromArray(compare, items) {
|
||||
const heap = new Heap();
|
||||
heap.set(compare);
|
||||
heap.items = items;
|
||||
heap.init();
|
||||
return heap;
|
||||
};
|
||||
|
||||
/*
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
@ -9,263 +9,273 @@
|
||||
const assert = require('assert');
|
||||
|
||||
/**
|
||||
* A double linked list.
|
||||
* Double Linked List
|
||||
* @alias module:utils.List
|
||||
* @constructor
|
||||
* @property {ListItem|null} head
|
||||
* @property {ListItem|null} tail
|
||||
* @property {Number} size
|
||||
*/
|
||||
|
||||
function List() {
|
||||
if (!(this instanceof List))
|
||||
return new List();
|
||||
class List {
|
||||
/**
|
||||
* Create a list.
|
||||
* @constructor
|
||||
* @property {ListItem|null} head
|
||||
* @property {ListItem|null} tail
|
||||
* @property {Number} size
|
||||
*/
|
||||
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the cache. Clear all items.
|
||||
*/
|
||||
|
||||
List.prototype.reset = function reset() {
|
||||
let item, next;
|
||||
|
||||
for (item = this.head; item; item = next) {
|
||||
next = item.next;
|
||||
item.prev = null;
|
||||
item.next = null;
|
||||
constructor() {
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
assert(!item);
|
||||
/**
|
||||
* Reset the cache. Clear all items.
|
||||
*/
|
||||
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
this.size = 0;
|
||||
};
|
||||
reset() {
|
||||
let item, next;
|
||||
|
||||
/**
|
||||
* Remove the first item in the list.
|
||||
* @returns {ListItem}
|
||||
*/
|
||||
|
||||
List.prototype.shift = function shift() {
|
||||
const item = this.head;
|
||||
|
||||
if (!item)
|
||||
return null;
|
||||
|
||||
this.remove(item);
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepend an item to the linked list (sets new head).
|
||||
* @param {ListItem}
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
List.prototype.unshift = function unshift(item) {
|
||||
return this.insert(null, item);
|
||||
};
|
||||
|
||||
/**
|
||||
* Append an item to the linked list (sets new tail).
|
||||
* @param {ListItem}
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
List.prototype.push = function push(item) {
|
||||
return this.insert(this.tail, item);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove the last item in the list.
|
||||
* @returns {ListItem}
|
||||
*/
|
||||
|
||||
List.prototype.pop = function pop() {
|
||||
const item = this.tail;
|
||||
|
||||
if (!item)
|
||||
return null;
|
||||
|
||||
this.remove(item);
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
/**
|
||||
* Insert item into the linked list.
|
||||
* @private
|
||||
* @param {ListItem|null} ref
|
||||
* @param {ListItem} item
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
List.prototype.insert = function insert(ref, item) {
|
||||
if (item.prev || item.next || item === this.head)
|
||||
return false;
|
||||
|
||||
assert(!item.prev);
|
||||
assert(!item.next);
|
||||
|
||||
if (ref == null) {
|
||||
if (!this.head) {
|
||||
this.head = item;
|
||||
this.tail = item;
|
||||
} else {
|
||||
this.head.prev = item;
|
||||
item.next = this.head;
|
||||
this.head = item;
|
||||
for (item = this.head; item; item = next) {
|
||||
next = item.next;
|
||||
item.prev = null;
|
||||
item.next = null;
|
||||
}
|
||||
this.size++;
|
||||
|
||||
assert(!item);
|
||||
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the first item in the list.
|
||||
* @returns {ListItem}
|
||||
*/
|
||||
|
||||
shift() {
|
||||
const item = this.head;
|
||||
|
||||
if (!item)
|
||||
return null;
|
||||
|
||||
this.remove(item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend an item to the linked list (sets new head).
|
||||
* @param {ListItem}
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
unshift(item) {
|
||||
return this.insert(null, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append an item to the linked list (sets new tail).
|
||||
* @param {ListItem}
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
push(item) {
|
||||
return this.insert(this.tail, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the last item in the list.
|
||||
* @returns {ListItem}
|
||||
*/
|
||||
|
||||
pop() {
|
||||
const item = this.tail;
|
||||
|
||||
if (!item)
|
||||
return null;
|
||||
|
||||
this.remove(item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert item into the linked list.
|
||||
* @private
|
||||
* @param {ListItem|null} ref
|
||||
* @param {ListItem} item
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
insert(ref, item) {
|
||||
if (item.prev || item.next || item === this.head)
|
||||
return false;
|
||||
|
||||
assert(!item.prev);
|
||||
assert(!item.next);
|
||||
|
||||
if (ref == null) {
|
||||
if (!this.head) {
|
||||
this.head = item;
|
||||
this.tail = item;
|
||||
} else {
|
||||
this.head.prev = item;
|
||||
item.next = this.head;
|
||||
this.head = item;
|
||||
}
|
||||
this.size += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
item.next = ref.next;
|
||||
item.prev = ref;
|
||||
ref.next = item;
|
||||
|
||||
if (ref === this.tail)
|
||||
this.tail = item;
|
||||
|
||||
this.size += 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
item.next = ref.next;
|
||||
item.prev = ref;
|
||||
ref.next = item;
|
||||
/**
|
||||
* Remove item from the linked list.
|
||||
* @private
|
||||
* @param {ListItem}
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
if (ref === this.tail)
|
||||
this.tail = item;
|
||||
remove(item) {
|
||||
if (!item.prev && !item.next && item !== this.head)
|
||||
return false;
|
||||
|
||||
this.size++;
|
||||
if (item.prev)
|
||||
item.prev.next = item.next;
|
||||
|
||||
return true;
|
||||
};
|
||||
if (item.next)
|
||||
item.next.prev = item.prev;
|
||||
|
||||
/**
|
||||
* Remove item from the linked list.
|
||||
* @private
|
||||
* @param {ListItem}
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
if (item === this.head)
|
||||
this.head = item.next;
|
||||
|
||||
List.prototype.remove = function remove(item) {
|
||||
if (!item.prev && !item.next && item !== this.head)
|
||||
return false;
|
||||
if (item === this.tail)
|
||||
this.tail = item.prev || this.head;
|
||||
|
||||
if (item.prev)
|
||||
item.prev.next = item.next;
|
||||
if (!this.head)
|
||||
assert(!this.tail);
|
||||
|
||||
if (item.next)
|
||||
item.next.prev = item.prev;
|
||||
if (!this.tail)
|
||||
assert(!this.head);
|
||||
|
||||
if (item === this.head)
|
||||
this.head = item.next;
|
||||
|
||||
if (item === this.tail)
|
||||
this.tail = item.prev || this.head;
|
||||
|
||||
if (!this.head)
|
||||
assert(!this.tail);
|
||||
|
||||
if (!this.tail)
|
||||
assert(!this.head);
|
||||
|
||||
item.prev = null;
|
||||
item.next = null;
|
||||
|
||||
this.size--;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Replace an item in-place.
|
||||
* @param {ListItem} ref
|
||||
* @param {ListItem} item
|
||||
*/
|
||||
|
||||
List.prototype.replace = function replace(ref, item) {
|
||||
if (ref.prev)
|
||||
ref.prev.next = item;
|
||||
|
||||
if (ref.next)
|
||||
ref.next.prev = item;
|
||||
|
||||
item.prev = ref.prev;
|
||||
item.next = ref.next;
|
||||
|
||||
ref.next = null;
|
||||
ref.prev = null;
|
||||
|
||||
if (this.head === ref)
|
||||
this.head = item;
|
||||
|
||||
if (this.tail === ref)
|
||||
this.tail = item;
|
||||
};
|
||||
|
||||
/**
|
||||
* Slice the list to an array of items.
|
||||
* Will remove the items sliced.
|
||||
* @param {Number?} total
|
||||
* @returns {ListItem[]}
|
||||
*/
|
||||
|
||||
List.prototype.slice = function slice(total) {
|
||||
const items = [];
|
||||
let item, next;
|
||||
|
||||
if (total == null)
|
||||
total = -1;
|
||||
|
||||
for (item = this.head; item; item = next) {
|
||||
next = item.next;
|
||||
item.prev = null;
|
||||
item.next = null;
|
||||
|
||||
this.size--;
|
||||
this.size -= 1;
|
||||
|
||||
items.push(item);
|
||||
|
||||
if (items.length === total)
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (next) {
|
||||
this.head = next;
|
||||
next.prev = null;
|
||||
} else {
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
/**
|
||||
* Replace an item in-place.
|
||||
* @param {ListItem} ref
|
||||
* @param {ListItem} item
|
||||
*/
|
||||
|
||||
replace(ref, item) {
|
||||
if (ref.prev)
|
||||
ref.prev.next = item;
|
||||
|
||||
if (ref.next)
|
||||
ref.next.prev = item;
|
||||
|
||||
item.prev = ref.prev;
|
||||
item.next = ref.next;
|
||||
|
||||
ref.next = null;
|
||||
ref.prev = null;
|
||||
|
||||
if (this.head === ref)
|
||||
this.head = item;
|
||||
|
||||
if (this.tail === ref)
|
||||
this.tail = item;
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
/**
|
||||
* Slice the list to an array of items.
|
||||
* Will remove the items sliced.
|
||||
* @param {Number?} total
|
||||
* @returns {ListItem[]}
|
||||
*/
|
||||
|
||||
slice(total) {
|
||||
if (total == null)
|
||||
total = -1;
|
||||
|
||||
const items = [];
|
||||
|
||||
let next = null;
|
||||
|
||||
for (let item = this.head; item; item = next) {
|
||||
next = item.next;
|
||||
item.prev = null;
|
||||
item.next = null;
|
||||
|
||||
this.size -= 1;
|
||||
|
||||
items.push(item);
|
||||
|
||||
if (items.length === total)
|
||||
break;
|
||||
}
|
||||
|
||||
if (next) {
|
||||
this.head = next;
|
||||
next.prev = null;
|
||||
} else {
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the list to an array of items.
|
||||
* @returns {ListItem[]}
|
||||
*/
|
||||
|
||||
toArray() {
|
||||
const items = [];
|
||||
|
||||
for (let item = this.head; item; item = item.next)
|
||||
items.push(item);
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the list to an array of items.
|
||||
* @returns {ListItem[]}
|
||||
*/
|
||||
|
||||
List.prototype.toArray = function toArray() {
|
||||
const items = [];
|
||||
|
||||
for (let item = this.head; item; item = item.next)
|
||||
items.push(item);
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents an linked list item.
|
||||
* List Item
|
||||
* @alias module:utils.ListItem
|
||||
* @constructor
|
||||
* @private
|
||||
* @param {String} key
|
||||
* @param {Object} value
|
||||
*/
|
||||
|
||||
function ListItem(value) {
|
||||
this.next = null;
|
||||
this.prev = null;
|
||||
this.value = value;
|
||||
class ListItem {
|
||||
/**
|
||||
* Create a list item.
|
||||
* @constructor
|
||||
* @private
|
||||
* @param {String} key
|
||||
* @param {Object} value
|
||||
*/
|
||||
|
||||
constructor(value) {
|
||||
this.next = null;
|
||||
this.prev = null;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@ -10,194 +10,196 @@
|
||||
const assert = require('assert');
|
||||
|
||||
/**
|
||||
* Represents a mutex lock for locking asynchronous object methods.
|
||||
* Mutex Lock
|
||||
* @alias module:utils.Lock
|
||||
* @constructor
|
||||
* @param {Boolean?} named - Whether to
|
||||
* maintain a map of queued jobs by job name.
|
||||
*/
|
||||
|
||||
function Lock(named) {
|
||||
if (!(this instanceof Lock))
|
||||
return Lock.create(named);
|
||||
class Lock {
|
||||
/**
|
||||
* Create a lock.
|
||||
* @constructor
|
||||
* @param {Boolean?} named - Whether to
|
||||
* maintain a map of queued jobs by job name.
|
||||
*/
|
||||
|
||||
this.named = named === true;
|
||||
constructor(named = false) {
|
||||
this.named = named === true;
|
||||
|
||||
this.jobs = [];
|
||||
this.busy = false;
|
||||
this.destroyed = false;
|
||||
this.jobs = [];
|
||||
this.busy = false;
|
||||
this.destroyed = false;
|
||||
|
||||
this.map = new Map();
|
||||
this.current = null;
|
||||
this.map = new Map();
|
||||
this.current = null;
|
||||
|
||||
this.unlocker = this.unlock.bind(this);
|
||||
this.unlocker = this.unlock.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a closure scoped lock.
|
||||
* @param {Boolean?} named
|
||||
* @returns {Function} Lock method.
|
||||
*/
|
||||
|
||||
static create(named) {
|
||||
const lock = new Lock(named);
|
||||
return function _lock(arg1, arg2) {
|
||||
return lock.lock(arg1, arg2);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the lock has a pending
|
||||
* job or a job in progress (by name).
|
||||
* @param {String} name
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
has(name) {
|
||||
assert(this.named, 'Must use named jobs.');
|
||||
|
||||
if (this.current === name)
|
||||
return true;
|
||||
|
||||
return this.pending(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the lock has
|
||||
* a pending job by name.
|
||||
* @param {String} name
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
pending(name) {
|
||||
assert(this.named, 'Must use named jobs.');
|
||||
|
||||
const count = this.map.get(name);
|
||||
|
||||
if (count == null)
|
||||
return false;
|
||||
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock the parent object and all its methods
|
||||
* which use the lock. Begin to queue calls.
|
||||
* @param {String?} name - Job name.
|
||||
* @param {Boolean?} force - Bypass the lock.
|
||||
* @returns {Promise} - Returns {Function}, must be
|
||||
* called once the method finishes executing in order
|
||||
* to resolve the queue.
|
||||
*/
|
||||
|
||||
lock(arg1, arg2) {
|
||||
let name, force;
|
||||
|
||||
if (this.named) {
|
||||
name = arg1 || null;
|
||||
force = arg2 || false;
|
||||
} else {
|
||||
name = null;
|
||||
force = arg1 || false;
|
||||
}
|
||||
|
||||
if (this.destroyed)
|
||||
return Promise.reject(new Error('Lock is destroyed.'));
|
||||
|
||||
if (force) {
|
||||
assert(this.busy);
|
||||
return Promise.resolve(nop);
|
||||
}
|
||||
|
||||
if (this.busy) {
|
||||
if (name) {
|
||||
const count = this.map.get(name) || 0;
|
||||
this.map.set(name, count + 1);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
this.jobs.push(new Job(resolve, reject, name));
|
||||
});
|
||||
}
|
||||
|
||||
this.busy = true;
|
||||
this.current = name;
|
||||
|
||||
return Promise.resolve(this.unlocker);
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual unlock callback.
|
||||
* @private
|
||||
*/
|
||||
|
||||
unlock() {
|
||||
assert(this.destroyed || this.busy);
|
||||
|
||||
this.busy = false;
|
||||
this.current = null;
|
||||
|
||||
if (this.jobs.length === 0)
|
||||
return;
|
||||
|
||||
assert(!this.destroyed);
|
||||
|
||||
const job = this.jobs.shift();
|
||||
|
||||
if (job.name) {
|
||||
let count = this.map.get(job.name);
|
||||
assert(count > 0);
|
||||
if (--count === 0)
|
||||
this.map.delete(job.name);
|
||||
else
|
||||
this.map.set(job.name, count);
|
||||
}
|
||||
|
||||
this.busy = true;
|
||||
this.current = job.name;
|
||||
|
||||
job.resolve(this.unlocker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the lock. Purge all pending calls.
|
||||
*/
|
||||
|
||||
destroy() {
|
||||
assert(!this.destroyed, 'Lock is already destroyed.');
|
||||
|
||||
this.destroyed = true;
|
||||
|
||||
const jobs = this.jobs;
|
||||
|
||||
this.busy = false;
|
||||
this.jobs = [];
|
||||
this.map.clear();
|
||||
this.current = null;
|
||||
|
||||
for (const job of jobs)
|
||||
job.reject(new Error('Lock was destroyed.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a closure scoped lock.
|
||||
* @param {Boolean?} named
|
||||
* @returns {Function} Lock method.
|
||||
*/
|
||||
|
||||
Lock.create = function create(named) {
|
||||
const lock = new Lock(named);
|
||||
return function _lock(arg1, arg2) {
|
||||
return lock.lock(arg1, arg2);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether the lock has a pending
|
||||
* job or a job in progress (by name).
|
||||
* @param {String} name
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
Lock.prototype.has = function has(name) {
|
||||
assert(this.named, 'Must use named jobs.');
|
||||
|
||||
if (this.current === name)
|
||||
return true;
|
||||
|
||||
const count = this.map.get(name);
|
||||
|
||||
if (count == null)
|
||||
return false;
|
||||
|
||||
return count > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether the lock has
|
||||
* a pending job by name.
|
||||
* @param {String} name
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
Lock.prototype.hasPending = function hasPending(name) {
|
||||
assert(this.named, 'Must use named jobs.');
|
||||
|
||||
const count = this.map.get(name);
|
||||
|
||||
if (count == null)
|
||||
return false;
|
||||
|
||||
return count > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Lock the parent object and all its methods
|
||||
* which use the lock. Begin to queue calls.
|
||||
* @param {String?} name - Job name.
|
||||
* @param {Boolean?} force - Bypass the lock.
|
||||
* @returns {Promise} - Returns {Function}, must be
|
||||
* called once the method finishes executing in order
|
||||
* to resolve the queue.
|
||||
*/
|
||||
|
||||
Lock.prototype.lock = function lock(arg1, arg2) {
|
||||
let name, force;
|
||||
|
||||
if (this.named) {
|
||||
name = arg1 || null;
|
||||
force = arg2;
|
||||
} else {
|
||||
name = null;
|
||||
force = arg1;
|
||||
}
|
||||
|
||||
if (this.destroyed)
|
||||
return Promise.reject(new Error('Lock is destroyed.'));
|
||||
|
||||
if (force) {
|
||||
assert(this.busy);
|
||||
return Promise.resolve(nop);
|
||||
}
|
||||
|
||||
if (this.busy) {
|
||||
if (name) {
|
||||
let count = this.map.get(name);
|
||||
if (!count)
|
||||
count = 0;
|
||||
this.map.set(name, count + 1);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
this.jobs.push(new Job(resolve, reject, name));
|
||||
});
|
||||
}
|
||||
|
||||
this.busy = true;
|
||||
this.current = name;
|
||||
|
||||
return Promise.resolve(this.unlocker);
|
||||
};
|
||||
|
||||
/**
|
||||
* The actual unlock callback.
|
||||
* @private
|
||||
*/
|
||||
|
||||
Lock.prototype.unlock = function unlock() {
|
||||
assert(this.destroyed || this.busy);
|
||||
|
||||
this.busy = false;
|
||||
this.current = null;
|
||||
|
||||
if (this.jobs.length === 0)
|
||||
return;
|
||||
|
||||
assert(!this.destroyed);
|
||||
|
||||
const job = this.jobs.shift();
|
||||
|
||||
if (job.name) {
|
||||
let count = this.map.get(job.name);
|
||||
assert(count > 0);
|
||||
if (--count === 0)
|
||||
this.map.delete(job.name);
|
||||
else
|
||||
this.map.set(job.name, count);
|
||||
}
|
||||
|
||||
this.busy = true;
|
||||
this.current = job.name;
|
||||
|
||||
job.resolve(this.unlocker);
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the lock. Purge all pending calls.
|
||||
*/
|
||||
|
||||
Lock.prototype.destroy = function destroy() {
|
||||
assert(!this.destroyed, 'Lock is already destroyed.');
|
||||
|
||||
this.destroyed = true;
|
||||
|
||||
const jobs = this.jobs;
|
||||
|
||||
this.busy = false;
|
||||
this.jobs = [];
|
||||
this.map.clear();
|
||||
this.current = null;
|
||||
|
||||
for (const job of jobs)
|
||||
job.reject(new Error('Lock was destroyed.'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Lock Job
|
||||
* @constructor
|
||||
* @ignore
|
||||
* @param {Function} resolve
|
||||
* @param {Function} reject
|
||||
* @param {String?} name
|
||||
*/
|
||||
|
||||
function Job(resolve, reject, name) {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
this.name = name || null;
|
||||
class Job {
|
||||
/**
|
||||
* Create a lock job.
|
||||
* @constructor
|
||||
* @param {Function} resolve
|
||||
* @param {Function} reject
|
||||
* @param {String?} name
|
||||
*/
|
||||
|
||||
constructor(resolve, reject, name) {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
this.name = name || null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
934
lib/utils/lru.js
934
lib/utils/lru.js
@ -10,489 +10,515 @@
|
||||
const assert = require('assert');
|
||||
|
||||
/**
|
||||
* An LRU cache, used for caching {@link ChainEntry}s.
|
||||
* LRU Cache
|
||||
* @alias module:utils.LRU
|
||||
* @constructor
|
||||
* @param {Number} capacity
|
||||
* @param {Function?} getSize
|
||||
*/
|
||||
|
||||
function LRU(capacity, getSize) {
|
||||
if (!(this instanceof LRU))
|
||||
return new LRU(capacity, getSize);
|
||||
class LRU {
|
||||
/**
|
||||
* Create an LRU cache.
|
||||
* @constructor
|
||||
* @param {Number} capacity
|
||||
* @param {Function?} getSize
|
||||
*/
|
||||
|
||||
this.map = new Map();
|
||||
this.size = 0;
|
||||
this.items = 0;
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
this.pending = null;
|
||||
constructor(capacity, getSize) {
|
||||
this.map = new Map();
|
||||
this.size = 0;
|
||||
this.items = 0;
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
this.pending = null;
|
||||
|
||||
assert(typeof capacity === 'number', 'Capacity must be a number.');
|
||||
assert(capacity >= 0, 'Capacity cannot be negative.');
|
||||
assert(!getSize || typeof getSize === 'function', 'Bad size callback.');
|
||||
assert(typeof capacity === 'number', 'Capacity must be a number.');
|
||||
assert(capacity >= 0, 'Capacity cannot be negative.');
|
||||
assert(!getSize || typeof getSize === 'function', 'Bad size callback.');
|
||||
|
||||
this.capacity = capacity;
|
||||
this.getSize = getSize;
|
||||
this.capacity = capacity;
|
||||
this.getSize = getSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate size of an item.
|
||||
* @private
|
||||
* @param {LRUItem} item
|
||||
* @returns {Number} Size.
|
||||
*/
|
||||
|
||||
_getSize(item) {
|
||||
if (this.getSize) {
|
||||
const keySize = Math.floor(item.key.length * 1.375);
|
||||
return 120 + keySize + this.getSize(item.value);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact the LRU linked list.
|
||||
* @private
|
||||
*/
|
||||
|
||||
_compact() {
|
||||
if (this.size <= this.capacity)
|
||||
return;
|
||||
|
||||
let item = null;
|
||||
let next = null;
|
||||
|
||||
for (item = this.head; item; item = next) {
|
||||
if (this.size <= this.capacity)
|
||||
break;
|
||||
|
||||
this.size -= this._getSize(item);
|
||||
this.items -= 1;
|
||||
this.map.delete(item.key);
|
||||
|
||||
next = item.next;
|
||||
|
||||
item.prev = null;
|
||||
item.next = null;
|
||||
}
|
||||
|
||||
if (!item) {
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.head = item;
|
||||
item.prev = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the cache. Clear all items.
|
||||
*/
|
||||
|
||||
reset() {
|
||||
let item, next;
|
||||
|
||||
for (item = this.head; item; item = next) {
|
||||
this.map.delete(item.key);
|
||||
this.items -= 1;
|
||||
next = item.next;
|
||||
item.prev = null;
|
||||
item.next = null;
|
||||
}
|
||||
|
||||
assert(!item);
|
||||
|
||||
this.size = 0;
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item to the cache.
|
||||
* @param {String|Number} key
|
||||
* @param {Object} value
|
||||
*/
|
||||
|
||||
set(key, value) {
|
||||
if (this.capacity === 0)
|
||||
return;
|
||||
|
||||
key = String(key);
|
||||
|
||||
let item = this.map.get(key);
|
||||
|
||||
if (item) {
|
||||
this.size -= this._getSize(item);
|
||||
item.value = value;
|
||||
this.size += this._getSize(item);
|
||||
this._removeList(item);
|
||||
this._appendList(item);
|
||||
this._compact();
|
||||
return;
|
||||
}
|
||||
|
||||
item = new LRUItem(key, value);
|
||||
|
||||
this.map.set(key, item);
|
||||
|
||||
this._appendList(item);
|
||||
|
||||
this.size += this._getSize(item);
|
||||
this.items += 1;
|
||||
|
||||
this._compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an item from the cache.
|
||||
* @param {String|Number} key
|
||||
* @returns {Object} Item.
|
||||
*/
|
||||
|
||||
get(key) {
|
||||
if (this.capacity === 0)
|
||||
return null;
|
||||
|
||||
key = String(key);
|
||||
|
||||
const item = this.map.get(key);
|
||||
|
||||
if (!item)
|
||||
return null;
|
||||
|
||||
this._removeList(item);
|
||||
this._appendList(item);
|
||||
|
||||
return item.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the cache contains a key.
|
||||
* @param {String|Number} key
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
has(key) {
|
||||
if (this.capacity === 0)
|
||||
return false;
|
||||
return this.map.has(String(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from the cache.
|
||||
* @param {String|Number} key
|
||||
* @returns {Boolean} Whether an item was removed.
|
||||
*/
|
||||
|
||||
remove(key) {
|
||||
if (this.capacity === 0)
|
||||
return false;
|
||||
|
||||
key = String(key);
|
||||
|
||||
const item = this.map.get(key);
|
||||
|
||||
if (!item)
|
||||
return false;
|
||||
|
||||
this.size -= this._getSize(item);
|
||||
this.items -= 1;
|
||||
|
||||
this.map.delete(key);
|
||||
|
||||
this._removeList(item);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend an item to the linked list (sets new head).
|
||||
* @private
|
||||
* @param {LRUItem}
|
||||
*/
|
||||
|
||||
_prependList(item) {
|
||||
this._insertList(null, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append an item to the linked list (sets new tail).
|
||||
* @private
|
||||
* @param {LRUItem}
|
||||
*/
|
||||
|
||||
_appendList(item) {
|
||||
this._insertList(this.tail, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert item into the linked list.
|
||||
* @private
|
||||
* @param {LRUItem|null} ref
|
||||
* @param {LRUItem} item
|
||||
*/
|
||||
|
||||
_insertList(ref, item) {
|
||||
assert(!item.next);
|
||||
assert(!item.prev);
|
||||
|
||||
if (ref == null) {
|
||||
if (!this.head) {
|
||||
this.head = item;
|
||||
this.tail = item;
|
||||
} else {
|
||||
this.head.prev = item;
|
||||
item.next = this.head;
|
||||
this.head = item;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
item.next = ref.next;
|
||||
item.prev = ref;
|
||||
ref.next = item;
|
||||
|
||||
if (ref === this.tail)
|
||||
this.tail = item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove item from the linked list.
|
||||
* @private
|
||||
* @param {LRUItem}
|
||||
*/
|
||||
|
||||
_removeList(item) {
|
||||
if (item.prev)
|
||||
item.prev.next = item.next;
|
||||
|
||||
if (item.next)
|
||||
item.next.prev = item.prev;
|
||||
|
||||
if (item === this.head)
|
||||
this.head = item.next;
|
||||
|
||||
if (item === this.tail)
|
||||
this.tail = item.prev || this.head;
|
||||
|
||||
if (!this.head)
|
||||
assert(!this.tail);
|
||||
|
||||
if (!this.tail)
|
||||
assert(!this.head);
|
||||
|
||||
item.prev = null;
|
||||
item.next = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all keys in the cache, sorted by LRU.
|
||||
* @returns {String[]}
|
||||
*/
|
||||
|
||||
keys() {
|
||||
const items = [];
|
||||
|
||||
for (let item = this.head; item; item = item.next) {
|
||||
if (item === this.head)
|
||||
assert(!item.prev);
|
||||
if (!item.prev)
|
||||
assert(item === this.head);
|
||||
if (!item.next)
|
||||
assert(item === this.tail);
|
||||
items.push(item.key);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all values in the cache, sorted by LRU.
|
||||
* @returns {String[]}
|
||||
*/
|
||||
|
||||
values() {
|
||||
const items = [];
|
||||
|
||||
for (let item = this.head; item; item = item.next)
|
||||
items.push(item.value);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the LRU cache to an array of items.
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
|
||||
toArray() {
|
||||
const items = [];
|
||||
|
||||
for (let item = this.head; item; item = item.next)
|
||||
items.push(item);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an atomic batch for the lru
|
||||
* (used for caching database writes).
|
||||
* @returns {LRUBatch}
|
||||
*/
|
||||
|
||||
batch() {
|
||||
return new LRUBatch(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the pending batch.
|
||||
*/
|
||||
|
||||
start() {
|
||||
assert(!this.pending);
|
||||
this.pending = this.batch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the pending batch.
|
||||
*/
|
||||
|
||||
clear() {
|
||||
assert(this.pending);
|
||||
this.pending.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop the pending batch.
|
||||
*/
|
||||
|
||||
drop() {
|
||||
assert(this.pending);
|
||||
this.pending = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit the pending batch.
|
||||
*/
|
||||
|
||||
commit() {
|
||||
assert(this.pending);
|
||||
this.pending.commit();
|
||||
this.pending = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push an item onto the pending batch.
|
||||
* @param {String} key
|
||||
* @param {Object} value
|
||||
*/
|
||||
|
||||
push(key, value) {
|
||||
assert(this.pending);
|
||||
|
||||
if (this.capacity === 0)
|
||||
return;
|
||||
|
||||
this.pending.set(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a removal onto the pending batch.
|
||||
* @param {String} key
|
||||
*/
|
||||
|
||||
unpush(key) {
|
||||
assert(this.pending);
|
||||
|
||||
if (this.capacity === 0)
|
||||
return;
|
||||
|
||||
this.pending.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate size of an item.
|
||||
* @private
|
||||
* @param {LRUItem} item
|
||||
* @returns {Number} Size.
|
||||
*/
|
||||
|
||||
LRU.prototype._getSize = function _getSize(item) {
|
||||
if (this.getSize) {
|
||||
const keySize = Math.floor(item.key.length * 1.375);
|
||||
return 120 + keySize + this.getSize(item.value);
|
||||
}
|
||||
|
||||
return 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Compact the LRU linked list.
|
||||
* @private
|
||||
*/
|
||||
|
||||
LRU.prototype._compact = function _compact() {
|
||||
if (this.size <= this.capacity)
|
||||
return;
|
||||
|
||||
let item, next;
|
||||
for (item = this.head; item; item = next) {
|
||||
if (this.size <= this.capacity)
|
||||
break;
|
||||
this.size -= this._getSize(item);
|
||||
this.items--;
|
||||
this.map.delete(item.key);
|
||||
next = item.next;
|
||||
item.prev = null;
|
||||
item.next = null;
|
||||
}
|
||||
|
||||
if (!item) {
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.head = item;
|
||||
item.prev = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset the cache. Clear all items.
|
||||
*/
|
||||
|
||||
LRU.prototype.reset = function reset() {
|
||||
let item, next;
|
||||
|
||||
for (item = this.head; item; item = next) {
|
||||
this.map.delete(item.key);
|
||||
this.items--;
|
||||
next = item.next;
|
||||
item.prev = null;
|
||||
item.next = null;
|
||||
}
|
||||
|
||||
assert(!item);
|
||||
|
||||
this.size = 0;
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an item to the cache.
|
||||
* @param {String|Number} key
|
||||
* @param {Object} value
|
||||
*/
|
||||
|
||||
LRU.prototype.set = function set(key, value) {
|
||||
if (this.capacity === 0)
|
||||
return;
|
||||
|
||||
key = String(key);
|
||||
|
||||
let item = this.map.get(key);
|
||||
|
||||
if (item) {
|
||||
this.size -= this._getSize(item);
|
||||
item.value = value;
|
||||
this.size += this._getSize(item);
|
||||
this._removeList(item);
|
||||
this._appendList(item);
|
||||
this._compact();
|
||||
return;
|
||||
}
|
||||
|
||||
item = new LRUItem(key, value);
|
||||
|
||||
this.map.set(key, item);
|
||||
|
||||
this._appendList(item);
|
||||
|
||||
this.size += this._getSize(item);
|
||||
this.items++;
|
||||
|
||||
this._compact();
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve an item from the cache.
|
||||
* @param {String|Number} key
|
||||
* @returns {Object} Item.
|
||||
*/
|
||||
|
||||
LRU.prototype.get = function get(key) {
|
||||
if (this.capacity === 0)
|
||||
return null;
|
||||
|
||||
key = String(key);
|
||||
|
||||
const item = this.map.get(key);
|
||||
|
||||
if (!item)
|
||||
return null;
|
||||
|
||||
this._removeList(item);
|
||||
this._appendList(item);
|
||||
|
||||
return item.value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether the cache contains a key.
|
||||
* @param {String|Number} key
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
LRU.prototype.has = function has(key) {
|
||||
if (this.capacity === 0)
|
||||
return false;
|
||||
return this.map.has(String(key));
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove an item from the cache.
|
||||
* @param {String|Number} key
|
||||
* @returns {Boolean} Whether an item was removed.
|
||||
*/
|
||||
|
||||
LRU.prototype.remove = function remove(key) {
|
||||
if (this.capacity === 0)
|
||||
return false;
|
||||
|
||||
key = String(key);
|
||||
|
||||
const item = this.map.get(key);
|
||||
|
||||
if (!item)
|
||||
return false;
|
||||
|
||||
this.size -= this._getSize(item);
|
||||
this.items--;
|
||||
|
||||
this.map.delete(key);
|
||||
|
||||
this._removeList(item);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepend an item to the linked list (sets new head).
|
||||
* @private
|
||||
* @param {LRUItem}
|
||||
*/
|
||||
|
||||
LRU.prototype._prependList = function _prependList(item) {
|
||||
this._insertList(null, item);
|
||||
};
|
||||
|
||||
/**
|
||||
* Append an item to the linked list (sets new tail).
|
||||
* @private
|
||||
* @param {LRUItem}
|
||||
*/
|
||||
|
||||
LRU.prototype._appendList = function _appendList(item) {
|
||||
this._insertList(this.tail, item);
|
||||
};
|
||||
|
||||
/**
|
||||
* Insert item into the linked list.
|
||||
* @private
|
||||
* @param {LRUItem|null} ref
|
||||
* @param {LRUItem} item
|
||||
*/
|
||||
|
||||
LRU.prototype._insertList = function _insertList(ref, item) {
|
||||
assert(!item.next);
|
||||
assert(!item.prev);
|
||||
|
||||
if (ref == null) {
|
||||
if (!this.head) {
|
||||
this.head = item;
|
||||
this.tail = item;
|
||||
} else {
|
||||
this.head.prev = item;
|
||||
item.next = this.head;
|
||||
this.head = item;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
item.next = ref.next;
|
||||
item.prev = ref;
|
||||
ref.next = item;
|
||||
|
||||
if (ref === this.tail)
|
||||
this.tail = item;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove item from the linked list.
|
||||
* @private
|
||||
* @param {LRUItem}
|
||||
*/
|
||||
|
||||
LRU.prototype._removeList = function _removeList(item) {
|
||||
if (item.prev)
|
||||
item.prev.next = item.next;
|
||||
|
||||
if (item.next)
|
||||
item.next.prev = item.prev;
|
||||
|
||||
if (item === this.head)
|
||||
this.head = item.next;
|
||||
|
||||
if (item === this.tail)
|
||||
this.tail = item.prev || this.head;
|
||||
|
||||
if (!this.head)
|
||||
assert(!this.tail);
|
||||
|
||||
if (!this.tail)
|
||||
assert(!this.head);
|
||||
|
||||
item.prev = null;
|
||||
item.next = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Collect all keys in the cache, sorted by LRU.
|
||||
* @returns {String[]}
|
||||
*/
|
||||
|
||||
LRU.prototype.keys = function keys() {
|
||||
const items = [];
|
||||
|
||||
for (let item = this.head; item; item = item.next) {
|
||||
if (item === this.head)
|
||||
assert(!item.prev);
|
||||
if (!item.prev)
|
||||
assert(item === this.head);
|
||||
if (!item.next)
|
||||
assert(item === this.tail);
|
||||
items.push(item.key);
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
/**
|
||||
* Collect all values in the cache, sorted by LRU.
|
||||
* @returns {String[]}
|
||||
*/
|
||||
|
||||
LRU.prototype.values = function values() {
|
||||
const items = [];
|
||||
|
||||
for (let item = this.head; item; item = item.next)
|
||||
items.push(item.value);
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert the LRU cache to an array of items.
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
|
||||
LRU.prototype.toArray = function toArray() {
|
||||
const items = [];
|
||||
|
||||
for (let item = this.head; item; item = item.next)
|
||||
items.push(item);
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an atomic batch for the lru
|
||||
* (used for caching database writes).
|
||||
* @returns {LRUBatch}
|
||||
*/
|
||||
|
||||
LRU.prototype.batch = function batch() {
|
||||
return new LRUBatch(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Start the pending batch.
|
||||
*/
|
||||
|
||||
LRU.prototype.start = function start() {
|
||||
assert(!this.pending);
|
||||
this.pending = this.batch();
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the pending batch.
|
||||
*/
|
||||
|
||||
LRU.prototype.clear = function clear() {
|
||||
assert(this.pending);
|
||||
this.pending.clear();
|
||||
};
|
||||
|
||||
/**
|
||||
* Drop the pending batch.
|
||||
*/
|
||||
|
||||
LRU.prototype.drop = function drop() {
|
||||
assert(this.pending);
|
||||
this.pending = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Commit the pending batch.
|
||||
*/
|
||||
|
||||
LRU.prototype.commit = function commit() {
|
||||
assert(this.pending);
|
||||
this.pending.commit();
|
||||
this.pending = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Push an item onto the pending batch.
|
||||
* @param {String} key
|
||||
* @param {Object} value
|
||||
*/
|
||||
|
||||
LRU.prototype.push = function push(key, value) {
|
||||
assert(this.pending);
|
||||
|
||||
if (this.capacity === 0)
|
||||
return;
|
||||
|
||||
this.pending.set(key, value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Push a removal onto the pending batch.
|
||||
* @param {String} key
|
||||
*/
|
||||
|
||||
LRU.prototype.unpush = function unpush(key) {
|
||||
assert(this.pending);
|
||||
|
||||
if (this.capacity === 0)
|
||||
return;
|
||||
|
||||
this.pending.remove(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents an LRU item.
|
||||
* LRU Item
|
||||
* @alias module:utils.LRUItem
|
||||
* @constructor
|
||||
* @private
|
||||
* @param {String} key
|
||||
* @param {Object} value
|
||||
*/
|
||||
|
||||
function LRUItem(key, value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.next = null;
|
||||
this.prev = null;
|
||||
class LRUItem {
|
||||
/**
|
||||
* Create an LRU item.
|
||||
* @constructor
|
||||
* @private
|
||||
* @param {String} key
|
||||
* @param {Object} value
|
||||
*/
|
||||
|
||||
constructor(key, value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.next = null;
|
||||
this.prev = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LRU Batch
|
||||
* @alias module:utils.LRUBatch
|
||||
* @constructor
|
||||
* @param {LRU} lru
|
||||
*/
|
||||
|
||||
function LRUBatch(lru) {
|
||||
this.lru = lru;
|
||||
this.ops = [];
|
||||
}
|
||||
class LRUBatch {
|
||||
/**
|
||||
* Create an LRU batch.
|
||||
* @constructor
|
||||
* @param {LRU} lru
|
||||
*/
|
||||
|
||||
/**
|
||||
* Push an item onto the batch.
|
||||
* @param {String} key
|
||||
* @param {Object} value
|
||||
*/
|
||||
|
||||
LRUBatch.prototype.set = function set(key, value) {
|
||||
this.ops.push(new LRUOp(false, key, value));
|
||||
};
|
||||
|
||||
/**
|
||||
* Push a removal onto the batch.
|
||||
* @param {String} key
|
||||
*/
|
||||
|
||||
LRUBatch.prototype.remove = function remove(key) {
|
||||
this.ops.push(new LRUOp(true, key, null));
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the batch.
|
||||
*/
|
||||
|
||||
LRUBatch.prototype.clear = function clear() {
|
||||
this.ops.length = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Commit the batch.
|
||||
*/
|
||||
|
||||
LRUBatch.prototype.commit = function commit() {
|
||||
for (const op of this.ops) {
|
||||
if (op.remove) {
|
||||
this.lru.remove(op.key);
|
||||
continue;
|
||||
}
|
||||
this.lru.set(op.key, op.value);
|
||||
constructor(lru) {
|
||||
this.lru = lru;
|
||||
this.ops = [];
|
||||
}
|
||||
|
||||
this.ops.length = 0;
|
||||
};
|
||||
/**
|
||||
* Push an item onto the batch.
|
||||
* @param {String} key
|
||||
* @param {Object} value
|
||||
*/
|
||||
|
||||
set(key, value) {
|
||||
this.ops.push(new LRUOp(false, key, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a removal onto the batch.
|
||||
* @param {String} key
|
||||
*/
|
||||
|
||||
remove(key) {
|
||||
this.ops.push(new LRUOp(true, key, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the batch.
|
||||
*/
|
||||
|
||||
clear() {
|
||||
this.ops.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit the batch.
|
||||
*/
|
||||
|
||||
commit() {
|
||||
for (const op of this.ops) {
|
||||
if (op.remove) {
|
||||
this.lru.remove(op.key);
|
||||
continue;
|
||||
}
|
||||
this.lru.set(op.key, op.value);
|
||||
}
|
||||
|
||||
this.ops.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LRU Op
|
||||
* @alias module:utils.LRUOp
|
||||
* @constructor
|
||||
* @private
|
||||
* @param {Boolean} remove
|
||||
* @param {String} key
|
||||
* @param {Object} value
|
||||
*/
|
||||
|
||||
function LRUOp(remove, key, value) {
|
||||
this.remove = remove;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
class LRUOp {
|
||||
/**
|
||||
* Create an LRU op.
|
||||
* @constructor
|
||||
* @param {Boolean} remove
|
||||
* @param {String} key
|
||||
* @param {Object} value
|
||||
*/
|
||||
|
||||
constructor(remove, key, value) {
|
||||
this.remove = remove;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@ -10,154 +10,162 @@
|
||||
const assert = require('assert');
|
||||
|
||||
/**
|
||||
* Represents a mutex lock for locking asynchronous object methods.
|
||||
* Locks methods according to passed-in key.
|
||||
* Mapped Lock
|
||||
* @alias module:utils.MappedLock
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
function MappedLock() {
|
||||
if (!(this instanceof MappedLock))
|
||||
return MappedLock.create();
|
||||
class MappedLock {
|
||||
/**
|
||||
* Create a mapped lock.
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
this.jobs = new Map();
|
||||
this.busy = new Set();
|
||||
this.destroyed = false;
|
||||
constructor() {
|
||||
this.jobs = new Map();
|
||||
this.busy = new Set();
|
||||
this.destroyed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a closure scoped lock.
|
||||
* @returns {Function} Lock method.
|
||||
*/
|
||||
|
||||
static create() {
|
||||
const lock = new MappedLock();
|
||||
return function _lock(key, force) {
|
||||
return lock.lock(key, force);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the lock has a pending
|
||||
* job or a job in progress (by name).
|
||||
* @param {String} name
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
has(name) {
|
||||
return this.busy.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the lock has
|
||||
* a pending job by name.
|
||||
* @param {String} name
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
pending(name) {
|
||||
return this.jobs.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock the parent object and all its methods
|
||||
* which use the lock with a specified key.
|
||||
* Begin to queue calls.
|
||||
* @param {String|Number} key
|
||||
* @param {Boolean} [force=false] - Force a call.
|
||||
* @returns {Promise} - Returns {Function}, must be
|
||||
* called once the method finishes executing in order
|
||||
* to resolve the queue.
|
||||
*/
|
||||
|
||||
lock(key, force = false) {
|
||||
if (this.destroyed)
|
||||
return Promise.reject(new Error('Lock is destroyed.'));
|
||||
|
||||
if (key == null)
|
||||
return Promise.resolve(nop);
|
||||
|
||||
if (force) {
|
||||
assert(this.busy.has(key));
|
||||
return Promise.resolve(nop);
|
||||
}
|
||||
|
||||
if (this.busy.has(key)) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.jobs.has(key))
|
||||
this.jobs.set(key, []);
|
||||
this.jobs.get(key).push(new Job(resolve, reject));
|
||||
});
|
||||
}
|
||||
|
||||
this.busy.add(key);
|
||||
|
||||
return Promise.resolve(this.unlock(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an unlock callback.
|
||||
* @private
|
||||
* @param {String} key
|
||||
* @returns {Function} Unlocker.
|
||||
*/
|
||||
|
||||
unlock(key) {
|
||||
const self = this;
|
||||
return function unlocker() {
|
||||
const jobs = self.jobs.get(key);
|
||||
|
||||
assert(self.destroyed || self.busy.has(key));
|
||||
self.busy.delete(key);
|
||||
|
||||
if (!jobs)
|
||||
return;
|
||||
|
||||
assert(!self.destroyed);
|
||||
|
||||
const job = jobs.shift();
|
||||
assert(job);
|
||||
|
||||
if (jobs.length === 0)
|
||||
self.jobs.delete(key);
|
||||
|
||||
self.busy.add(key);
|
||||
|
||||
job.resolve(unlocker);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the lock. Purge all pending calls.
|
||||
*/
|
||||
|
||||
destroy() {
|
||||
assert(!this.destroyed, 'Lock is already destroyed.');
|
||||
|
||||
const map = this.jobs;
|
||||
|
||||
this.destroyed = true;
|
||||
|
||||
this.jobs = new Map();
|
||||
this.busy = new Map();
|
||||
|
||||
for (const jobs of map.values()) {
|
||||
for (const job of jobs)
|
||||
job.reject(new Error('Lock was destroyed.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a closure scoped lock.
|
||||
* @returns {Function} Lock method.
|
||||
*/
|
||||
|
||||
MappedLock.create = function create() {
|
||||
const lock = new MappedLock();
|
||||
return function _lock(key, force) {
|
||||
return lock.lock(key, force);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether the lock has a pending
|
||||
* job or a job in progress (by name).
|
||||
* @param {String} name
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
MappedLock.prototype.has = function has(name) {
|
||||
return this.busy.has(name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether the lock has
|
||||
* a pending job by name.
|
||||
* @param {String} name
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
MappedLock.prototype.hasPending = function hasPending(name) {
|
||||
return this.jobs.has(name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Lock the parent object and all its methods
|
||||
* which use the lock with a specified key.
|
||||
* Begin to queue calls.
|
||||
* @param {String|Number} key
|
||||
* @param {Boolean?} force - Force a call.
|
||||
* @returns {Promise} - Returns {Function}, must be
|
||||
* called once the method finishes executing in order
|
||||
* to resolve the queue.
|
||||
*/
|
||||
|
||||
MappedLock.prototype.lock = function lock(key, force) {
|
||||
if (this.destroyed)
|
||||
return Promise.reject(new Error('Lock is destroyed.'));
|
||||
|
||||
if (key == null)
|
||||
return Promise.resolve(nop);
|
||||
|
||||
if (force) {
|
||||
assert(this.busy.has(key));
|
||||
return Promise.resolve(nop);
|
||||
}
|
||||
|
||||
if (this.busy.has(key)) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.jobs.has(key))
|
||||
this.jobs.set(key, []);
|
||||
this.jobs.get(key).push(new Job(resolve, reject));
|
||||
});
|
||||
}
|
||||
|
||||
this.busy.add(key);
|
||||
|
||||
return Promise.resolve(this.unlock(key));
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an unlock callback.
|
||||
* @private
|
||||
* @param {String} key
|
||||
* @returns {Function} Unlocker.
|
||||
*/
|
||||
|
||||
MappedLock.prototype.unlock = function unlock(key) {
|
||||
const self = this;
|
||||
return function unlocker() {
|
||||
const jobs = self.jobs.get(key);
|
||||
|
||||
assert(self.destroyed || self.busy.has(key));
|
||||
self.busy.delete(key);
|
||||
|
||||
if (!jobs)
|
||||
return;
|
||||
|
||||
assert(!self.destroyed);
|
||||
|
||||
const job = jobs.shift();
|
||||
assert(job);
|
||||
|
||||
if (jobs.length === 0)
|
||||
self.jobs.delete(key);
|
||||
|
||||
self.busy.add(key);
|
||||
|
||||
job.resolve(unlocker);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the lock. Purge all pending calls.
|
||||
*/
|
||||
|
||||
MappedLock.prototype.destroy = function destroy() {
|
||||
assert(!this.destroyed, 'Lock is already destroyed.');
|
||||
|
||||
const map = this.jobs;
|
||||
|
||||
this.destroyed = true;
|
||||
|
||||
this.jobs = new Map();
|
||||
this.busy = new Map();
|
||||
|
||||
for (const jobs of map.values()) {
|
||||
for (const job of jobs)
|
||||
job.reject(new Error('Lock was destroyed.'));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Lock Job
|
||||
* @constructor
|
||||
* @ignore
|
||||
* @param {Function} resolve
|
||||
* @param {Function} reject
|
||||
*/
|
||||
|
||||
function Job(resolve, reject) {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
class Job {
|
||||
/**
|
||||
* Create a lock job.
|
||||
* @constructor
|
||||
* @param {Function} resolve
|
||||
* @param {Function} reject
|
||||
*/
|
||||
|
||||
constructor(resolve, reject) {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@ -10,210 +10,209 @@ const assert = require('assert');
|
||||
const AsyncEmitter = require('../utils/asyncemitter');
|
||||
|
||||
/**
|
||||
* NodeClient
|
||||
* Sort of a fake local client for separation of concerns.
|
||||
* Node Client
|
||||
* @alias module:node.NodeClient
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
function NodeClient(node) {
|
||||
if (!(this instanceof NodeClient))
|
||||
return new NodeClient(node);
|
||||
class NodeClient extends AsyncEmitter {
|
||||
/**
|
||||
* Create a node client.
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
AsyncEmitter.call(this);
|
||||
constructor(node) {
|
||||
super();
|
||||
|
||||
this.node = node;
|
||||
this.network = node.network;
|
||||
this.filter = null;
|
||||
this.opened = false;
|
||||
this.node = node;
|
||||
this.network = node.network;
|
||||
this.filter = null;
|
||||
this.opened = false;
|
||||
|
||||
this.init();
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the client.
|
||||
*/
|
||||
|
||||
init() {
|
||||
this.node.on('connect', (entry, block) => {
|
||||
if (!this.opened)
|
||||
return;
|
||||
|
||||
this.emit('block connect', entry, block.txs);
|
||||
});
|
||||
|
||||
this.node.on('disconnect', (entry, block) => {
|
||||
if (!this.opened)
|
||||
return;
|
||||
|
||||
this.emit('block disconnect', entry);
|
||||
});
|
||||
|
||||
this.node.on('tx', (tx) => {
|
||||
if (!this.opened)
|
||||
return;
|
||||
|
||||
this.emit('tx', tx);
|
||||
});
|
||||
|
||||
this.node.on('reset', (tip) => {
|
||||
if (!this.opened)
|
||||
return;
|
||||
|
||||
this.emit('chain reset', tip);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the client.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async open(options) {
|
||||
assert(!this.opened, 'NodeClient is already open.');
|
||||
this.opened = true;
|
||||
setImmediate(() => this.emit('connect'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the client.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async close() {
|
||||
assert(this.opened, 'NodeClient is not open.');
|
||||
this.opened = false;
|
||||
setImmediate(() => this.emit('disconnect'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener.
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
*/
|
||||
|
||||
bind(type, handler) {
|
||||
return this.on(type, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener.
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
*/
|
||||
|
||||
hook(type, handler) {
|
||||
return this.on(type, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get chain tip.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async getTip() {
|
||||
return this.node.chain.tip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get chain entry.
|
||||
* @param {Hash} hash
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async getEntry(hash) {
|
||||
const entry = await this.node.chain.getEntry(hash);
|
||||
|
||||
if (!entry)
|
||||
return null;
|
||||
|
||||
if (!await this.node.chain.isMainChain(entry))
|
||||
return null;
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a transaction. Do not wait for promise.
|
||||
* @param {TX} tx
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async send(tx) {
|
||||
this.node.relay(tx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set bloom filter.
|
||||
* @param {Bloom} filter
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async setFilter(filter) {
|
||||
this.filter = filter;
|
||||
this.node.pool.setFilter(filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add data to filter.
|
||||
* @param {Buffer} data
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async addFilter(data) {
|
||||
this.node.pool.queueFilterLoad();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset filter.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async resetFilter() {
|
||||
this.node.pool.queueFilterLoad();
|
||||
}
|
||||
|
||||
/**
|
||||
* Esimate smart fee.
|
||||
* @param {Number?} blocks
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async estimateFee(blocks) {
|
||||
if (!this.node.fees)
|
||||
return this.network.feeRate;
|
||||
|
||||
return this.node.fees.estimateFee(blocks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hash range.
|
||||
* @param {Number} start
|
||||
* @param {Number} end
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async getHashes(start = -1, end = -1) {
|
||||
return this.node.chain.getHashes(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rescan for any missed transactions.
|
||||
* @param {Number|Hash} start - Start block.
|
||||
* @param {Bloom} filter
|
||||
* @param {Function} iter - Iterator.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async rescan(start) {
|
||||
return this.node.chain.scan(start, this.filter, (entry, txs) => {
|
||||
return this.emitAsync('block rescan', entry, txs);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Object.setPrototypeOf(NodeClient.prototype, AsyncEmitter.prototype);
|
||||
|
||||
/**
|
||||
* Initialize the client.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype.init = function init() {
|
||||
this.node.on('connect', (entry, block) => {
|
||||
if (!this.opened)
|
||||
return;
|
||||
|
||||
this.emit('block connect', entry, block.txs);
|
||||
});
|
||||
|
||||
this.node.on('disconnect', (entry, block) => {
|
||||
if (!this.opened)
|
||||
return;
|
||||
|
||||
this.emit('block disconnect', entry);
|
||||
});
|
||||
|
||||
this.node.on('tx', (tx) => {
|
||||
if (!this.opened)
|
||||
return;
|
||||
|
||||
this.emit('tx', tx);
|
||||
});
|
||||
|
||||
this.node.on('reset', (tip) => {
|
||||
if (!this.opened)
|
||||
return;
|
||||
|
||||
this.emit('chain reset', tip);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Open the client.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype.open = async function open(options) {
|
||||
assert(!this.opened, 'NodeClient is already open.');
|
||||
this.opened = true;
|
||||
setImmediate(() => this.emit('connect'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Close the client.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype.close = async function close() {
|
||||
assert(this.opened, 'NodeClient is not open.');
|
||||
this.opened = false;
|
||||
setImmediate(() => this.emit('disconnect'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a listener.
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
*/
|
||||
|
||||
NodeClient.prototype.bind = function bind(type, handler) {
|
||||
return this.on(type, handler);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a listener.
|
||||
* @param {String} type
|
||||
* @param {Function} handler
|
||||
*/
|
||||
|
||||
NodeClient.prototype.hook = function hook(type, handler) {
|
||||
return this.on(type, handler);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get chain tip.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype.getTip = async function getTip() {
|
||||
return this.node.chain.tip;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get chain entry.
|
||||
* @param {Hash} hash
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype.getEntry = async function getEntry(hash) {
|
||||
const entry = await this.node.chain.getEntry(hash);
|
||||
|
||||
if (!entry)
|
||||
return null;
|
||||
|
||||
if (!await this.node.chain.isMainChain(entry))
|
||||
return null;
|
||||
|
||||
return entry;
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a transaction. Do not wait for promise.
|
||||
* @param {TX} tx
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype.send = async function send(tx) {
|
||||
this.node.relay(tx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set bloom filter.
|
||||
* @param {Bloom} filter
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype.setFilter = async function setFilter(filter) {
|
||||
this.filter = filter;
|
||||
this.node.pool.setFilter(filter);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add data to filter.
|
||||
* @param {Buffer} data
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype.addFilter = async function addFilter(data) {
|
||||
this.node.pool.queueFilterLoad();
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset filter.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype.resetFilter = async function resetFilter() {
|
||||
this.node.pool.queueFilterLoad();
|
||||
};
|
||||
|
||||
/**
|
||||
* Esimate smart fee.
|
||||
* @param {Number?} blocks
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype.estimateFee = async function estimateFee(blocks) {
|
||||
if (!this.node.fees)
|
||||
return this.network.feeRate;
|
||||
|
||||
return this.node.fees.estimateFee(blocks);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get hash range.
|
||||
* @param {Number} start
|
||||
* @param {Number} end
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype.getHashes = async function getHashes(start = -1, end = -1) {
|
||||
return this.node.chain.getHashes(start, end);
|
||||
};
|
||||
|
||||
/**
|
||||
* Rescan for any missed transactions.
|
||||
* @param {Number|Hash} start - Start block.
|
||||
* @param {Bloom} filter
|
||||
* @param {Function} iter - Iterator.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype.rescan = async function rescan(start) {
|
||||
return this.node.chain.scan(start, this.filter, (entry, txs) => {
|
||||
return this.emitAsync('block rescan', entry, txs);
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Expose
|
||||
*/
|
||||
|
||||
Loading…
Reference in New Issue
Block a user