Skip to content

Latest commit

 

History

History
291 lines (214 loc) · 11.6 KB

API.md

File metadata and controls

291 lines (214 loc) · 11.6 KB

API

The hapi utility toy chest

Note

Toys is intended for use with hapi v20+ and nodejs v16+ (see v3 for lower support).

Toys

Toys.withRouteDefaults(defaults)

Returns a function with signature function(route) that will apply defaults as defaults to the route route 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.

const defaultToGet = 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 }) => await getUserById(params.id)
            },
            ({ pre }) => ensureUserIsPublic(pre.user),
            {
                groups: async ({ params, pre }) => await getUserGroups(params.id, pre.user.roles),
                posts: async ({ params, pre }) => await getUserPosts(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.

server.route({
    method: 'get',
    path: '/',
    options: {
        handler: (request) => {

            return { ok: true };
        },
        ext: {
            onPostAuth: Toys.ext((request, h) => {

                if (!request.headers['special-header']) {
                    throw Boom.unauthorized();
                }

                return h.continue;
            })
        }
    }
});

Toys.EXTENSION(method, [options])

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) {
            throw Boom.unauthorized();
        }

        return h.continue;
    }),
    Toys.onPreResponse((request, h) => {

        if (!request.response.isBoom &&
            request.query.specialParam === 'secret') {

            request.log(['my-plugin'], 'Someone knew a secret');
        }

        return h.continue;
    }, {
        sandbox: 'plugin'
    })
]);

Toys.auth.strategy(server, name, authenticate)

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.

Toys.auth.strategy(server, 'simple-bearer', async (request, h) => {

    const token = (request.headers.authorization || '').replace('Bearer ', '');

    if (!token) {
        throw Boom.unauthorized(null, 'Bearer');
    }

    const credentials = await lookupSession(token);

    return h.authenticated({ credentials });
});

server.route({
    method: 'get',
    path: '/user',
    options: {
        auth: 'simple-bearer',
        handler: (request) => request.auth.credentials.user
    }
});

Toys.noop

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.

await server.register([
    require('./my-plugin-a'),
    require('./my-plugin-b'),
    (process.env.NODE_ENV === 'production') ? Toys.noop : require('lout')
]);

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.biz
        const { fromAddress } = Toys.options(request);
        const user = await server.methods.getUser(request.params.id);

        await server.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.

const multiplyBy = async (x) => {

    await Hoek.wait(10); // Wait 10ms

    return x * (Toys.asyncStorage('y') || 0);
};

// The result is 4 * 3 = 12
const result = await Toys.withAsyncStorage('y', 3, async () => {

    return await multiplyBy(4);
});

Toys.asyncStorageInternals()

Returns a Map which maps identifiers utilized by Toys.withAsyncStorage() to the underlying instances of AsyncLocalStorage.

Toys.patchJoiSchema(schema)

Converts a Joi creation schema into a patch schema, ignoring defaults and making all keys optional.

// result.name is no longer required
const result = Toys.patchJoiSchema(Joi.object().keys({
    name: Joi.string().required()
}));