andrew-codes/gatsby-plugin-elasticlunr-search

View on GitHub
src/gatsby-node.js

Summary

Maintainability
A
0 mins
Test Coverage
const crypto = require(`crypto`);
const {GraphQLScalarType} = require(`graphql`);
const elasticlunr = require(`elasticlunr`);

const SEARCH_INDEX_ID = `SearchIndex < Site`;
const SEARCH_INDEX_TYPE = `SiteSearchIndex`;
const parent = `___SOURCE___`;

const md5 = src => crypto.createHash(`md5`).update(src).digest(`hex`);

const createEmptySearchIndexNode = () => ({
    id: SEARCH_INDEX_ID,
    parent,
    children: [],
    pages: [],
});

const appendPage = ({pages}, newPage) => {
    const newPages = [
        ...pages,
        newPage
    ];
    const content = JSON.stringify(newPage);
    return {
        id: SEARCH_INDEX_ID,
        parent,
        children: [],
        pages: newPages,
        internal: {
            type: SEARCH_INDEX_TYPE,
            content: content,
            contentDigest: md5(content),
        },
    };
};

const createOrGetIndex = async (node, cache, getNode, server, {
    fields,
    resolvers,
}) => {
    const cacheKey = `${node.id}:index`;
    const cached = await cache.get(cacheKey);
    if (cached) {
        return cached;
    }

    const index = elasticlunr();
    index.setRef(`id`);
    fields.forEach(field => index.addField(field));

    for (const pageId of node.pages) {
        const pageNode = getNode(pageId);
        const fieldResolvers = resolvers[pageNode.internal.type];
        if (fieldResolvers) {
            const doc = {
                id: pageNode.id,
                date: pageNode.date,
                ...Object.keys(fieldResolvers)
                    .reduce((prev, key) => ({
                        ...prev,
                        [key]: fieldResolvers[key](pageNode),
                    }), {}),
            };

            index.addDoc(doc);
        }
    }

    const json = index.toJSON();
    await cache.set(cacheKey, json);
    return json;
};

const SearchIndex = new GraphQLScalarType({
    name: `${SEARCH_INDEX_TYPE}_Index`,
    description: `Serialized elasticlunr search index`,
    parseValue() {
        throw new Error(`Not supported`);
    },
    serialize(value) {
        return value;
    },
    parseLiteral() {
        throw new Error(`Not supported`);
    },
});

exports.sourceNodes = async ({ getNodes, boundActionCreators }) => {
    const {
        touchNode,
    } = boundActionCreators;

    const existingNodes = getNodes().filter(
        n => n.internal.owner === `@andrew-codes/gatsby-plugin-elasticlunr-search`
    );
    existingNodes.forEach(n => touchNode(n.id));
};

exports.onCreateNode = ({node, boundActionCreators, getNode}, {
    resolvers,
}) => {
    if (Object.keys(resolvers).indexOf(node.internal.type) === -1) {
        return;
    }

    const {
        createNode,
    } = boundActionCreators;
    const searchIndex = getNode(SEARCH_INDEX_ID) || createEmptySearchIndexNode();
    const newSearchIndex = appendPage(searchIndex, node.id);
    createNode(newSearchIndex);
};

exports.setFieldsOnGraphQLNodeType = ({type, getNode, cache}, pluginOptions) => {
    if (type.name !== SEARCH_INDEX_TYPE) {
        return null;
    }

    return {
        index: {
            type: SearchIndex,
            resolve: (node, _opts, _3, server) =>
                createOrGetIndex(node, cache, getNode, server, pluginOptions)
        },
    };
};