UNPKG

@hapi/hapi

Version:

HTTP Server framework

127 lines (85 loc) 3.58 kB
'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; };