hexo/node_modules/warehouse/lib/database.js

194 lines
5.0 KiB
JavaScript

'use strict';
const { parse: createJsonParseStream } = require('./jsonstream');
const Promise = require('bluebird');
const fs = require('graceful-fs');
const Model = require('./model');
const Schema = require('./schema');
const SchemaType = require('./schematype');
const WarehouseError = require('./error');
const pkg = require('../package.json');
const { open } = fs.promises;
const pipeline = Promise.promisify(require('stream').pipeline);
const log = require('hexo-log')();
let _writev;
if (typeof fs.writev === 'function') {
_writev = (handle, buffers) => handle.writev(buffers);
} else {
_writev = async (handle, buffers) => {
for (const buffer of buffers) await handle.write(buffer);
};
}
async function exportAsync(database, path) {
const handle = await open(path, 'w');
try {
// Start body & Meta & Start models
await handle.write(`{"meta":${JSON.stringify({
version: database.options.version,
warehouse: pkg.version
})},"models":{`);
const models = database._models;
const keys = Object.keys(models);
const { length } = keys;
// models body
for (let i = 0; i < length; i++) {
const key = keys[i];
if (!models[key]) continue;
const buffers = [];
if (i) buffers.push(Buffer.from(',', 'ascii'));
buffers.push(Buffer.from(`"${key}":`));
buffers.push(Buffer.from(models[key]._export()));
await _writev(handle, buffers);
}
// End models
await handle.write('}}');
} catch (e) {
log.error(e);
if (e instanceof RangeError && e.message.includes('Invalid string length')) {
// NOTE: Currently, we can't deal with anything about this issue.
// If do not `catch` the exception after the process will not work (e.g: `after_generate` filter.)
// A side-effect of this workaround is the `db.json` will not generate.
log.warn('see: https://github.com/nodejs/node/issues/35973');
} else {
throw e;
}
} finally {
await handle.close();
}
}
class Database {
/**
* Database constructor.
*
* @param {object} [options]
* @param {number} [options.version=0] Database version
* @param {string} [options.path] Database path
* @param {function} [options.onUpgrade] Triggered when the database is upgraded
* @param {function} [options.onDowngrade] Triggered when the database is downgraded
*/
constructor(options) {
this.options = Object.assign({
version: 0,
onUpgrade() {},
onDowngrade() {}
}, options);
this._models = {};
class _Model extends Model {}
this.Model = _Model;
_Model.prototype._database = this;
}
/**
* Creates a new model.
*
* @param {string} name
* @param {Schema|object} [schema]
* @return {Model}
*/
model(name, schema) {
if (this._models[name]) {
return this._models[name];
}
this._models[name] = new this.Model(name, schema);
const model = this._models[name];
return model;
}
/**
* Loads database.
*
* @param {function} [callback]
* @return {Promise}
*/
load(callback) {
const { path, onUpgrade, onDowngrade, version: newVersion } = this.options;
if (!path) throw new WarehouseError('options.path is required');
let oldVersion = 0;
const getMetaCallBack = data => {
if (data.meta && data.meta.version) {
oldVersion = data.meta.version;
}
};
// data event arg0 wrap key/value pair.
const parseStream = createJsonParseStream('models.$*');
parseStream.once('header', getMetaCallBack);
parseStream.once('footer', getMetaCallBack);
parseStream.on('data', data => {
this.model(data.key)._import(data.value);
});
const rs = fs.createReadStream(path, 'utf8');
return pipeline(rs, parseStream).then(() => {
if (newVersion > oldVersion) {
return onUpgrade(oldVersion, newVersion);
} else if (newVersion < oldVersion) {
return onDowngrade(oldVersion, newVersion);
}
}).asCallback(callback);
}
/**
* Saves database.
*
* @param {function} [callback]
* @return {Promise}
*/
save(callback) {
const { path } = this.options;
if (!path) throw new WarehouseError('options.path is required');
return Promise.resolve(exportAsync(this, path)).asCallback(callback);
}
toJSON() {
const models = Object.keys(this._models)
.reduce((obj, key) => {
const value = this._models[key];
if (value != null) obj[key] = value;
return obj;
}, {});
return {
meta: {
version: this.options.version,
warehouse: pkg.version
}, models
};
}
}
Database.prototype.Schema = Schema;
Database.Schema = Database.prototype.Schema;
Database.prototype.SchemaType = SchemaType;
Database.SchemaType = Database.prototype.SchemaType;
Database.version = pkg.version;
module.exports = Database;