379 lines
10 KiB
JavaScript
379 lines
10 KiB
JavaScript
"use strict";
|
|
|
|
const _ = require(`lodash`);
|
|
|
|
const invariant = require(`invariant`);
|
|
|
|
const {
|
|
getDb,
|
|
colls
|
|
} = require(`./index`); /////////////////////////////////////////////////////////////////////
|
|
// Node collection metadata
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
function makeTypeCollName(type) {
|
|
return `gatsby:nodeType:${type}`;
|
|
}
|
|
/**
|
|
* Creates a collection that will contain nodes of a certain type. The
|
|
* name of the collection for type `MyType` will be something like
|
|
* `gatsby:nodeType:MyType` (see `makeTypeCollName`)
|
|
*/
|
|
|
|
|
|
function createNodeTypeCollection(type) {
|
|
const collName = makeTypeCollName(type);
|
|
const nodeTypesColl = getDb().getCollection(colls.nodeTypes.name);
|
|
invariant(nodeTypesColl, `Collection ${colls.nodeTypes.name} should exist`);
|
|
nodeTypesColl.insert({
|
|
type,
|
|
collName
|
|
}); // TODO what if `addCollection` fails? We will have inserted into
|
|
// nodeTypesColl but no collection will exist. Need to make this
|
|
// into a transaction
|
|
|
|
const options = {
|
|
unique: [`id`],
|
|
indices: [`id`],
|
|
disableMeta: true
|
|
};
|
|
const coll = getDb().addCollection(collName, options);
|
|
return coll;
|
|
}
|
|
/**
|
|
* Returns the name of the collection that contains nodes of the
|
|
* specified type, where type is the node's `node.internal.type`
|
|
*/
|
|
|
|
|
|
function getTypeCollName(type) {
|
|
const nodeTypesColl = getDb().getCollection(colls.nodeTypes.name);
|
|
invariant(nodeTypesColl, `Collection ${colls.nodeTypes.name} should exist`);
|
|
let nodeTypeInfo = nodeTypesColl.by(`type`, type);
|
|
return nodeTypeInfo ? nodeTypeInfo.collName : undefined;
|
|
}
|
|
/**
|
|
* Returns a reference to the collection that contains nodes of the
|
|
* specified type, where type is the node's `node.internal.type`
|
|
*/
|
|
|
|
|
|
function getNodeTypeCollection(type) {
|
|
const typeCollName = getTypeCollName(type);
|
|
let coll;
|
|
|
|
if (typeCollName) {
|
|
coll = getDb().getCollection(typeCollName);
|
|
invariant(coll, `Type [${type}] Collection doesn't exist for nodeTypeInfo: [${typeCollName}]`);
|
|
return coll;
|
|
} else {
|
|
return undefined;
|
|
}
|
|
}
|
|
/**
|
|
* Deletes all empty node type collections, unless `force` is true, in
|
|
* which case it deletes the collections even if they have nodes in
|
|
* them
|
|
*/
|
|
|
|
|
|
function deleteNodeTypeCollections(force = false) {
|
|
const nodeTypesColl = getDb().getCollection(colls.nodeTypes.name); // find() returns all objects in collection
|
|
|
|
const nodeTypes = nodeTypesColl.find();
|
|
|
|
for (const nodeType of nodeTypes) {
|
|
let coll = getDb().getCollection(nodeType.collName);
|
|
|
|
if (coll.count() === 0 || force) {
|
|
getDb().removeCollection(coll.name);
|
|
nodeTypesColl.remove(nodeType);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Deletes all nodes from all the node type collections, including the
|
|
* id -> type metadata. There will be no nodes related data in loki
|
|
* after this is called
|
|
*/
|
|
|
|
|
|
function deleteAll() {
|
|
const db = getDb();
|
|
|
|
if (db) {
|
|
deleteNodeTypeCollections(true);
|
|
db.getCollection(colls.nodeMeta.name).clear();
|
|
}
|
|
} /////////////////////////////////////////////////////////////////////
|
|
// Queries
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* Returns the node with `id` == id, or null if not found
|
|
*/
|
|
|
|
|
|
function getNode(id) {
|
|
if (!id) {
|
|
return null;
|
|
} // First, find out which collection the node is in
|
|
|
|
|
|
const nodeMetaColl = getDb().getCollection(colls.nodeMeta.name);
|
|
invariant(nodeMetaColl, `nodeMeta collection should exist`);
|
|
const nodeMeta = nodeMetaColl.by(`id`, id);
|
|
|
|
if (nodeMeta) {
|
|
// Now get the collection and query it by the `id` field, which
|
|
// has an index on it
|
|
const {
|
|
typeCollName
|
|
} = nodeMeta;
|
|
const typeColl = getDb().getCollection(typeCollName);
|
|
invariant(typeColl, `type collection ${typeCollName} referenced by nodeMeta but doesn't exist`);
|
|
return typeColl.by(`id`, id);
|
|
} else {
|
|
return undefined;
|
|
}
|
|
}
|
|
/**
|
|
* Returns all nodes of a type (where `typeName ==
|
|
* node.internal.type`). This is an O(1) operation since nodes are
|
|
* already stored in separate collections by type
|
|
*/
|
|
|
|
|
|
function getNodesByType(typeName) {
|
|
invariant(typeName, `typeName is null`);
|
|
const collName = getTypeCollName(typeName);
|
|
const coll = getDb().getCollection(collName);
|
|
if (!coll) return [];
|
|
return coll.data;
|
|
}
|
|
/**
|
|
* Returns the collection of all nodes. This should be deprecated and
|
|
* `getNodesByType` should be used instead. Or at least where possible
|
|
*/
|
|
|
|
|
|
function getNodes() {
|
|
const nodeTypes = getTypes();
|
|
return _.flatMap(nodeTypes, nodeType => getNodesByType(nodeType));
|
|
}
|
|
/**
|
|
* Returns the unique collection of all node types
|
|
*/
|
|
|
|
|
|
function getTypes() {
|
|
const nodeTypes = getDb().getCollection(colls.nodeTypes.name).data;
|
|
return nodeTypes.map(nodeType => nodeType.type);
|
|
}
|
|
/**
|
|
* Looks up the node by id, records a dependency between the node and
|
|
* the path, and then returns the node
|
|
*
|
|
* @param {string} id node id to lookup
|
|
* @param {string} path the page path to record a node dependency
|
|
* against
|
|
* @returns {Object} node or undefined if not found
|
|
*/
|
|
|
|
|
|
function getNodeAndSavePathDependency(id, path) {
|
|
invariant(id, `id is null`);
|
|
invariant(id, `path is null`);
|
|
|
|
const createPageDependency = require(`../../redux/actions/add-page-dependency`);
|
|
|
|
const node = getNode(id);
|
|
createPageDependency({
|
|
path,
|
|
nodeId: id
|
|
});
|
|
return node;
|
|
}
|
|
/**
|
|
* Determine if node has changed (by comparing its
|
|
* `internal.contentDigest`
|
|
*
|
|
* @param {string} id
|
|
* @param {string} digest
|
|
* @returns {boolean}
|
|
*/
|
|
|
|
|
|
function hasNodeChanged(id, digest) {
|
|
const node = getNode(id);
|
|
|
|
if (!node) {
|
|
return true;
|
|
} else {
|
|
return node.internal.contentDigest !== digest;
|
|
}
|
|
} /////////////////////////////////////////////////////////////////////
|
|
// Create/Update/Delete
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* Creates a node in the DB. Will create a collection for the node
|
|
* type if one hasn't been created yet
|
|
*
|
|
* @param {Object} node The node to add. Must have an `id` and
|
|
* `internal.type`
|
|
*/
|
|
|
|
|
|
function createNode(node, oldNode) {
|
|
invariant(node.internal, `node has no "internal" field`);
|
|
invariant(node.internal.type, `node has no "internal.type" field`);
|
|
invariant(node.id, `node has no "id" field`);
|
|
const type = node.internal.type; // Loki doesn't provide "upsert", so if the node already exists, we
|
|
// delete and then create it
|
|
|
|
if (oldNode) {
|
|
deleteNode(oldNode);
|
|
}
|
|
|
|
let nodeTypeColl = getNodeTypeCollection(type);
|
|
|
|
if (!nodeTypeColl) {
|
|
nodeTypeColl = createNodeTypeCollection(type);
|
|
}
|
|
|
|
const nodeMetaColl = getDb().getCollection(colls.nodeMeta.name);
|
|
invariant(nodeMetaColl, `Collection ${colls.nodeMeta.name} should exist`);
|
|
nodeMetaColl.insert({
|
|
id: node.id,
|
|
typeCollName: nodeTypeColl.name
|
|
}); // TODO what if this insert fails? We will have inserted the id ->
|
|
// collName mapping, but there won't be any nodes in the type
|
|
// collection. Need to create a transaction around this
|
|
|
|
return nodeTypeColl.insert(node);
|
|
}
|
|
/**
|
|
* Updates a node in the DB. The contents of `node` will completely
|
|
* overwrite value in the DB. Note, `node` must be a loki node. i.e it
|
|
* has `$loki` and `meta` fields.
|
|
*
|
|
* @param {Object} node The new node information. This should be all
|
|
* the node information. Not just changes
|
|
*/
|
|
|
|
|
|
function updateNode(node) {
|
|
invariant(node.internal, `node has no "internal" field`);
|
|
invariant(node.internal.type, `node has no "internal.type" field`);
|
|
invariant(node.id, `node has no "id" field`);
|
|
const type = node.internal.type;
|
|
let coll = getNodeTypeCollection(type);
|
|
invariant(coll, `${type} collection doesn't exist. When trying to update`);
|
|
coll.update(node);
|
|
}
|
|
/**
|
|
* Deletes a node from its type collection and removes its id ->
|
|
* collName mapping. Function is idempotent. If the node has already
|
|
* been deleted, this is a noop.
|
|
*
|
|
* @param {Object} the node to delete. Must have an `id` and
|
|
* `internal.type`
|
|
*/
|
|
|
|
|
|
function deleteNode(node) {
|
|
invariant(node.internal, `node has no "internal" field`);
|
|
invariant(node.internal.type, `node has no "internal.type" field`);
|
|
invariant(node.id, `node has no "id" field`);
|
|
const type = node.internal.type;
|
|
let nodeTypeColl = getNodeTypeCollection(type);
|
|
|
|
if (!nodeTypeColl) {
|
|
invariant(nodeTypeColl, `${type} collection doesn't exist. When trying to delete`);
|
|
}
|
|
|
|
if (nodeTypeColl.by(`id`, node.id)) {
|
|
const nodeMetaColl = getDb().getCollection(colls.nodeMeta.name);
|
|
invariant(nodeMetaColl, `Collection ${colls.nodeMeta.name} should exist`);
|
|
nodeMetaColl.findAndRemove({
|
|
id: node.id
|
|
}); // TODO What if this `remove()` fails? We will have removed the id
|
|
// -> collName mapping, but not the actual node in the
|
|
// collection. Need to make this into a transaction
|
|
|
|
nodeTypeColl.remove(node);
|
|
} // idempotent. Do nothing if node wasn't already in DB
|
|
|
|
}
|
|
/**
|
|
* deprecated
|
|
*/
|
|
|
|
|
|
function deleteNodes(nodes) {
|
|
for (const node of nodes) {
|
|
deleteNode(node);
|
|
}
|
|
} /////////////////////////////////////////////////////////////////////
|
|
// Reducer
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
function reducer(state = new Map(), action) {
|
|
switch (action.type) {
|
|
case `DELETE_CACHE`:
|
|
deleteAll();
|
|
return null;
|
|
|
|
case `CREATE_NODE`:
|
|
{
|
|
createNode(action.payload, action.oldNode);
|
|
return null;
|
|
}
|
|
|
|
case `ADD_FIELD_TO_NODE`:
|
|
case `ADD_CHILD_NODE_TO_PARENT_NODE`:
|
|
updateNode(action.payload);
|
|
return null;
|
|
|
|
case `DELETE_NODE`:
|
|
{
|
|
if (action.payload) {
|
|
deleteNode(action.payload);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
case `DELETE_NODES`:
|
|
{
|
|
deleteNodes(action.payload);
|
|
return null;
|
|
}
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
} /////////////////////////////////////////////////////////////////////
|
|
// Exports
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
module.exports = {
|
|
getNodeTypeCollection,
|
|
getNodes,
|
|
getNode,
|
|
getNodesByType,
|
|
getTypes,
|
|
hasNodeChanged,
|
|
getNodeAndSavePathDependency,
|
|
createNode,
|
|
updateNode,
|
|
deleteNode,
|
|
deleteNodeTypeCollections,
|
|
deleteAll,
|
|
reducer
|
|
};
|
|
//# sourceMappingURL=nodes.js.map
|