mirror of https://github.com/jkjoy/sunpeiwen.git
194 lines
4.8 KiB
JavaScript
194 lines
4.8 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;
|