Skip to content

Commit

Permalink
feat(disk): use local mongdb instead of disk
Browse files Browse the repository at this point in the history
The sails-disk integration was causing problems in testing.
The temporary files were not well cleaned and there was often random dead-locks.
This mongodb package fetches the mongodb binary and we run it:
- In a temp directory in dev (for persistance)
- In memory for testing
- There should not be more than 4 concurrent Jest process
- The best option is using --runInBand in testing
  • Loading branch information
reel committed Nov 13, 2019
1 parent 32c2635 commit a060760
Show file tree
Hide file tree
Showing 4 changed files with 805 additions and 404 deletions.
186 changes: 36 additions & 150 deletions packages/disk/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
const Waterline = require('waterline');
const disk = require('sails-disk');
const HenriMongoose = require('@usehenri/mongoose');
const { MongoMemoryServer } = require('mongodb-memory-server');
const md5 = require('md5');
const debug = require('debug')('henri:disk');
const path = require('path');
const os = require('os');
const fs = require('fs');

/**
* Disk database adapter
*
* @class Disk
*/
class Disk {
class Disk extends HenriMongoose {
/**
* Creates an instance of Disk.
*
Expand All @@ -16,159 +21,49 @@ class Disk {
* @memberof Disk
*/
constructor(name, config, thisHenri) {
super(name, { url: 'soon' }, thisHenri);

this.adapterName = 'disk';
this.name = name;
this.config = config;
this.models = {};
this.user = null;
this.waterline = new Waterline();
this.instance = null;
this.sessionPath = '.tmp/nedb-sessions.db';
this.name = name;
this.mongod = null;
this.mongoUri = '';
this.henri = thisHenri;

this.addModel = this.addModel.bind(this);
this.overload = this.overload.bind(this);
this.getModels = this.getModels.bind(this);
this.getSessionConnector = this.getSessionConnector.bind(this);
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
debug('constructor => done');
}

/**
* Add a model to the store
* Starts the store
*
* @param {object} model The model object
* @param {string} user The user object name
* @returns {object} The model instance (initialized)
* @returns {Promise} Resolves or not
* @memberof Disk
*/
addModel(model, user) {
let obj = Object.assign({}, model);
let isUser = false;

if (obj.identity === user) {
obj = this.overload(obj, user);
this.user = obj.globalId;
isUser = true;
}

obj.schema._id = {
autoMigrations: { autoIncrement: true },
type: 'number',
};
obj.primaryKey = '_id';

obj.attributes = model.schema;
obj.datastore = this.name;

delete obj.schema;
delete obj.graphql;
async start() {
debug('starting %s', this.name);

const instance = Waterline.Model.extend(obj);
const dataPath = path.join(
os.tmpdir(),
`henri-mongo-${md5(process.cwd())}`
);

this.waterline.registerModel(instance);
if (isUser) {
this.user = obj.globalId;
if (!fs.existsSync(dataPath)) {
fs.mkdirSync(dataPath);
}
this.models[obj.globalId] = instance;

return this.models[obj.globalId];
}

/**
* Overload the user entity
*
* @param {any} model Current model
* @returns {object} the model
* @memberof Disk
*/
overload(model) {
this.henri.pen.info('disk', `user model`, model.globalId, `overloading...`);

model.schema.email = { required: true, type: 'string' };
model.schema.password = { required: true, type: 'string' };

model.beforeCreate = async (values, cb) => {
values.password = await this.henri.user.encrypt(values.password);
cb();
};
model.beforeUpdate = async (values, cb) => {
if (values.hasOwnProperty('password')) {
values.password = await this.henri.user.encrypt(values.password);
}
cb();
};
model.hasRole = async function(roles = []) {
let given = Array.isArray(roles) ? roles : [roles];

return given.every(element => this.roles.includes(element));
};

return model;
}

/**
* Returns the models of this store
*
* @returns {object} the models
* @memberof Disk
*/
getModels() {
return this.models || {};
}

/**
* Returns the session connector (for connect styles session storage)
*
* @param {function} session session-store function
* @returns {object} a store
* @memberof Disk
*/
getSessionConnector(session) {
// eslint-disable-next-line global-require
const NedbStore = require('nedb-session-store')(session);

return new NedbStore({
filename: this.sessionPath,
this.mongod = new MongoMemoryServer({
instance: {
dbName: 'henri',
dbPath: dataPath,
storageEngine: this.henri.isTest ? 'ephemeralForTest' : 'wiredTiger',
},
});
}

/**
* Start the store
*
* @returns {Promise} Resolves or not
* @memberof Disk
*/
async start() {
return new Promise(resolve => {
var config = {
adapters: {
disk: disk,
},

datastores: {
[this.name]: {
adapter: 'disk',
},
},
};
this.config.url = await this.mongod.getConnectionString();

this.waterline.initialize(config, (err, orm) => {
if (err) {
throw err;
}
this.instance = orm;
for (let name in this.models) {
if (typeof this.models[name] !== 'undefined') {
if (name === this.user) {
this.henri._user = orm.collections[name.toLowerCase()];
}
global[name] = orm.collections[name.toLowerCase()];
}
}
resolve();
});
});
return super.start();
}

/**
Expand All @@ -178,20 +73,11 @@ class Disk {
* @memberof Disk
*/
async stop() {
return new Promise(resolve => {
this.waterline.teardown(err => {
if (err) {
this.henri.pen.error(
'disk',
'something went wrong while stopping the orm',
err
);
debug('stopping %s', this.name);

return resolve(err);
}
setTimeout(() => resolve(), 250);
});
});
await super.stop();

await this.mongod.stop();
}
}

Expand Down
7 changes: 4 additions & 3 deletions packages/disk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
"author": "Felix-Antoine Paradis",
"license": "MIT",
"dependencies": {
"nedb-session-store": "^1.1.2",
"sails-disk": "^1.1.2",
"waterline": "^0.13.6"
"@usehenri/mongoose": "^0.37.0",
"debug": "^4.1.1",
"md5": "^2.2.1",
"mongodb-memory-server": "^6.0.1"
}
}
90 changes: 45 additions & 45 deletions packages/disk/types.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,51 @@
/* eslint-disable sort-keys */
const types = require('mongoose').SchemaTypes;

module.exports = {
// Mongoose specific
Array: 'json',
Bool: 'boolean',
Boolean: 'boolean',
Buffer: 'string',
Date: 'number',
Decimal128: 'string',
DocumentArray: 'json',
Embedded: 'ref',
Mixed: 'string',
Number: 'number',
Object: 'json',
ObjectId: 'ref',
String: 'string',
String: types.String,
Number: types.Number,
Boolean: types.Boolean,
DocumentArray: types.DocumentArray,
Embedded: types.Embedded,
Array: types.Array,
Buffer: types.Buffer,
Date: types.Date,
ObjectId: types.ObjectId,
Mixed: types.Mixed,
Decimal128: types.Decimal128,
Object: types.Mixed,
Bool: types.Boolean,

// Sequelize specific
STRING: 'string',
CHAR: 'string',
TEXT: 'string',
TINYINT: 'number',
SMALLINT: 'number',
MEDIUMINT: 'number',
INTEGER: 'number',
BIGINT: 'number',
NUMBER: 'number',
FLOAT: 'number',
DOUBLE: 'number',
DECIMAL: 'number',
REAL: 'number',
BOOLEAN: 'boolean',
BLOB: 'string',
ENUM: 'json',
DATE: 'number',
DATEONLY: 'number',
TIME: 'number',
NOW: 'number',
UUID: 'string',
UUIDV1: 'string',
UUIDV4: 'string',
HSTORE: 'string',
JSON: 'json',
JSONB: 'json',
ARRAY: 'json',
RANGE: 'string',
GEOMETRY: 'string',
GEOGRAPHY: 'string',
VIRTUAL: 'json',
STRING: types.String,
CHAR: types.String,
TEXT: types.String,
TINYINT: types.Number,
SMALLINT: types.Number,
MEDIUMINT: types.Number,
INTEGER: types.Number,
BIGINT: types.Number,
NUMBER: types.Number,
FLOAT: types.Number,
DOUBLE: types.Number,
DECIMAL: types.Number,
REAL: types.Number,
BOOLEAN: types.Boolean,
BLOB: types.String,
ENUM: types.Array,
DATE: types.Date,
DATEONLY: types.Date,
TIME: types.Date,
NOW: types.Date,
UUID: types.ObjectId,
UUIDV1: types.ObjectId,
UUIDV4: types.ObjectId,
HSTORE: types.Mixed,
JSON: types.Mixed,
JSONB: types.Mixed,
ARRAY: types.Array,
RANGE: types.mixed,
GEOMETRY: types.Mixed,
GEOGRAPHY: types.Mixed,
VIRTUAL: types.Mixed,
};

0 comments on commit a060760

Please sign in to comment.