127 lines
3.5 KiB
JavaScript
127 lines
3.5 KiB
JavaScript
'use strict';
|
|
|
|
const Boom = require('@hapi/boom');
|
|
const Hoek = require('@hapi/hoek');
|
|
|
|
const Config = require('./config');
|
|
|
|
|
|
const internals = {
|
|
methodNameRx: /^[_$a-zA-Z][$\w]*(?:\.[_$a-zA-Z][$\w]*)*$/
|
|
};
|
|
|
|
|
|
exports = module.exports = internals.Methods = class {
|
|
|
|
methods = {};
|
|
|
|
#core = null;
|
|
|
|
constructor(core) {
|
|
|
|
this.#core = core;
|
|
}
|
|
|
|
add(name, method, options, realm) {
|
|
|
|
if (typeof name !== 'object') {
|
|
return this._add(name, method, options, realm);
|
|
}
|
|
|
|
// {} or [{}, {}]
|
|
|
|
const items = [].concat(name);
|
|
for (let item of items) {
|
|
item = Config.apply('methodObject', item);
|
|
this._add(item.name, item.method, item.options ?? {}, realm);
|
|
}
|
|
}
|
|
|
|
_add(name, method, options, realm) {
|
|
|
|
Hoek.assert(typeof method === 'function', 'method must be a function');
|
|
Hoek.assert(typeof name === 'string', 'name must be a string');
|
|
Hoek.assert(name.match(internals.methodNameRx), 'Invalid name:', name);
|
|
Hoek.assert(!Hoek.reach(this.methods, name, { functions: false }), 'Server method function name already exists:', name);
|
|
|
|
options = Config.apply('method', options, name);
|
|
|
|
const settings = Hoek.clone(options, { shallow: ['bind'] });
|
|
settings.generateKey = settings.generateKey ?? internals.generateKey;
|
|
|
|
const bind = settings.bind ?? realm.settings.bind ?? null;
|
|
const bound = !bind ? method : (...args) => method.apply(bind, args);
|
|
|
|
// Not cached
|
|
|
|
if (!settings.cache) {
|
|
return this._assign(name, bound);
|
|
}
|
|
|
|
// Cached
|
|
|
|
Hoek.assert(!settings.cache.generateFunc, 'Cannot set generateFunc with method caching:', name);
|
|
Hoek.assert(settings.cache.generateTimeout !== undefined, 'Method caching requires a timeout value in generateTimeout:', name);
|
|
|
|
settings.cache.generateFunc = (id, flags) => bound(...id.args, flags);
|
|
const cache = this.#core._cachePolicy(settings.cache, '#' + name);
|
|
|
|
const func = function (...args) {
|
|
|
|
const key = settings.generateKey.apply(bind, args);
|
|
if (typeof key !== 'string') {
|
|
return Promise.reject(Boom.badImplementation('Invalid method key when invoking: ' + name, { name, args }));
|
|
}
|
|
|
|
return cache.get({ id: key, args });
|
|
};
|
|
|
|
func.cache = {
|
|
drop: function (...args) {
|
|
|
|
const key = settings.generateKey.apply(bind, args);
|
|
if (typeof key !== 'string') {
|
|
return Promise.reject(Boom.badImplementation('Invalid method key when invoking: ' + name, { name, args }));
|
|
}
|
|
|
|
return cache.drop(key);
|
|
},
|
|
stats: cache.stats
|
|
};
|
|
|
|
this._assign(name, func, func);
|
|
}
|
|
|
|
_assign(name, method) {
|
|
|
|
const path = name.split('.');
|
|
let ref = this.methods;
|
|
for (let i = 0; i < path.length; ++i) {
|
|
if (!ref[path[i]]) {
|
|
ref[path[i]] = (i + 1 === path.length ? method : {});
|
|
}
|
|
|
|
ref = ref[path[i]];
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
internals.supportedArgs = ['string', 'number', 'boolean'];
|
|
|
|
|
|
internals.generateKey = function (...args) {
|
|
|
|
let key = '';
|
|
for (let i = 0; i < args.length; ++i) {
|
|
const arg = args[i];
|
|
if (!internals.supportedArgs.includes(typeof arg)) {
|
|
return null;
|
|
}
|
|
|
|
key = key + (i ? ':' : '') + encodeURIComponent(arg.toString());
|
|
}
|
|
|
|
return key;
|
|
};
|