Files
30-seconds-of-code/node_modules/gatsby/dist/db/loki/nodes.js
2019-08-20 15:52:05 +02:00

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