gridsome
Version:
A JAMstack framework for building blazing fast websites with Vue.js
176 lines (133 loc) • 4.21 kB
JavaScript
const Loki = require('lokijs')
const autoBind = require('auto-bind')
const NodeIndex = require('./NodeIndex')
const EventEmitter = require('eventemitter3')
const { deprecate } = require('../utils/deprecate')
const { isArray, isPlainObject } = require('lodash')
const Collection = require('./Collection')
const { safeKey } = require('../utils')
const lokiOps = require('./lokiOps')
const { SyncWaterfallHook } = require('tapable')
const SyncBailWaterfallHook = require('../app/SyncBailWaterfallHook')
Object.assign(Loki.LokiOps, lokiOps)
class Store {
constructor (app) {
this.app = app
this.collections = {}
this.nodeIndex = new NodeIndex(app)
this._events = new EventEmitter()
this.lastUpdate = null
this.setUpdateTime()
autoBind(this)
this.metadata = new Loki.Collection('core/metadata', {
unique: ['key'],
autoupdate: true
})
this.hooks = {
addCollection: new SyncWaterfallHook(['options']),
addNode: new SyncBailWaterfallHook(['options', 'collection'])
}
this.hooks.addNode.tap('TransformNodeContent', require('./transformNodeContent'))
this.hooks.addNode.tap('ProcessNodeFields', require('./processNodeFields'))
}
getHooksContext () {
return {
hooks: this.hooks
}
}
on (eventName, fn, ctx) {
return this._events.on(eventName, fn, ctx)
}
off (eventName, fn, ctx) {
return this._events.removeListener(eventName, fn, ctx)
}
// site
addMetadata (key, data) {
let node = this.metadata.findOne({ key })
if (node && isArray(node.data) && isArray(data)) {
node.data = node.data.concat(data)
} else if (node && isPlainObject(node.data) && isPlainObject(data)) {
Object.assign(node.data, data)
} else if (node) {
node.data = data
} else {
node = this.metadata.insert({ key, data })
}
return node
}
// nodes
addCollection (options, store) {
options = this.hooks.addCollection.call(options)
if (this.collections.hasOwnProperty(options.typeName)) {
return this.getCollection(options.typeName)
}
const collection = new Collection(
options.typeName,
options,
store
)
collection.on('add', node => {
this.nodeIndex.addEntry(node, collection)
this.setUpdateTime()
})
collection.on('update', node => {
this.nodeIndex.updateEntry(node, collection)
this.setUpdateTime()
})
collection.on('remove', node => {
this.nodeIndex.removeEntry(node)
this.setUpdateTime()
})
this.collections[options.typeName] = collection
return collection
}
getCollection (typeName) {
return this.collections[typeName]
}
getNodeByUid (uid) {
const entry = this.nodeIndex.getEntry(uid)
return entry
? this.collections[entry.typeName].getNodeById(entry.id)
: null
}
getNode (typeName, id) {
return this.getCollection(typeName).getNodeById(id)
}
// TODO: move this to internal plugin
setBelongsTo (node, typeName, id) {
const entry = this.nodeIndex.getEntry(node.$uid)
const belongsTo = entry.belongsTo
const key = safeKey(id)
belongsTo[typeName] = belongsTo[typeName] || {}
belongsTo[typeName][key] = true
this.nodeIndex.index.update({ ...entry, belongsTo })
}
chainIndex (query = {}, resolveNodes = true) {
const chain = this.nodeIndex.getChain().find(query)
if (!resolveNodes) {
return chain
}
return chain.map(entry => {
const collection = this.collections[entry.typeName]
const node = collection.collection.by('id', entry.id)
return { ...node, $loki: undefined }
})
}
// utils
setUpdateTime () {
this.lastUpdate = Date.now()
if (this.app.isBootstrapped) {
this._events.emit('change')
}
}
// deprecated
addContentType (options, store) {
deprecate('The store.addContentType() method has been renamed to store.addCollection().')
return this.addCollection(options, store)
}
getContentType (typeName) {
deprecate('The store.getContentType() method has been renamed to store.getCollection().')
return this.getCollection(typeName)
}
}
module.exports = Store