gatsby-source-sanity
Version:
Gatsby source plugin for building websites using Sanity.io as a backend.
170 lines • 6.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = require("lodash");
const mutator_1 = require("@sanity/mutator");
const graphql_1 = require("gatsby/graphql");
const scalarTypeNames = graphql_1.specifiedScalarTypes.map(def => def.name).concat(['JSON', 'Date']);
// Movie => SanityMovie
const typePrefix = 'Sanity';
// Node fields used internally by Gatsby.
exports.RESTRICTED_NODE_FIELDS = ['id', 'children', 'parent', 'fields', 'internal'];
// Transform a Sanity document into a Gatsby node
function processDocument(doc, options) {
const { createNode, createNodeId, createParentChildLink, createContentDigest, overlayDrafts, skipCreate } = options;
const hoistedNodes = [];
const rawAliases = getRawAliases(doc, options);
const safe = prefixConflictingKeys(doc);
const hoisted = hoistMixedArrays(safe, options, { hoistedNodes, path: [doc._id] });
const withRefs = makeNodeReferences(hoisted, options);
const node = Object.assign({}, withRefs, rawAliases, { id: createNodeId(overlayDrafts ? unprefixDraftId(doc._id) : doc._id), parent: null, children: [], internal: {
mediaType: 'application/json',
type: getTypeName(doc._type),
contentDigest: createContentDigest(JSON.stringify(withRefs))
} });
if (!skipCreate) {
createNode(node);
hoistedNodes.forEach(childNode => {
const child = childNode;
createNode(child);
createParentChildLink({ parent: node, child });
});
}
return node;
}
exports.processDocument = processDocument;
// `drafts.foo-bar` => `foo.bar`
function unprefixDraftId(id) {
return id.replace(/^drafts\./, '');
}
// movie => SanityMovie
// blog_post => SanityBlogPost
// sanity.imageAsset => SanityImageAsset
function getTypeName(type) {
if (!type) {
return type;
}
const typeName = lodash_1.startCase(type);
if (scalarTypeNames.includes(typeName)) {
return typeName;
}
return `${typePrefix}${typeName.replace(/\s+/g, '').replace(/^Sanity/, '')}`;
}
exports.getTypeName = getTypeName;
// {foo: 'bar', children: []} => {foo: 'bar', sanityChildren: []}
function prefixConflictingKeys(obj) {
// Will be overwritten, but initialize for type safety
const initial = { _id: '', _type: '' };
return Object.keys(obj).reduce((target, key) => {
if (exports.RESTRICTED_NODE_FIELDS.includes(key)) {
target[`${lodash_1.camelCase(typePrefix)}${lodash_1.upperFirst(key)}`] = obj[key];
}
else {
target[key] = obj[key];
}
return target;
}, initial);
}
// Transform arrays with both inline objects and references into just references,
// adding gatsby child nodes as needed
// {body: [{_ref: 'grrm'}, {_type: 'book', name: 'Game of Thrones'}]}
// =>
// {body: [{_ref: 'grrm'}, {_ref: 'someNode'}]}
function hoistMixedArrays(obj, options, context) {
const { createNodeId, createContentDigest, overlayDrafts } = options;
const { hoistedNodes, path } = context;
if (lodash_1.isPlainObject(obj)) {
const initial = {};
return Object.keys(obj).reduce((acc, key) => {
// Skip raw aliases
if (key.startsWith('_raw')) {
return acc;
}
acc[key] = hoistMixedArrays(obj[key], options, Object.assign({}, context, { path: path.concat(key) }));
return acc;
}, initial);
}
if (Array.isArray(obj)) {
let hasRefs = false;
let hasNonRefs = false;
// First, let's make sure we handle deeply nested structures
const hoisted = obj.map((item, index) => {
hasRefs = hasRefs || (item && item._ref);
hasNonRefs = hasNonRefs || (item && !item._ref);
return hoistMixedArrays(item, options, Object.assign({}, context, { path: path.concat(`${index}`) }));
});
const hasMixed = hasRefs && hasNonRefs;
if (!hasMixed) {
return hoisted;
}
// Now let's hoist non-ref nodes
return hoisted.map((item, index) => {
if (!item || item._ref) {
return item;
}
const safe = prefixConflictingKeys(item);
const withRefs = makeNodeReferences(safe, options);
const rawId = path.concat(`${index}`).join('>');
const id = createNodeId(rawId);
const parent = createNodeId(overlayDrafts ? unprefixDraftId(path[0]) : path[0]);
const childNode = Object.assign({}, withRefs, { id,
parent, children: [], internal: {
mediaType: 'application/json',
type: getTypeName(item._type),
contentDigest: createContentDigest(JSON.stringify(withRefs))
} });
hoistedNodes.push(childNode);
return { _ref: rawId };
});
}
return obj;
}
function getRawAliases(doc, options) {
const { typeMap } = options;
const typeName = getTypeName(doc._type);
const type = typeMap.objects[typeName];
if (!type) {
return doc;
}
const initial = {};
return Object.keys(type.fields).reduce((acc, fieldName) => {
const field = type.fields[fieldName];
if (typeMap.scalars.includes(field.namedType.name.value)) {
return acc;
}
const aliasName = '_' + lodash_1.camelCase(`raw ${fieldName}`);
acc[aliasName] = doc[fieldName];
return acc;
}, initial);
}
// Tranform Sanity refs ({_ref: 'foo'}) to Gatsby refs (field___NODE: 'foo')
// {author: {_ref: 'grrm'}} => {author___NODE: 'someNodeIdFor-grrm'}
function makeNodeReferences(doc, options) {
const { createNodeId } = options;
const refs = mutator_1.extractWithPath('..[_ref]', doc);
if (refs.length === 0) {
return doc;
}
const newDoc = lodash_1.cloneDeep(doc);
refs.forEach(match => {
const path = match.path.slice(0, -1);
const key = path[path.length - 1];
const isArrayIndex = typeof key === 'number';
const referencedId = createNodeId(match.value);
if (isArrayIndex) {
const arrayPath = path.slice(0, -1);
const field = path[path.length - 2];
const nodePath = path.slice(0, -2).concat(`${field}___NODE`);
const refPath = nodePath.concat(key);
lodash_1.set(newDoc, nodePath, lodash_1.get(newDoc, nodePath, lodash_1.get(newDoc, arrayPath)));
lodash_1.set(newDoc, refPath, referencedId);
lodash_1.unset(newDoc, arrayPath);
}
else {
const refPath = path.slice(0, -1).concat(`${key}___NODE`);
lodash_1.unset(newDoc, path);
lodash_1.set(newDoc, refPath, referencedId);
}
});
return newDoc;
}
//# sourceMappingURL=normalize.js.map