gatsby-source-payload-cms
Version:
Source data from Payload CMS
281 lines (280 loc) • 13.9 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAssetNode = exports.createLocalFileNode = exports.nodeBuilder = exports.sourceNodes = void 0;
const constants_1 = require("./constants");
const axios_instance_1 = require("./axios-instance");
const fetch_1 = require("./fetch");
const utils_1 = require("./utils");
const utils_2 = require("./utils");
const gatsby_source_filesystem_1 = require("gatsby-source-filesystem");
const lodash_1 = require("lodash");
const utils_3 = require("./utils");
let isFirstSource = true;
const pluginName = `gatsby-source-payload-cms`;
// const LAST_FETCHED_KEY = `updatedAt`
/**
* The sourceNodes API is the heart of a Gatsby source plugin. This is where data is ingested and transformed into Gatsby's data layer.
* @see https://www.gatsbyjs.com/docs/reference/config-files/gatsby-node/#sourceNodes
*/
const sourceNodes = (gatsbyApi, pluginOptions) => __awaiter(void 0, void 0, void 0, function* () {
const { actions, reporter, cache, getNodes } = gatsbyApi;
const { touchNode } = actions;
const { endpoint } = pluginOptions;
const { collectionTypes, globalTypes, uploadTypes } = pluginOptions;
/**
* It's good practice to give your users some feedback on progress and status. Instead of printing individual lines, use the activityTimer API.
* This will give your users a nice progress bar and can you give updates with the .setStatus API.
* In the end your users will also have the exact time it took to source the data.
* @see https://www.gatsbyjs.com/docs/reference/config-files/node-api-helpers/#reporter
*/
const sourcingTimer = reporter.activityTimer(`Sourcing from ${pluginName} API`);
sourcingTimer.start();
if (isFirstSource) {
/**
* getNodes() returns all nodes in Gatsby's data layer
*/
getNodes().forEach((node) => {
/**
* "owner" is the name of your plugin, the "name" you defined in the package.json
*/
if (node.internal.owner !== pluginName) {
return;
}
/**
* Gatsby aggressively garbage collects nodes between runs. This means that nodes that were created in the previous run but are not created in the current run will be deleted. You can tell Gatsby to keep old, but still valid nodes around, by "touching" them.
* For this you need to use the touchNode API.
*
* However, Gatsby only checks if a node has been touched on the first sourcing. This is what the "isFirstSource" variable is for.
* @see https://www.gatsbyjs.com/docs/reference/config-files/actions/#touchNode
*/
touchNode(node);
});
isFirstSource = false;
}
/**
* If your API supports delta updates via e.g. a timestamp or token, you can store that information via the cache API.
*
* The cache API is a key-value store that persists between runs.
* You should also use it to persist results of time/memory/cpu intensive tasks.
* @see https://www.gatsbyjs.com/docs/reference/config-files/node-api-helpers/#cache
*/
const lastFetchedDate = yield cache.get(constants_1.CACHE_KEYS.Timestamp);
const lastFetchedDateCurrent = Date.now();
/**
* The reporter API has a couple of methods:
* - info: Print a message to the console
* - warn: Print a warning message to the console
* - error: Print an error message to the console
* - panic: Print an error message to the console and exit the process
* - panicOnBuild: Print an error message to the console and exit the process (only during "gatsby build")
* - verbose: Print a message to the console that is only visible when the "verbose" flag is enabled (e.g. gatsby build --verbose)
* @see https://www.gatsbyjs.com/docs/reference/config-files/node-api-helpers/#reporter
*
* Try to keep the terminal information concise and informative. You can use the "verbose" method to print more detailed information.
* You don't need to print out every bit of detail your plugin is doing as otherwise it'll flood the user's terminal.
*/
reporter.verbose(`[${pluginName}] Last fetched date: ${lastFetchedDate}`);
const axiosInstance = (0, axios_instance_1.createAxiosInstance)(pluginOptions);
const context = Object.assign(Object.assign({ axiosInstance }, gatsbyApi), { pluginOptions });
// convert string collectionTypes and globalTypes to object
const normalizedGlobalTypes = (0, utils_1.normalizeGlobals)(globalTypes, endpoint);
const normalizedCollectionTypes = (0, utils_3.normalizeCollections)(collectionTypes, endpoint);
const normalizedUploadTypes = (0, utils_3.normalizeCollections)(uploadTypes, endpoint);
const collectionResults = yield Promise.all(normalizedCollectionTypes.map((type) => (0, fetch_1.fetchEntities)(type, context)));
const globalResults = yield Promise.all(normalizedGlobalTypes.map((type) => (0, fetch_1.fetchEntity)(type, context)));
const uploadResults = yield Promise.all(normalizedUploadTypes.map((type) => (0, fetch_1.fetchEntities)(type, context)));
/**
* Gatsby's cache API uses LMDB to store data inside the .cache/caches folder.
*
* As mentioned above, cache the timestamp of last sourcing.
* The cache API accepts "simple" data structures like strings, integers, arrays.
* For example, passing a Set or Map won't work because the "structuredClone" option is purposefully not enabled:
* https://github.com/kriszyp/lmdb-js#serialization-options
*/
yield cache.set(constants_1.CACHE_KEYS.Timestamp, lastFetchedDateCurrent);
/**
* Up until now the terminal output only showed "Sourcing from plugin API" and a timer. Via the "setStatus" method you can add more information to the output.
* It'll then print "Sourcing from plugin API - Processing X posts and X authors"
*/
//sourcingTimer.setStatus(`Processing ${posts.length} posts and ${authors.length} authors`)
/**
* Set a default prefix
*/
const prefix = pluginOptions.nodePrefix;
/**
* Collect relationship ids
*/
let relationshipIds = {};
/**
* Iterate over the data and create nodes
*/
for (const result of collectionResults) {
for (const collection of result) {
nodeBuilder({
gatsbyApi,
input: {
type: (0, utils_2.gatsbyNodeTypeName)(Object.assign({ payloadSlug: collection.gatsbyNodeType }, ((0, lodash_1.isString)(prefix) && { prefix: prefix }))),
data: collection,
},
});
// Populate relationshipIds
if (pluginOptions.localFiles || pluginOptions.imageCdn) {
relationshipIds = Object.assign(Object.assign({}, (0, utils_2.documentRelationships)(collection, [collection.gatsbyNodeType, collection.id].join(`.`))), relationshipIds);
}
}
}
for (const result of globalResults) {
for (const global of result) {
nodeBuilder({
gatsbyApi,
input: {
type: (0, utils_2.gatsbyNodeTypeName)(Object.assign({ payloadSlug: global.gatsbyNodeType }, ((0, lodash_1.isString)(prefix) && { prefix: prefix }))),
data: global,
},
});
// Populate relationshipIds
if (pluginOptions.localFiles || pluginOptions.imageCdn) {
relationshipIds = Object.assign(Object.assign({}, (0, utils_2.documentRelationships)(global, global.gatsbyNodeType)), relationshipIds);
}
}
}
for (const result of uploadResults) {
for (const upload of result) {
let imageCdnId;
if (pluginOptions.localFiles) {
createLocalFileNode(context, upload, relationshipIds);
}
if (pluginOptions.imageCdn) {
imageCdnId = yield createAssetNode(context, upload, relationshipIds);
}
nodeBuilder({
gatsbyApi,
input: {
type: (0, utils_2.gatsbyNodeTypeName)(Object.assign({ payloadSlug: upload.gatsbyNodeType }, ((0, lodash_1.isString)(prefix) && { prefix: prefix }))),
data: Object.assign(Object.assign({}, upload), (imageCdnId && { gatsbyImageCdn: imageCdnId })),
},
});
}
}
/*for (const author of authors) {
nodeBuilder({ gatsbyApi, input: { type: NODE_TYPES.Author, data: author } })
}*/
sourcingTimer.end();
});
exports.sourceNodes = sourceNodes;
function nodeBuilder({ gatsbyApi, input }) {
const idFragments = [input.type, input.data.id];
if (input.data.locale) {
idFragments.push(input.data.locale);
}
const id = gatsbyApi.createNodeId(idFragments.join(`-`));
const extraData = {};
if (input.type === `Post`) {
// const assetId = createAssetNode(gatsbyApi, input.data.image)
// This sets the autogenerated Node ID onto the "image" key of the Post node. Then the @link directive in the schema will work.
// extraData.image = assetId
}
const node = Object.assign(Object.assign(Object.assign({}, input.data), extraData), { id,
/**
* "id" is a reserved field in Gatsby, so if you want to keep it, you need to rename it
* You can see all reserved fields here:
* @see https://www.gatsbyjs.com/docs/reference/graphql-data-layer/node-interface/
*/
_id: input.data.id, parent: null, children: [], internal: {
type: input.type,
/**
* The content digest is a hash of the entire node.
* Gatsby uses this internally to determine if the node needs to be updated.
*/
contentDigest: gatsbyApi.createContentDigest(input.data),
} });
/**
* Add the node to Gatsby's data layer. This is the most important piece of a Gatsby source plugin.
* @see https://www.gatsbyjs.com/docs/reference/config-files/actions/#createNode
*/
gatsbyApi.actions.createNode(node);
}
exports.nodeBuilder = nodeBuilder;
function createLocalFileNode(context, data, relationshipIds) {
return __awaiter(this, void 0, void 0, function* () {
const { createNode, createNodeField } = context.actions;
const { getCache } = context;
const baseUrl = (0, lodash_1.get)(context, `pluginOptions.baseUrl`, ``).replace(/\/$/, ``);
const url = (0, utils_1.payloadImageUrl)(data, data.payloadImageSize, baseUrl);
const fileNode = yield (0, gatsby_source_filesystem_1.createRemoteFileNode)({
url,
getCache,
createNode,
createNodeId: () => `upload-${data.id}`,
});
const relationships = Object.keys((0, lodash_1.pickBy)(relationshipIds, (value) => {
return value === data.id;
}));
if (relationships.length > 0) {
yield createNodeField({
node: fileNode,
name: `relationships`,
value: relationships,
});
}
});
}
exports.createLocalFileNode = createLocalFileNode;
function createAssetNode(context, data, relationshipIds) {
const id = context.createNodeId(`${constants_1.NODE_TYPES.Asset}-${data.url}`);
const baseUrl = (0, lodash_1.get)(context, `pluginOptions.baseUrl`, ``).replace(/\/$/, ``);
const image = (0, utils_1.payloadImage)(data, data.payloadImageSize);
const url = (0, utils_1.payloadImageUrl)(data, data.payloadImageSize, baseUrl);
const relationships = Object.keys((0, lodash_1.pickBy)(relationshipIds, (value) => {
return value === data.id;
}));
/**
* For Image CDN and the "RemoteFile" interface, these fields are required:
* - url
* - filename
* - mimeType
* For images, these fields are also required:
* - width
* - height
*/
const assetNode = {
id,
url,
/**
* Don't hardcode the "mimeType" field, it has to match the input image. If you don't have that information, use:
* @see https://github.com/nodeca/probe-image-size
* For the sake of this demo, it can be hardcoded since all images are JPGs
*/
mimeType: image.mimeType,
filename: url,
/**
* If you don't know the width and height of the image, use: https://github.com/nodeca/probe-image-size
*/
width: Math.round(image.width),
height: Math.round(image.height),
// placeholderUrl: `${data.url}&w=%width%&h=%height%`,
relationships,
alt: data.alt || ``,
parent: null,
children: [],
internal: {
type: constants_1.NODE_TYPES.Asset,
contentDigest: context.createContentDigest(data),
},
};
context.actions.createNode(assetNode);
/**
* Return the id so it can be used for the foreign key relationship on the Post node.
*/
return id;
}
exports.createAssetNode = createAssetNode;