commit 069fb113e8e2224af05c7123423167513ea3f318
parent e68e95084da05221f4a04775655ddedb75a265f8
Author: finwo <finwo@pm.me>
Date: Mon, 5 Nov 2018 13:33:28 +0100
Using adapters as plugins now
Diffstat:
17 files changed, 264 insertions(+), 326 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,5 +1,6 @@
# Created by https://www.gitignore.io/api/osx,linux,windows,node
+# Edit at https://www.gitignore.io/?templates=osx,linux,windows,node
### Linux ###
*~
@@ -91,6 +92,9 @@ typings/
# Serverless directories
.serverless
+# FuseBox cache
+.fusebox/
+
### OSX ###
# General
.DS_Store
@@ -144,7 +148,7 @@ $RECYCLE.BIN/
# Windows shortcuts
*.lnk
-
# End of https://www.gitignore.io/api/osx,linux,windows,node
.idea/
.gtm/
+/data/
diff --git a/src/adapter.js b/src/adapter.js
@@ -0,0 +1,122 @@
+// Our own dependencies
+const hasModule = require('./has-module'),
+ toAbsolutePath = require('./to-absolute-path');
+
+// Adapter descriptors
+let adapters = [
+ {
+ name : ['ram', 'mem', 'memory'],
+ backend : 'memdown',
+ dependencies: ['levelup', 'memdown'],
+ options : {
+ path: 'omit'
+ }
+ }, {
+ name : ['dir', 'leveldb', 'level'],
+ backend : 'leveldown',
+ dependencies: ['levelup', 'leveldown'],
+ options : {
+ path: 'absolute'
+ }
+ }, {
+ name : ['mongo', 'mongodb'],
+ backend : 'mongodown',
+ dependencies: ['levelup', 'mongodown'],
+ options : {
+ path: {protocol: 'mongodb:'}
+ }
+ }, {
+ name : ['mssql'],
+ backend : 'sqldown',
+ dependencies: ['levelup', 'sqldown', 'knex', 'mssql']
+ }, {
+ name : ['mysql'],
+ backend : 'sqldown',
+ dependencies: ['levelup', 'sqldown', 'knex', 'mysql']
+ }, {
+ name : ['mysql2'],
+ backend : 'sqldown',
+ dependencies: ['levelup', 'sqldown', 'knex', 'mysql2']
+ }, {
+ name : ['sqlite', 'sqlite3'],
+ backend : 'sqldown',
+ dependencies: ['levelup', 'sqldown', 'knex', 'sqlite3']
+ }, {
+ name : ['pg', 'postgres', 'postgresql'],
+ backend : 'sqldown',
+ dependencies: ['levelup', 'sqldown', 'knex', 'pg', 'pg-query-stream']
+ }
+];
+
+function wrapper(descriptor) {
+
+ // Sanity checks
+ if (!descriptor) return;
+ if (!descriptor.name) return;
+ if (!descriptor.backend) return;
+ if (!descriptor.dependencies) return;
+ if (!descriptor.options) descriptor.options = {};
+
+ // Normalize the names
+ if ('string' === typeof descriptor.name) {
+ descriptor.name = [descriptor.name];
+ }
+ if (!Array.isArray(descriptor.name)) {
+ return;
+ }
+
+ // Missing dependency notifier
+ for (let dependency of descriptor.dependencies) {
+ if (!hasModule(dependency)) {
+ descriptor.backend = function () {
+ throw new Error("Missing dependency: " + dependency);
+ };
+ break;
+ }
+ }
+
+ // Let's build the actual wrapper
+ switch (typeof descriptor.options.path) {
+ case 'string':
+ switch (descriptor.options.path) {
+ case 'omit':
+ descriptor.wrapper = function (parsedLocation, options, callback) {
+ let backend = 'function' === typeof descriptor.backend ? descriptor.backend : require(descriptor.backend);
+ return require('levelup')(backend(), options, callback);
+ };
+ break;
+ case 'absolute':
+ descriptor.wrapper = function (parsedLocation, options, callback) {
+ let backend = 'function' === typeof descriptor.backend ? descriptor.backend : require(descriptor.backend);
+ return require('levelup')(backend(toAbsolutePath(parsedLocation)), options, callback);
+ };
+ break;
+ }
+ break;
+ case 'object':
+ descriptor.wrapper = function (parsedLocation, options, callback) {
+ let backend = 'function' === typeof descriptor.backend ? descriptor.backend : require(descriptor.backend);
+ Object.assign(parsedLocation, descriptor.options.path);
+ return require('levelup')(backend(parsedLocation.toString()), options, callback);
+ };
+ break;
+ default:
+ descriptor.wrapper = function (parsedLocation, options, callback) {
+ let backend = 'function' === typeof descriptor.backend ? descriptor.backend : require(descriptor.backend);
+ return require('levelup')(backend(parsedLocation.href || parsedLocation), options, callback);
+ };
+ break;
+ }
+
+ return descriptor;
+}
+
+module.exports = function (autolevel) {
+ for (let adapter of adapters) {
+ wrapper(adapter, autolevel);
+ if (!adapter.wrapper) continue;
+ for (let name of adapter.name) {
+ autolevel[name] = adapter.wrapper;
+ }
+ }
+};
diff --git a/src/has-module.js b/src/has-module.js
@@ -0,0 +1,15 @@
+module.exports = function hasModule( name ) {
+ if ( require.resolve ) {
+ try {
+ return !!require.resolve(name);
+ } catch(e) {
+ return false;
+ }
+ }
+ try {
+ require(name);
+ return true;
+ } catch(e) {
+ return false;
+ }
+};
diff --git a/src/has-module.test.js b/src/has-module.test.js
@@ -0,0 +1,41 @@
+import expect from 'expect';
+
+// Setup environment
+if ( 'object' !== typeof process ) process = {};
+if (!process.env) process.env = {};
+process.env.TEST = true;
+
+// Load extends
+expect.extend(require('jest-isa'));
+
+// Load our module
+let hasModule = require('./has-module');
+
+test('Ensure hasModule is a function', async () => {
+ expect(hasModule).toBeDefined(); // Basics
+ expect(hasModule).isA(Function);
+});
+
+test('Verify some packages', async () => {
+ expect(hasModule('./has-module')).toBe(true);
+ expect(hasModule('./index')).toBe(true);
+ expect(hasModule('jest-isa')).toBe(true);
+ expect(hasModule('non-existent-module')).toBe(false);
+ expect(hasModule('pizza-courier')).toBe(false);
+});
+
+// TODO: fix this one
+// test('Verify some packages without require.resolve', async () => {
+//
+// // Reload module
+// process.env.NO_RESOLVE = true;
+// console.log(process.env);
+// delete require.cache[require.resolve('./has-module')];
+// hasModule = require('./has-module');
+//
+// expect(hasModule('./has-module')).toBe(true);
+// expect(hasModule('./index')).toBe(true);
+// expect(hasModule('jest-isa')).toBe(true);
+// expect(hasModule('non-existent-module')).toBe(false);
+// expect(hasModule('pizza-courier')).toBe(false);
+// });
diff --git a/src/index.js b/src/index.js
@@ -1,22 +1,5 @@
let parseUrl = require('url-parse');
-// Check if a module is available
-function hasModule( name ) {
- if ( require.resolve ) {
- try {
- return !!require.resolve(name);
- } catch(e) {
- return false;
- }
- }
- try {
- require(name);
- return true;
- } catch(e) {
- return false;
- }
-}
-
/**
* Initializes a new levelup instance, based on the given options
*
@@ -40,11 +23,8 @@ module.exports = function autolevel( location, options, callback ) {
) || 'mem://';
}
- // Load protocol initializers
- require('./mem' )(hasModule, autolevel);
- require('./mongodb')(hasModule, autolevel);
- require('./plain' )(hasModule, autolevel);
- require('./sql' )(hasModule, autolevel);
+ // Let the adapters register themselves
+ require('./adapter')(autolevel);
// Parse the given location, it should contain a protocol
let parsedLocation = parseUrl(location),
diff --git a/src/index.test.js b/src/index.test.js
@@ -1,9 +1,12 @@
import expect from 'expect';
+import rimraf from 'rimraf';
+import absolutePath from './to-absolute-path';
// Setup environment
-if ( 'object' !== typeof process ) process = {};
+if ('object' !== typeof process) process = {};
if (!process.env) process.env = {};
process.env.TEST = true;
+let noop = ()=>{};
// Load extends
expect.extend(require('jest-isa'));
@@ -33,50 +36,30 @@ test('Ensure autolevel loads', async () => {
expect(instance).isA(Object);
});
-// test('kv.genId', async () => {
-// const known = [];
-// for (let i = 0; i < 1e3; i++) {
-// let generated = await kv.genId(known);
-// expect(generated).isA(String);
-// expect(known.indexOf(generated)).toBe(known.lastIndexOf(generated));
-// }
-// for (let i = 0; i < 1e3; i++) {
-// let generated = await kv.genId(known,()=>''+Math.round(Math.random()));
-// expect(generated).isA(String);
-// expect(known.indexOf(generated)).toBe(known.lastIndexOf(generated));
-// }
-// for( let entry of testData ) {
-// let generated = await kv.genId(known);
-// testId.set( entry, generated );
-// expect(generated).isA(String);
-// expect(known.indexOf(generated)).toBe(known.lastIndexOf(generated));
-// }
-// });
-//
-// test('kv.put', async () => {
-// for ( let entry of testData ) {
-// expect(await kv.put( testId.get(entry), entry )).toBeUndefined();
-// }
-// });
-//
-// test('kv.get', async () => {
-// for ( let expected of testData ) {
-// expect(await kv.get( testId.get(expected), opts )).toBe(expected);
-// }
-// });
-//
-// test('kv.find', async () => {
-// let result;
-//
-// // Search for foobar
-// result = await kv.find({foo:'bar'}, opts);
-// expect(result.length).toBe(1);
-// expect(result[0].key).toBe( testId.get(testData[0]) );
-// expect(result[0].value).toBe( testData[0] );
-//
-// // Search for 30cm BBQ Meat Lovers
-// result = await kv.find({size:'>25'}, opts);
-// expect(result.length).toBe(1);
-// expect(result[0].key).toBe( testId.get(testData[3]) );
-// expect(result[0].value).toBe( testData[3] );
-// });
+test('Verify memory adapter', async () => {
+ expect(autolevel).toBeDefined(); // Basics
+ expect(autolevel).isA(Function);
+ let instance = autolevel('mem://');
+ expect(await instance.put('key', 'value')).toBeUndefined();
+ expect(await instance.get('key')).isA(Buffer);
+ expect(await instance.get('key', {asBuffer: false})).toBe('value');
+});
+
+test('Verify plain adapter', async () => {
+ expect(autolevel).toBeDefined(); // Basics
+ expect(autolevel).isA(Function);
+ let instance = autolevel('dir://data/');
+ expect(await instance.put('key', 'value')).toBeUndefined();
+ expect(await instance.get('key')).isA(Buffer);
+ expect(await instance.get('key', {asBuffer: false})).toBe('value');
+ expect(await instance.close()).toBeUndefined();
+ rimraf(absolutePath('dir://data'), noop);
+});
+
+/* TODO:
+ * mssql
+ * mysql
+ * mysql2
+ * mongodb
+ * sqlite
+ */
diff --git a/src/mem.js b/src/mem.js
@@ -1,21 +0,0 @@
-module.exports = function( hasModule, supported ) {
-
- // No levelup = can't use this
- if (!hasModule('levelup')) {
- return;
- }
-
- // No memdown = can't use this
- if (!hasModule('memdown')) {
- return;
- }
-
- // Our initializer
- supported.mem = function (parsedLocation, options, callback) {
- return require('levelup')(require('memdown')(), options, callback);
- };
-
- // Aliases
- supported.memory = supported.mem;
- supported.ram = supported.mem;
-};
diff --git a/src/mem.test.js b/src/mem.test.js
@@ -1,35 +0,0 @@
-import expect from 'expect';
-
-// Setup environment
-if ( 'object' !== typeof process ) process = {};
-if (!process.env) process.env = {};
-process.env.TEST = true;
-
-// Load extends
-expect.extend(require('jest-isa'));
-
-// Load our module
-let autolevel = require('./index'),
- adapter = false;
-
-test('Ensure autolevel loads', async () => {
- expect(autolevel).toBeDefined(); // Basics
- expect(autolevel).isA(Function);
- let instance = autolevel();
- expect(instance).isA(Object);
-});
-
-test('Verify adapter loads', async () => {
- adapter = autolevel('mem://');
- expect(adapter).isA(Object);
-});
-
-test('Storing data', async () => {
- let result = await adapter.put('key','value');
- expect(result).toBeUndefined();
-});
-
-test('Fetching data', async () => {
- let result = await adapter.get('key', { asBuffer: false });
- expect(result).toBe('value');
-});
diff --git a/src/mongodb.js b/src/mongodb.js
@@ -1,20 +0,0 @@
-module.exports = function( hasModule, supported ) {
-
- // No levelup = can't use this
- if (!hasModule('levelup')) {
- return;
- }
-
- // No mongodown = can't use this
- if (!hasModule('mongodown')) {
- return;
- }
-
- // Our initializer
- supported.mongo = function (parsedLocation, options, callback) {
- return require('levelup')( require('mongodown')(parsedLocation.href || parsedLocation), options, callback );
- };
-
- // Aliases
- supported.mongodb = supported.mongo;
-};
diff --git a/src/mongodb.test.js b/src/mongodb.test.js
@@ -1,39 +0,0 @@
-import expect from 'expect';
-import rimraf from 'rimraf';
-
-// Setup environment
-if ( 'object' !== typeof process ) process = {};
-if (!process.env) process.env = {};
-process.env.TEST = true;
-
-// Load extends
-expect.extend(require('jest-isa'));
-
-// Load our module
-let autolevel = require('./index'),
- adapter = false;
-
-test('Ensure autolevel loads', async () => {
- expect(autolevel).toBeDefined(); // Basics
- expect(autolevel).isA(Function);
- let instance = autolevel();
- expect(instance).isA(Object);
-});
-
-test('Verify adapter loads', async () => {
- // TODO
- // adapter = autolevel('dir://');
- // expect(adapter).isA(Object);
-});
-
-test('Storing data', async () => {
- // TODO
- // let result = await adapter.put('key','value');
- // expect(result).toBeUndefined();
-});
-
-test('Fetching data', async () => {
- // TODO
- // let result = await adapter.get('key', { asBuffer: false });
- // expect(result).toBe('value');
-});
diff --git a/src/plain.js b/src/plain.js
@@ -1,35 +0,0 @@
-const path = require('path');
-
-module.exports = function (hasModule, supported) {
- const basedir = global.approot || (''+require('app-root-path'));
-
- // Turn the parsed location into an absolute path
- function toAbsolutePath( parsedLocation ) {
- if ( 'string' === typeof parsedLocation ) {
- parsedLocation = require('url-parse')(parsedLocation);
- }
- let targetLocation = parsedLocation.hostname + parsedLocation.pathname;
- if ( targetLocation.substr(0,1) !== '/' ) {
- targetLocation = basedir + '/' + targetLocation;
- }
- return targetLocation.replace(/\//g,path.sep);
- }
-
- if (hasModule('level')) {
- // Directly use level
- supported.dir = function (parsedLocation, options, callback) {
- return require('level')(toAbsolutePath(parsedLocation), options, callback);
- };
- } else if (hasModule('levelup') && hasModule('leveldown')) {
- // Combine levelup + leveldown
- supported.dir = function (parsedLocation, options, callback) {
- return require('levelup')(require('leveldown')(toAbsolutePath(parsedLocation), options, callback));
- };
- } else {
- // Can't load this
- return false;
- }
-
- // Aliases
- supported.leveldb = supported.dir;
-};
diff --git a/src/plain.test.js b/src/plain.test.js
@@ -1,39 +0,0 @@
-import expect from 'expect';
-import rimraf from 'rimraf';
-
-// Setup environment
-if ( 'object' !== typeof process ) process = {};
-if (!process.env) process.env = {};
-process.env.TEST = true;
-
-// Load extends
-expect.extend(require('jest-isa'));
-
-// Load our module
-let autolevel = require('./index'),
- adapter = false;
-
-test('Ensure autolevel loads', async () => {
- expect(autolevel).toBeDefined(); // Basics
- expect(autolevel).isA(Function);
- let instance = autolevel();
- expect(instance).isA(Object);
-});
-
-test('Verify adapter loads', async () => {
- // TODO
- // adapter = autolevel('dir://');
- // expect(adapter).isA(Object);
-});
-
-test('Storing data', async () => {
- // TODO
- // let result = await adapter.put('key','value');
- // expect(result).toBeUndefined();
-});
-
-test('Fetching data', async () => {
- // TODO
- // let result = await adapter.get('key', { asBuffer: false });
- // expect(result).toBe('value');
-});
diff --git a/src/sql.js b/src/sql.js
@@ -1,27 +0,0 @@
-module.exports = function( hasModule, supported ) {
-
- // No levelup = can't use this
- if (!hasModule('levelup')) {
- return;
- }
-
- // No memdown = can't use this
- if (!hasModule('sqldown')) {
- return;
- }
-
- // Our initializer
- supported.sql = function(parsedLocation, options, callback) {
- return require('levelup')(require('sqldown')(parsedLocation.href), options, callback);
- };
-
- // Aliases
- supported.mssql = supported.sql;
- supported.mysql = supported.sql;
- supported.mysql2 = supported.sql;
- supported.sqlite = supported.sql;
- supported.sqlite3 = supported.sql;
- supported.postgres = supported.sql;
- supported.postgresql = supported.sql;
- supported.pg = supported.sql;
-};
diff --git a/src/sql.test.js b/src/sql.test.js
@@ -1,39 +0,0 @@
-import expect from 'expect';
-import rimraf from 'rimraf';
-
-// Setup environment
-if ( 'object' !== typeof process ) process = {};
-if (!process.env) process.env = {};
-process.env.TEST = true;
-
-// Load extends
-expect.extend(require('jest-isa'));
-
-// Load our module
-let autolevel = require('./index'),
- adapter = false;
-
-test('Ensure autolevel loads', async () => {
- expect(autolevel).toBeDefined(); // Basics
- expect(autolevel).isA(Function);
- let instance = autolevel();
- expect(instance).isA(Object);
-});
-
-test('Verify adapter loads', async () => {
- // TODO
- // adapter = autolevel('dir://');
- // expect(adapter).isA(Object);
-});
-
-test('Storing data', async () => {
- // TODO
- // let result = await adapter.put('key','value');
- // expect(result).toBeUndefined();
-});
-
-test('Fetching data', async () => {
- // TODO
- // let result = await adapter.get('key', { asBuffer: false });
- // expect(result).toBe('value');
-});
diff --git a/src/to-absolute-path.js b/src/to-absolute-path.js
@@ -0,0 +1,13 @@
+const basedir = global.approot || (''+require('app-root-path')),
+ path = require('path');
+
+module.exports = function toAbsolutePath( parsedLocation ) {
+ if ( 'string' === typeof parsedLocation ) {
+ parsedLocation = require('url-parse')(parsedLocation);
+ }
+ let targetLocation = parsedLocation.hostname + parsedLocation.pathname;
+ if ( targetLocation.substr(0,1) !== '/' ) {
+ targetLocation = basedir + '/' + targetLocation;
+ }
+ return targetLocation.replace(/\//g,path.sep);
+};
diff --git a/src/to-absolute-path.test.js b/src/to-absolute-path.test.js
@@ -0,0 +1,34 @@
+import expect from 'expect';
+
+// Setup environment
+if ( 'object' !== typeof process ) process = {};
+if (!process.env) process.env = {};
+process.env.TEST = true;
+const approot = '' + require('app-root-path'),
+ path = require('path'),
+ urlParse = require('url-parse');
+
+// Load extends
+expect.extend(require('jest-isa'));
+
+// Load our module
+let toAbsolutePath = require('./to-absolute-path');
+
+test('Ensure toAbsolutePath is a function', async () => {
+ expect(toAbsolutePath).toBeDefined(); // Basics
+ expect(toAbsolutePath).isA(Function);
+});
+
+test('Test some paths', async () => {
+ let cases = [
+ ['file:///tmp', '/tmp'],
+ ['/tmp', '/tmp'],
+ ['file://tmp', approot + path.sep + 'tmp'],
+ ['tmp', approot + path.sep + 'tmp'],
+ ];
+
+ for ( let testCase of cases ) {
+ expect(toAbsolutePath(testCase[0])).toBe(testCase[1]);
+ expect(toAbsolutePath(urlParse(testCase[0]))).toBe(testCase[1]);
+ }
+});
diff --git a/update.sh b/update.sh
@@ -7,6 +7,7 @@ echo "Updating .gitignore"
curl -s https://www.gitignore.io/api/osx,linux,windows,node > .gitignore
echo ".idea/" >> .gitignore
echo ".gtm/" >> .gitignore
+echo "/data/" >> .gitignore
#echo "Updating modules"
#git submodule foreach --recursive git clean -xfd &>/dev/null