Toys is intended for use with hapi v19+ and nodejs v12+ (see v2 for lower support).
Toys is a collection of utilities made to reduce common boilerplate in hapi v19+ projects, aid usage of events and streams in async functions (e.g. handlers and server methods), and provide versions of widely-used utilities from Hoek optimized to perform well in hot code paths such as route handlers.
constHapi=require('@hapi/hapi');constBoom=require('@hapi/boom');constToys=require('@hapipal/toys');(async()=>{constserver=Hapi.server();// Make a one-off auth strategy for testingToys.auth.strategy(server,'name-from-param',(request,h)=>{// Yes, perhaps not the most secureconst{ username }=request.params;if(!username){throwBoom.unauthorized(null,'Custom');}returnh.authenticated({credentials: {user: {name: username}}});});// Make function to efficiently index into a request to grab an authed user's nameconstgrabAuthedUsername=Toys.reacher('auth.credentials.user.name');// Default all route methods to "get", unless otherwise specifiedconstdefaultToGet=Toys.withRouteDefaults({method: 'get'});server.route(defaultToGet([{method: 'post',path: '/',handler: (request)=>{return{posted: true};}},{// Look ma, my method is defaulting to "get"!path: '/as/{username}',options: {auth: 'name-from-param',// Here's our simple auth strategyhandler: (request)=>{// grabAuthedUsername() is designed to be quickconstusername=grabAuthedUsername(request);return{ username };}}}]));awaitserver.start();console.log(`Now, go forth and ${server.info.uri}/as/your-name`);})();
API
The hapi utility toy chest
Note
Toys is intended for use with hapi v19+ and nodejs v12+ (see v2 for lower support).
Toys
Toys.withRouteDefaults(defaults)
Returns a function with signature function(route) that will apply defaults as defaults to the routeroute configuration object. It will shallow-merge any route validate and bind options to avoid inadvertently applying defaults to a Joi schema or other unfamiliar object. If route is an array of routes, it will apply the defaults to each route in the array.
constdefaultToGet=Toys.withRouteDefaults({method: 'get'});server.route(defaultToGet([{path: '/',handler: ()=>'I was gotten'},{method: 'post',path: '/',handler: ()=>'I was posted'}]));
Toys.pre(prereqs)
Returns a hapi route prerequisite configuration, mapping each key of prereqs to the assign value of a route prerequisite. When the key's corresponding value is a function, that function is used as the method of the prerequisite. When the key's corresponding value is an object, that object's keys and values are included in the prerequisite. When prereqs is a function, that function is simply passed-through. When prereqs is an array, the array's values are simply mapped as described above.
This is intended to be a useful shorthand for writing route prerequisites, as demonstrated below.
server.route({method: 'get',path: '/user/{id}',options: {pre: Toys.pre([{user: async({ params })=>awaitgetUserById(params.id)},({ pre })=>ensureUserIsPublic(pre.user),{groups: async({ params, pre })=>awaitgetUserGroups(params.id,pre.user.roles),posts: async({ params, pre })=>awaitgetUserPosts(params.id,pre.user.roles)}]),handler: ({ pre })=>({
...pre.user,groups: pre.groups,posts: pre.posts})}});// pre value is expanded as shown below/* pre: [ [ { assign: 'user', method: async ({ params }) => await getUserById(params.id) } ], ({ pre }) => ensureUserIsPublic(pre.user), [ { assign: 'groups', method: async ({ params, pre }) => await getUserGroups(params.id, pre.user.roles) }, { assign: 'posts', method: async ({ params, pre }) => await getUserPosts(params.id, pre.user.roles) } ] ] */
Toys.ext(method, [options])
Returns a hapi extension config{ method, options } without the type field. The config only has options set when provided as an argument. This is intended to be used with the route ext config.
Returns a hapi extension config{ type, method, options} with the type field set to EXTENSION, where EXTENSION is any of onRequest, onPreAuth, onPostAuth, onCredentials, onPreHandler, onPostHandler, onPreResponse, onPreStart, onPostStart, onPreStop, or onPostStop. The config only has options set when provided as an argument. This is intended to be used with server.ext().
server.ext([Toys.onPreAuth((request,h)=>{if(!request.query.specialParam){throwBoom.unauthorized();}returnh.continue;}),Toys.onPreResponse((request,h)=>{if(!request.response.isBoom&&request.query.specialParam==='secret'){request.log(['my-plugin'],'Someone knew a secret');}returnh.continue;},{sandbox: 'plugin'})]);
Toys.reacher(chain, [options])
Returns a function function(obj) that will return Hoek.reach(obj, chain, options). Unlike Hoek.reach(), this function is designed to be performant in hot code paths such as route handlers. See Hoek.reach() for a description of options.
constgetAuthedGroupId=Toys.reacher('auth.credentials.user.group.id');server.route({method: 'get',path: '/user/group',options: {auth: 'my-strategy',handler: (request)=>{constgroup=getAuthedGroupId(request);if(group!=='BRS'){throwBoom.unauthorized();}return{ group };}}});
Toys.transformer(transform, [options])
Returns a function function(obj) that will return Hoek.transform(obj, transform, options). Unlike Hoek.transform(), this function is designed to be performant in hot code paths such as route handlers. See Hoek.reach() for a description of options.
Adds an auth scheme and strategy with name name to server. Its implementation is given by authenticate as described in server.auth.scheme(). This is intended to make it simple to create a barebones auth strategy without having to create a reusable auth scheme; it is often useful for testing and simple auth implementations.
This is a plugin named toys-noop that does nothing and can be registered multiple times. This can be useful when conditionally registering a plugin in a list or glue manifest.
Waits for emitter to emit an event named eventName and returns the first value passed to the event's listener. When options.multiple is true it instead returns an array of all values passed to the listener. Throws if an event named 'error' is emitted unless options.error is false. This can be useful when waiting for an event in a handler, extension, or server method, which all require an async function when returning a value asynchronously.
Waits for a readable stream to end, a writable stream to finish, or a duplex stream to both end and finish. Throws an error if stream emits an 'error' event. This can be useful when waiting for a stream to process in a handler, extension, or server method, which all require an async function when returning a value asynchronously. This is powered by node's Stream.finished(), and accepts all of that utility's options. When options.cleanup is true, dangling event handlers left by Stream.finished() will be removed.
constFs=require('fs');constCrypto=require('crypto');// Hash a file and cache the result by filenameserver.method({name: 'hash',method: async(filename)=>{consthasher=Crypto.createHash('sha256');constinput=Fs.createReadStream(filename);constoutput=input.pipe(hasher);lethash='';output.on('data',(chunk)=>{hash+=chunk.toString('hex');});awaitToys.stream(output);returnhash;},options: {cache: {generateTimeout: 1000,expiresIn: 2000}}});
Toys.options(obj)
Given obj as a server, request, route, response toolkit, or realm, returns the relevant plugin options. If obj is none of the above then this method will throw an error. When used as an instance obj defaults to toys.server.
// Here is a route configuration in its own file.//// The route is added to the server somewhere else, but we still// need that server's plugin options for use in the handler.module.exports={method: 'post',path: '/user/{id}/resend-verification-email',handler: async(request)=>{// fromAddress configured at plugin registration time, e.g. no-reply@toys.bizconst{ fromAddress }=Toys.options(request);constuser=awaitserver.methods.getUser(request.params.id);awaitserver.methods.sendVerificationEmail({to: user.email,from: fromAddress});return{success: true};}};
Toys.header(response, name, value, [options])
Designed to behave identically to hapi's response.header(name, value, [options]), but provide a unified interface for setting HTTP headers between both hapi response objects and boom errors. This is useful in request extensions, when you don't know if request.response is a hapi response object or a boom error. Returns response.
name - the header name.
value - the header value.
options - (optional) object where:
append - if true, the value is appended to any existing header value using separator. Defaults to false.
separator - string used as separator when appending to an existing value. Defaults to ','.
override - if false, the header value is not set if an existing value present. Defaults to true.
duplicate - if false, the header value is not modified if the provided value is already included. Does not apply when append is false or if the name is 'set-cookie'. Defaults to true.
Toys.getHeaders(response)
Returns response's current HTTP headers, where response may be a hapi response object or a boom error.
Toys.code(response, statusCode)
Designed to behave identically to hapi's response.code(statusCode), but provide a unified interface for setting the HTTP status code between both hapi response objects and boom errors. This is useful in request extensions, when you don't know if request.response is a hapi response object or a boom error. Returns response.
Toys.getCode(response)
Returns response's current HTTP status code, where response may be a hapi response object or a boom error.
Toys.realm(obj)
Given obj as a server, request, route, response toolkit, or realm, returns the relevant realm. If obj is none of the above then this method will throw an error. When used as an instance obj defaults to toys.server.
Toys.rootRealm(realm)
Given a realm this method follows the realm.parent chain and returns the topmost realm, known as the "root realm." When used as an instance, returns toys.server.realm's root realm.
Toys.state(realm, pluginName)
Returns the plugin state for pluginName within realm (realm.plugins[pluginName]), and initializes it to an empty object if it is not already set. When used as an instance, returns the plugin state within toys.server.realm.
Toys.rootState(realm, pluginName)
Returns the plugin state for pluginName within realm's root realm, and initializes it to an empty object if it is not already set. When used as an instance, returns the plugin state within toys.server.realm's root realm.
Toys.forEachAncestorRealm(realm, fn)
Walks up the realm.parent chain and calls fn(realm) for each realm, starting with the passed realm. When used as an instance, this method starts with toys.server.realm.
Toys.asyncStorage(identifier)
Returns async local storage store associated with identifier, as set-up using Toys.withAsyncStorage(). When there is no active store, returns undefined.
Toys.withAsyncStorage(identifier, store, fn)
Runs and returns the result of fn with an active async local storage store identified by identifier. Intended to be used with Toys.asyncStorage(). Note that string identifiers beginning with '@hapipal' are reserved.
constmultiplyBy=async(x)=>{awaitHoek.wait(10);// Wait 10msreturnx*(Toys.asyncStorage('y')||0);};// The result is 4 * 3 = 12constresult=awaitToys.withAsyncStorage('y',3,async()=>{returnawaitmultiplyBy(4);});