diff --git a/.gitignore b/.gitignore index bd3d81c..825fe29 100755 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,7 @@ ssl *~ .idea nbproject -npm-debug.log \ No newline at end of file +npm-debug.log + +# Waterline +sailssqlite.db \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..60d2792 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +MOCHA_OPTS= --check-leaks +REPORTER = spec + +test: test-unit test-integration + +test-unit: + @NODE_ENV=test ./node_modules/.bin/mocha \ + --reporter $(REPORTER) \ + $(MOCHA_OPTS) \ + test/unit/** + +test-integration: + @NODE_ENV=test node test/integration/runner.js + +test-load: + @NODE_ENV=test ./node_modules/.bin/mocha \ + --reporter $(REPORTER) \ + $(MOCHA_OPTS) \ + test/load/** \ No newline at end of file diff --git a/README.md b/README.md index 7c6e466..f24d22d 100755 --- a/README.md +++ b/README.md @@ -1,30 +1,63 @@ -![image_squidhome@2x.png](http://i.imgur.com/RIvu9.png) +![image_squidhome@2x.png](http://i.imgur.com/RIvu9.png) -# BoilerplateAdapter +# SQLite3 Sails/Waterline Adapter -This template exists to make it easier for you to get started writing an official adapter for Sails.js. +A [Waterline](https://github.com/balderdashy/waterline) adapter for SQLite3. May be used in a [Sails](https://github.com/balderdashy/sails) app or anything using Waterline for the ORM. +## Disclaimers +- SQLite3 adapter is not optimized for performance. Native joins are not implemented (among other issues). +- This codebase contains no unit tests, though all integration tests with the waterline api (v1) pass. (See below for supported interfaces) + +#### People who can use this package as is: +Those prototyping apps with sailsjs 1.x and looking to use sqlite for a test database. + +For anyone looking to use this adapter in production, contributions welcome! + ## Getting started -It's usually pretty easy to add your own adapters for integrating with proprietary systems or existing open APIs. For most things, it's as easy as `require('some-module')` and mapping the appropriate methods to match waterline semantics. To get started: +To use this in your sails app, install using: + +> npm install --save sails-sqlite3 + +In your `config\datastores.js` file, add a property with your datastore name. Supported configuration: + +```js +default: { + adapter: 'sails-sqlite3', + filename: '[YOUR DATABASE].db', + mode: sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, + verbose: false +} +``` + +For more information on the `mode` configuration property, see the [driver documentation](https://github.com/mapbox/node-sqlite3/wiki/API#new-sqlite3databasefilename-mode-callback). + +#### Example with Modes +To use different database modes, import the `sqlite3` module, which is a dependency of this pacakge: + +```js +const sqlite3 = require('sqlite3'); + +const config = { + filename: 'testdb.db', + mode: sqlite3.OPEN_READONLY, + verbose: true +}; +``` + +## Testing -1. Fork this repository -2. Set up your README and package.json file. Sails.js adapter module names are of the form sails-*, where * is the name of the datastore or service you're integrating with. -3. Build your adapter. +> npm test -## How to test your adapter -1. Run `npm link` in this adapter's directory -2. Clone the sails.js core and modify the tests to use your new adapter. -3. Run `npm link sails-boilerplate` -4. From the sails.js core directory, run `npm test`. +Currently only `waterline-adapter-tests` are hooked up. Passing interfaces: -## Submitting your adapter -1. Do a pull request to this repository (make sure you attribute yourself as the author set the license in the package.json to "MIT") Please let us know about any special instructions for usage/testing. -2. We'll run the tests one last time. If there are any issues, we'll let you know. -3. When it's ready, we'll update the documentation with information about your new adapter -4. Then we'll tweet and post about it on our blog, adoring you with lavish praises. -5. Mike will send you jelly beans. +- semantic +- queryable +- associations +- migratable +## Acknowledgements +This is a rewrite from a fork of the sails-sqlite3 adapter written for sailsjs < 1.0.0 originally by [Andrew Jo](https://github.com/AndrewJo). I borrowed most of the structure of the code and a lot of the sql querying from the original codebase. ## About Sails.js and Waterline http://SailsJs.com diff --git a/index.js b/index.js deleted file mode 100644 index 32ed3d4..0000000 --- a/index.js +++ /dev/null @@ -1,223 +0,0 @@ -/*--------------------------------------------------------------- - :: sails-boilerplate - -> adapter ----------------------------------------------------------------*/ - -var async = require('async'); - -var adapter = module.exports = { - - // Set to true if this adapter supports (or requires) things like data types, validations, keys, etc. - // If true, the schema for models using this adapter will be automatically synced when the server starts. - // Not terribly relevant if not using a non-SQL / non-schema-ed data store - syncable: false, - - // Including a commitLog config enables transactions in this adapter - // Please note that these are not ACID-compliant transactions: - // They guarantee *ISOLATION*, and use a configurable persistent store, so they are *DURABLE* in the face of server crashes. - // However there is no scheduled task that rebuild state from a mid-step commit log at server start, so they're not CONSISTENT yet. - // and there is still lots of work to do as far as making them ATOMIC (they're not undoable right now) - // - // However, for the immediate future, they do a great job of preventing race conditions, and are - // better than a naive solution. They add the most value in findOrCreate() and createEach(). - // - // commitLog: { - // identity: '__default_mongo_transaction', - // adapter: 'sails-mongo' - // }, - - // Default configuration for collections - // (same effect as if these properties were included at the top level of the model definitions) - defaults: { - - // For example: - // port: 3306, - // host: 'localhost' - - // If setting syncable, you should consider the migrate option, - // which allows you to set how the sync will be performed. - // It can be overridden globally in an app (config/adapters.js) and on a per-model basis. - // - // drop => Drop schema and data, then recreate it - // alter => Drop/add columns as necessary, but try - // safe => Don't change anything (good for production DBs) - migrate: 'alter' - }, - - // This method runs when a model is initially registered at server start time - registerCollection: function(collection, cb) { - - cb(); - }, - - - // The following methods are optional - //////////////////////////////////////////////////////////// - - // Optional hook fired when a model is unregistered, typically at server halt - // useful for tearing down remaining open connections, etc. - teardown: function(cb) { - cb(); - }, - - - // REQUIRED method if integrating with a schemaful database - define: function(collectionName, definition, cb) { - - // Define a new "table" or "collection" schema in the data store - cb(); - }, - // REQUIRED method if integrating with a schemaful database - describe: function(collectionName, cb) { - - // Respond with the schema (attributes) for a collection or table in the data store - var attributes = {}; - cb(null, attributes); - }, - // REQUIRED method if integrating with a schemaful database - drop: function(collectionName, cb) { - // Drop a "table" or "collection" schema from the data store - cb(); - }, - - // Optional override of built-in alter logic - // Can be simulated with describe(), define(), and drop(), - // but will probably be made much more efficient by an override here - // alter: function (collectionName, attributes, cb) { - // Modify the schema of a table or collection in the data store - // cb(); - // }, - - - // REQUIRED method if users expect to call Model.create() or any methods - create: function(collectionName, values, cb) { - // Create a single new model specified by values - - // Respond with error or newly created model instance - cb(null, values); - }, - - // REQUIRED method if users expect to call Model.find(), Model.findAll() or related methods - // You're actually supporting find(), findAll(), and other methods here - // but the core will take care of supporting all the different usages. - // (e.g. if this is a find(), not a findAll(), it will only send back a single model) - find: function(collectionName, options, cb) { - - // ** Filter by criteria in options to generate result set - - // Respond with an error or a *list* of models in result set - cb(null, []); - }, - - // REQUIRED method if users expect to call Model.update() - update: function(collectionName, options, values, cb) { - - // ** Filter by criteria in options to generate result set - - // Then update all model(s) in the result set - - // Respond with error or a *list* of models that were updated - cb(); - }, - - // REQUIRED method if users expect to call Model.destroy() - destroy: function(collectionName, options, cb) { - - // ** Filter by criteria in options to generate result set - - // Destroy all model(s) in the result set - - // Return an error or nothing at all - cb(); - }, - - - - // REQUIRED method if users expect to call Model.stream() - stream: function(collectionName, options, stream) { - // options is a standard criteria/options object (like in find) - - // stream.write() and stream.end() should be called. - // for an example, check out: - // https://github.com/balderdashy/sails-dirty/blob/master/DirtyAdapter.js#L247 - - } - - - - /* - ********************************************** - * Optional overrides - ********************************************** - - // Optional override of built-in batch create logic for increased efficiency - // otherwise, uses create() - createEach: function (collectionName, cb) { cb(); }, - - // Optional override of built-in findOrCreate logic for increased efficiency - // otherwise, uses find() and create() - findOrCreate: function (collectionName, cb) { cb(); }, - - // Optional override of built-in batch findOrCreate logic for increased efficiency - // otherwise, uses findOrCreate() - findOrCreateEach: function (collectionName, cb) { cb(); } - */ - - - /* - ********************************************** - * Custom methods - ********************************************** - - //////////////////////////////////////////////////////////////////////////////////////////////////// - // - // > NOTE: There are a few gotchas here you should be aware of. - // - // + The collectionName argument is always prepended as the first argument. - // This is so you can know which model is requesting the adapter. - // - // + All adapter functions are asynchronous, even the completely custom ones, - // and they must always include a callback as the final argument. - // The first argument of callbacks is always an error object. - // For some core methods, Sails.js will add support for .done()/promise usage. - // - // + - // - //////////////////////////////////////////////////////////////////////////////////////////////////// - - - // Any other methods you include will be available on your models - foo: function (collectionName, cb) { - cb(null,"ok"); - }, - bar: function (collectionName, baz, watson, cb) { - cb("Failure!"); - } - - - // Example success usage: - - Model.foo(function (err, result) { - if (err) console.error(err); - else console.log(result); - - // outputs: ok - }) - - // Example error usage: - - Model.bar(235, {test: 'yes'}, function (err, result){ - if (err) console.error(err); - else console.log(result); - - // outputs: Failure! - }) - - */ - - -}; - -////////////// ////////////////////////////////////////// -////////////// Private Methods ////////////////////////////////////////// -////////////// ////////////////////////////////////////// \ No newline at end of file diff --git a/lib/adapter.js b/lib/adapter.js new file mode 100644 index 0000000..b2e51e6 --- /dev/null +++ b/lib/adapter.js @@ -0,0 +1,1117 @@ +/* eslint-disable prefer-arrow-callback */ +/*--------------------------------------------------------------- + :: sails-sqlite3 + -> adapter + + Code refactored for Sails 1.0.0 release + + Supports Migratable interface, but (as docs on this interface + stipulate) this should only be used for dev. This adapter + does not implement the majority of possibly desired + constraints since the waterline auto-migration is intended + to be light and quick +---------------------------------------------------------------*/ +/** + * Module dependencies + */ + +const fs = require('fs'); + +const _ = require('@sailshq/lodash'); +const sqlite3 = require('sqlite3'); +const Errors = require('waterline-errors').adapter; + +const Query = require('./query'); +const utils = require('./utils'); + + +/** + * Module state + */ + +// Private var to track of all the datastores that use this adapter. In order for your adapter +// to be able to connect to the database, you'll want to expose this var publicly as well. +// (See the `registerDatastore()` method for info on the format of each datastore entry herein.) +// +// > Note that this approach of process global state will be changing in an upcoming version of +// > the Waterline adapter spec (a breaking change). But if you follow the conventions laid out +// > below in this adapter template, future upgrades should be a breeze. +var registeredDatastores = {}; + + +/** + * sails-sqlite3 + * + * Expose the adapater definition. + * + * > Most of the methods below are optional. + * > + * > If you don't need / can't get to every method, just implement + * > what you have time for. The other methods will only fail if + * > you try to call them! + * > + * > For many adapters, this file is all you need. For very complex adapters, you may need more flexiblity. + * > In any case, it's probably a good idea to start with one file and refactor only if necessary. + * > If you do go that route, it's conventional in Node to create a `./lib` directory for your private submodules + * > and `require` them at the top of this file with other dependencies. e.g.: + * > ``` + * > var updateMethod = require('./lib/update'); + * > ``` + * + * @type {Dictionary} + */ +const adapter = { + + + // The identity of this adapter, to be referenced by datastore configurations in a Sails app. + identity: 'sails-sqlite3', + + + // Waterline Adapter API Version + // + // > Note that this is not necessarily tied to the major version release cycle of Sails/Waterline! + // > For example, Sails v1.5.0 might generate apps which use sails-hook-orm@2.3.0, which might + // > include Waterline v0.13.4. And all those things might rely on version 1 of the adapter API. + // > But Waterline v0.13.5 might support version 2 of the adapter API!! And while you can generally + // > trust semantic versioning to predict/understand userland API changes, be aware that the maximum + // > and/or minimum _adapter API version_ supported by Waterline could be incremented between major + // > version releases. When possible, compatibility for past versions of the adapter spec will be + // > maintained; just bear in mind that this is a _separate_ number, different from the NPM package + // > version. sails-hook-orm verifies this adapter API version when loading adapters to ensure + // > compatibility, so you should be able to rely on it to provide a good error message to the Sails + // > applications which use this adapter. + adapterApiVersion: 1, + + + // Default datastore configuration. + defaults: { + // Valid values are filenames, ":memory:" for an anonymous in-memory database and an empty string for an + // anonymous disk-based database. Anonymous databases are not persisted and when closing the database handle, + // their contents are lost. + filename: "", + mode: sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, + verbose: false + }, + + + // ╔═╗═╗ ╦╔═╗╔═╗╔═╗╔═╗ ┌─┐┬─┐┬┬ ┬┌─┐┌┬┐┌─┐ + // ║╣ ╔╩╦╝╠═╝║ ║╚═╗║╣ ├─┘├┬┘│└┐┌┘├─┤ │ ├┤ + // ╚═╝╩ ╚═╩ ╚═╝╚═╝╚═╝ ┴ ┴└─┴ └┘ ┴ ┴ ┴ └─┘ + // ┌┬┐┌─┐┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐┌─┐┌─┐ + // ││├─┤ │ ├─┤└─┐ │ │ │├┬┘├┤ └─┐ + // ─┴┘┴ ┴ ┴ ┴ ┴└─┘ ┴ └─┘┴└─└─┘└─┘ + // This allows outside access to this adapter's internal registry of datastore entries, + // for use in datastore methods like `.leaseConnection()`. + datastores: registeredDatastores, + + + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // ██╗ ██╗███████╗███████╗ ██████╗██╗ ██╗ ██████╗██╗ ███████╗ // + // ██║ ██║██╔════╝██╔════╝██╔════╝╚██╗ ██╔╝██╔════╝██║ ██╔════╝ // + // ██║ ██║█████╗ █████╗ ██║ ╚████╔╝ ██║ ██║ █████╗ // + // ██║ ██║██╔══╝ ██╔══╝ ██║ ╚██╔╝ ██║ ██║ ██╔══╝ // + // ███████╗██║██║ ███████╗╚██████╗ ██║ ╚██████╗███████╗███████╗ // + // ╚══════╝╚═╝╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═════╝╚══════╝╚══════╝ // + // // + // Lifecycle adapter methods: // + // Methods related to setting up and tearing down; registering/un-registering datastores. // + ////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * ╦═╗╔═╗╔═╗╦╔═╗╔╦╗╔═╗╦═╗ ┌┬┐┌─┐┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐┌─┐ + * ╠╦╝║╣ ║ ╦║╚═╗ ║ ║╣ ╠╦╝ ││├─┤ │ ├─┤└─┐ │ │ │├┬┘├┤ + * ╩╚═╚═╝╚═╝╩╚═╝ ╩ ╚═╝╩╚═ ─┴┘┴ ┴ ┴ ┴ ┴└─┘ ┴ └─┘┴└─└─┘ + * Register a new datastore with this adapter. This usually involves creating a new + * connection manager (e.g. MySQL pool or MongoDB client) for the underlying database layer. + * + * > Waterline calls this method once for every datastore that is configured to use this adapter. + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {Dictionary} datastoreConfig Dictionary (plain JavaScript object) of configuration options for this datastore (e.g. host, port, etc.) + * @param {Dictionary} physicalModelsReport Experimental: The physical models using this datastore (keyed by "tableName"-- NOT by `identity`!). This may change in a future release of the adapter spec. + * @property {Dictionary} * [Info about a physical model using this datastore. WARNING: This is in a bit of an unusual format.] + * @property {String} primaryKey [the name of the primary key attribute (NOT the column name-- the attribute name!)] + * @property {Dictionary} definition [the physical-layer report from waterline-schema. NOTE THAT THIS IS NOT A NORMAL MODEL DEF!] + * @property {String} tableName [the model's `tableName` (same as the key this is under, just here for convenience)] + * @property {String} identity [the model's `identity`] + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {Function} done A callback to trigger after successfully registering this datastore, or if an error is encountered. + * @param {Error?} + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + */ + registerDatastore: async function (datastoreConfig, physicalModelsReport, done) { + + // Grab the unique name for this datastore for easy access below. + var datastoreName = datastoreConfig.identity; + + // Some sanity checks: + if (!datastoreName) { + return done(new Error('Consistency violation: A datastore should contain an "identity" property: a special identifier that uniquely identifies it across this app. This should have been provided by Waterline core! If you are seeing this message, there could be a bug in Waterline, or the datastore could have become corrupted by userland code, or other code in this adapter. If you determine that this is a Waterline bug, please report this at https://sailsjs.com/bugs.')); + } + if (registeredDatastores[datastoreName]) { + return done(new Error('Consistency violation: Cannot register datastore: `' + datastoreName + '`, because it is already registered with this adapter! This could be due to an unexpected race condition in userland code (e.g. attempting to initialize Waterline more than once), or it could be due to a bug in this adapter. (If you get stumped, reach out at https://sailsjs.com/support.)')); + } + + let writeClient; + try { + writeClient = await wrapAsyncStatements((cb) => { + if (datastoreConfig.verbose) sqlite3.verbose(); + const writeClient = new sqlite3.Database( + datastoreConfig.filename, + datastoreConfig.mode, + err => { + if (!err) { + //set write client to serialize mode + writeClient.serialize(); + } + + cb(err, writeClient); + } + ); + }); + } catch (err) { + return done(err); + } + + // To maintain the spirit of this repository, this implementation will + // continue to spin up and tear down a connection to the Sqlite db on every + // request. + // TODO: Consider creating the connection and maintaining through the life + // of the sails app. (This would lock it from changes outside sails) + registeredDatastores[datastoreName] = { + config: datastoreConfig, + manager: { + models: physicalModelsReport, //for reference + schema: {}, + foreignKeys: utils.buildForeignKeyMap(physicalModelsReport), + writeClient, + }, + // driver: undefined // << TODO: include driver here (if relevant) + }; + + try { + for (let tableName in physicalModelsReport) { + await wrapAsyncStatements(this.describe.bind(this, datastoreName, tableName)); + } + } catch (err) { + return done(err); + } + + return done(); + }, + + + /** + * ╔╦╗╔═╗╔═╗╦═╗╔╦╗╔═╗╦ ╦╔╗╔ + * ║ ║╣ ╠═╣╠╦╝ ║║║ ║║║║║║║ + * ╩ ╚═╝╩ ╩╩╚══╩╝╚═╝╚╩╝╝╚╝ + * Tear down (un-register) a datastore. + * + * Fired when a datastore is unregistered. Typically called once for + * each relevant datastore when the server is killed, or when Waterline + * is shut down after a series of tests. Useful for destroying the manager + * (i.e. terminating any remaining open connections, etc.). + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {String} datastoreName The unique name (identity) of the datastore to un-register. + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {Function} done Callback + * @param {Error?} + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + */ + teardown: function (datastoreName, done) { + + // Look up the datastore entry (manager/driver/config). + var dsEntry = registeredDatastores[datastoreName]; + + // Sanity check: + if (_.isUndefined(dsEntry)) { + return done(new Error('Consistency violation: Attempting to tear down a datastore (`' + datastoreName + '`) which is not currently registered with this adapter. This is usually due to a race condition in userland code (e.g. attempting to tear down the same ORM instance more than once), or it could be due to a bug in this adapter. (If you get stumped, reach out at https://sailsjs.com/support.)')); + } + + // Close write client + dsEntry.manager.writeClient.close(); + delete registeredDatastores[datastoreName]; + + // Inform Waterline that we're done, and that everything went as expected. + return done(); + + }, + + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // ██████╗ ███╗ ███╗██╗ // + // ██╔══██╗████╗ ████║██║ // + // ██║ ██║██╔████╔██║██║ // + // ██║ ██║██║╚██╔╝██║██║ // + // ██████╔╝██║ ╚═╝ ██║███████╗ // + // ╚═════╝ ╚═╝ ╚═╝╚══════╝ // + // (D)ata (M)anipulation (L)anguage // + // // + // DML adapter methods: // + // Methods related to manipulating records stored in the database. // + ////////////////////////////////////////////////////////////////////////////////////////////////// + + + /** + * ╔═╗╦═╗╔═╗╔═╗╔╦╗╔═╗ + * ║ ╠╦╝║╣ ╠═╣ ║ ║╣ + * ╚═╝╩╚═╚═╝╩ ╩ ╩ ╚═╝ + * Create a new record. + * + * (e.g. add a new row to a SQL table, or a new document to a MongoDB collection.) + * + * > Note that depending on the value of `query.meta.fetch`, + * > you may be expected to return the physical record that was + * > created (a dictionary) as the second argument to the callback. + * > (Otherwise, exclude the 2nd argument or send back `undefined`.) + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {String} datastoreName The name of the datastore to perform the query on. + * @param {Dictionary} query The stage-3 query to perform. + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {Function} done Callback + * @param {Error?} + * @param {Dictionary?} + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + */ + create: async function (datastoreName, query, done) { + + // normalize newRecords property + query.newRecords = [query.newRecord]; + delete query.newRecord; + + try { + const recordList = await wrapAsyncStatements( + adapter.createEach.bind(adapter, datastoreName, query)); + + let record; + if (recordList && recordList.length >>> 0 > 0) { + record = recordList[0]; + } + done(undefined, record); + } catch (err) { + done(err); + } + }, + + + /** + * ╔═╗╦═╗╔═╗╔═╗╔╦╗╔═╗ ╔═╗╔═╗╔═╗╦ ╦ + * ║ ╠╦╝║╣ ╠═╣ ║ ║╣ ║╣ ╠═╣║ ╠═╣ + * ╚═╝╩╚═╚═╝╩ ╩ ╩ ╚═╝ ╚═╝╩ ╩╚═╝╩ ╩ + * Create multiple new records. + * + * > Note that depending on the value of `query.meta.fetch`, + * > you may be expected to return the array of physical records + * > that were created as the second argument to the callback. + * > (Otherwise, exclude the 2nd argument or send back `undefined`.) + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {String} datastoreName The name of the datastore to perform the query on. + * @param {Dictionary} query The stage-3 query to perform. + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {Function} done Callback + * @param {Error?} + * @param {Array?} + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + */ + createEach: async function (datastoreName, query, done) { + let verbose = false; + + // Look up the datastore entry (manager/driver/config). + const dsEntry = registeredDatastores[datastoreName]; + const manager = dsEntry.manager; + + // Sanity check: + if (_.isUndefined(dsEntry)) { + return done(new Error('Consistency violation: Cannot do that with datastore (`' + datastoreName + '`) because no matching datastore entry is registered in this adapter! This is usually due to a race condition (e.g. a lifecycle callback still running after the ORM has been torn down), or it could be due to a bug in this adapter. (If you get stumped, reach out at https://sailsjs.com/support.)')); + } + + try { + const client = manager.writeClient; + const tableName = query.using; + const escapedTable = utils.escapeTable(tableName); + + const attributeSets = utils.mapAllAttributes(query.newRecords, manager.schema[tableName]); + + const columnNames = attributeSets.keys.join(', '); + + const paramValues = attributeSets.paramLists.map((paramList) => { + return `( ${paramList.join(', ')} )`; + }).join(', '); + + // Build query + var insertQuery = `INSERT INTO ${escapedTable} (${columnNames}) values ${paramValues}`; + var selectQuery = `SELECT * FROM ${escapedTable} ORDER BY rowid DESC LIMIT ${query.newRecords.length}`; + + // first insert values + await wrapAsyncStatements( + client.run.bind(client, insertQuery, attributeSets.values)); + + // get the last inserted rows if requested + const model = manager.models[tableName]; + let newRows; + if (query.meta && query.meta.fetch) { + newRows = []; + const queryObj = new Query(tableName, manager.schema[tableName], model); + + await wrapAsyncStatements(client.each.bind(client, selectQuery, (err, row) => { + if (err) throw err; + + newRows.push(queryObj.castRow(row)); + })); + + // resort for the order we were given the records. + // we can guarantee that the first records will be given the + // first available row IDs (even if some were deleted creating gaps), + // so it's as easy as a sort using the primary key as the comparator + let pkName = model.definition[model.primaryKey].columnName; + newRows.sort((lhs, rhs) => { + if (lhs[pkName] < rhs[pkName]) return -1; + if (lhs[pkName] > rhs[pkName]) return 1; + return 0; + }); + } + + done(undefined, newRows); + } catch (err) { + done(err); + } + + }, + + + + /** + * ╦ ╦╔═╗╔╦╗╔═╗╔╦╗╔═╗ + * ║ ║╠═╝ ║║╠═╣ ║ ║╣ + * ╚═╝╩ ═╩╝╩ ╩ ╩ ╚═╝ + * Update matching records. + * + * > Note that depending on the value of `query.meta.fetch`, + * > you may be expected to return the array of physical records + * > that were updated as the second argument to the callback. + * > (Otherwise, exclude the 2nd argument or send back `undefined`.) + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {String} datastoreName The name of the datastore to perform the query on. + * @param {Dictionary} query The stage-3 query to perform. + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {Function} done Callback + * @param {Error?} + * @param {Array?} + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + */ + update: async function (datastoreName, query, done) { + + // Look up the datastore entry (manager/driver/config). + var dsEntry = registeredDatastores[datastoreName]; + + // Sanity check: + if (_.isUndefined(dsEntry)) { + return done(new Error('Consistency violation: Cannot do that with datastore (`' + datastoreName + '`) because no matching datastore entry is registered in this adapter! This is usually due to a race condition (e.g. a lifecycle callback still running after the ORM has been torn down), or it could be due to a bug in this adapter. (If you get stumped, reach out at https://sailsjs.com/support.)')); + } + + try { + const client = dsEntry.manager.writeClient; + const tableName = query.using; + const tableSchema = dsEntry.manager.schema[tableName]; + const model = dsEntry.manager.models[tableName]; + + const _query = new Query(tableName, tableSchema, model); + const updateQuery = _query.update(query.criteria, query.valuesToSet); + + /* TODO: See below note and fix so that we do not query the db twice where unnecessary + * Note: The sqlite driver we're using does not return changed values + * on an update. If we are expected to fetch, we need to deterministically + * be able to fetch the exact records that we updated. + * We cannot simply query off the same criteria because it is possible + * (nay likely) that one of the criteria is based on a field that is + * changed in the update call. In most cases, acquiring the primary key + * value before the update and then re-querying that key after the update + * will be sufficient. However, it is possible to update the primary key + * itself. So we will construct 2 cases: + * 1: Query the primary key for all records that will be updated. Then + * craft a new where object based on only those primary keys to + * query again after the update executes + * 2: craft a new where object based on what the primary key is changing + * to. + * + * Note that option 1 sucks. However, an analysis of the where criteria to + * determine the optimal *post-update* where criteria is more work than + * I have time to do, so option 1 it is. + */ + + let newQuery; + if (query.meta && query.meta.fetch) { + const pkCol = model.definition[model.primaryKey].columnName; + let newWhere = {}; + newQuery = _.cloneDeep(query); + newQuery.criteria = newQuery.criteria || {}; + + if (query.valuesToSet[pkCol]) { + newWhere[pkCol] = query.valuesToSet[pkCol]; + } else { + newQuery.criteria.select = [pkCol]; + + const rows = await wrapAsyncStatements( + adapter.find.bind(adapter, datastoreName, newQuery)); + + delete newQuery.criteria.select; + + const inSet = { in: rows.map(row => row[pkCol]) }; + newWhere[pkCol] = inSet; + } + + newQuery.criteria.where = newWhere; + } + + const statement = await wrapAsyncForThis( + client.run.bind(client, updateQuery.query, updateQuery.values)); + + let results; + if (query.meta && query.meta.fetch) { + if (statement.changes === 0) { + results = []; + } else { + results = await wrapAsyncStatements( + adapter.find.bind(adapter, datastoreName, newQuery)); + } + } + + done(undefined, results); + } catch (err) { + done(err); + } + + }, + + + /** + * ╔╦╗╔═╗╔═╗╔╦╗╦═╗╔═╗╦ ╦ + * ║║║╣ ╚═╗ ║ ╠╦╝║ ║╚╦╝ + * ═╩╝╚═╝╚═╝ ╩ ╩╚═╚═╝ ╩ + * Destroy one or more records. + * + * > Note that depending on the value of `query.meta.fetch`, + * > you may be expected to return the array of physical records + * > that were destroyed as the second argument to the callback. + * > (Otherwise, exclude the 2nd argument or send back `undefined`.) + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {String} datastoreName The name of the datastore to perform the query on. + * @param {Dictionary} query The stage-3 query to perform. + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {Function} done Callback + * @param {Error?} + * @param {Array?} + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + */ + destroy: async function (datastoreName, query, done) { + + // Look up the datastore entry (manager/driver/config). + var dsEntry = registeredDatastores[datastoreName]; + + // Sanity check: + if (_.isUndefined(dsEntry)) { + return done(new Error('Consistency violation: Cannot do that with datastore (`' + datastoreName + '`) because no matching datastore entry is registered in this adapter! This is usually due to a race condition (e.g. a lifecycle callback still running after the ORM has been torn down), or it could be due to a bug in this adapter. (If you get stumped, reach out at https://sailsjs.com/support.)')); + } + + try { + const client = dsEntry.manager.writeClient; + const tableName = query.using; + const tableSchema = dsEntry.manager.schema[tableName]; + const model = dsEntry.manager.models[tableName]; + + const _query = new Query(tableName, tableSchema, model); + const queryObj = _query.destroy(query.criteria); + + let results; + if (query.meta && query.meta.fetch) { + results = await wrapAsyncStatements( + adapter.find.bind(adapter, datastoreName, query)); + } + + await wrapAsyncStatements( + client.run.bind(client, queryObj.query, queryObj.values)); + + done(undefined, results); + } catch (err) { + done(err); + } + + }, + + + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // ██████╗ ██████╗ ██╗ // + // ██╔══██╗██╔═══██╗██║ // + // ██║ ██║██║ ██║██║ // + // ██║ ██║██║▄▄ ██║██║ // + // ██████╔╝╚██████╔╝███████╗ // + // ╚═════╝ ╚══▀▀═╝ ╚══════╝ // + // (D)ata (Q)uery (L)anguage // + // // + // DQL adapter methods: // + // Methods related to fetching information from the database (e.g. finding stored records). // + ////////////////////////////////////////////////////////////////////////////////////////////////// + + + /** + * ╔═╗╦╔╗╔╔╦╗ + * ╠╣ ║║║║ ║║ + * ╚ ╩╝╚╝═╩╝ + * Find matching records. + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {String} datastoreName The name of the datastore to perform the query on. + * @param {Dictionary} query The stage-3 query to perform. + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {Function} done Callback + * @param {Error?} + * @param {Array} [matching physical records] + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + */ + find: async function (datastoreName, query, done) { + + // Look up the datastore entry (manager/driver/config). + var dsEntry = registeredDatastores[datastoreName]; + + // Sanity check: + if (_.isUndefined(dsEntry)) { + return done(new Error('Consistency violation: Cannot do that with datastore (`' + datastoreName + '`) because no matching datastore entry is registered in this adapter! This is usually due to a race condition (e.g. a lifecycle callback still running after the ORM has been torn down), or it could be due to a bug in this adapter. (If you get stumped, reach out at https://sailsjs.com/support.)')); + } + + const tableName = query.using; + const schema = dsEntry.manager.schema[tableName]; + const model = dsEntry.manager.models[tableName]; + const queryObj = new Query(tableName, schema, model); + const queryStatement = queryObj.find(query.criteria); + + try { + await spawnReadonlyConnection(dsEntry, async function __FIND__(client) { + const values = []; + let resultCount = await wrapAsyncStatements( + client.each.bind(client, queryStatement.query, queryStatement.values, (err, row) => { + if (err) throw err; + + values.push(queryObj.castRow(row)); + })); + + done(undefined, values); + }); + } catch (err) { + done(err); + } + }, + + + /** + * ╦╔═╗╦╔╗╔ + * ║║ ║║║║║ + * ╚╝╚═╝╩╝╚╝ + * ┌─ ┌─┐┌─┐┬─┐ ┌┐┌┌─┐┌┬┐┬┬ ┬┌─┐ ┌─┐┌─┐┌─┐┬ ┬┬ ┌─┐┌┬┐┌─┐ ─┐ + * │─── ├┤ │ │├┬┘ │││├─┤ │ │└┐┌┘├┤ ├─┘│ │├─┘│ ││ ├─┤ │ ├┤ ───│ + * └─ └ └─┘┴└─ ┘└┘┴ ┴ ┴ ┴ └┘ └─┘ ┴ └─┘┴ └─┘┴─┘┴ ┴ ┴ └─┘ ─┘ + * Perform a "find" query with one or more native joins. + * + * > NOTE: If you don't want to support native joins (or if your database does not + * > support native joins, e.g. Mongo) remove this method completely! Without this method, + * > Waterline will handle `.populate()` using its built-in join polyfill (aka "polypopulate"), + * > which sends multiple queries to the adapter and joins the results in-memory. + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {String} datastoreName The name of the datastore to perform the query on. + * @param {Dictionary} query The stage-3 query to perform. + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {Function} done Callback + * @param {Error?} + * @param {Array} [matching physical records, populated according to the join instructions] + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + */ + /**************************************** + * NOTE: Intention is to support joins. + * Ignoring for the time being since + * waterline polyfills a join in memory + ***************************************/ + // join: function (datastoreName, query, done) { + + // // Look up the datastore entry (manager/driver/config). + // var dsEntry = registeredDatastores[datastoreName]; + + // // Sanity check: + // if (_.isUndefined(dsEntry)) { + // return done(new Error('Consistency violation: Cannot do that with datastore (`'+datastoreName+'`) because no matching datastore entry is registered in this adapter! This is usually due to a race condition (e.g. a lifecycle callback still running after the ORM has been torn down), or it could be due to a bug in this adapter. (If you get stumped, reach out at https://sailsjs.com/support.)')); + // } + + // // Perform the query and send back a result. + // // + // // > TODO: Replace this setTimeout with real logic that calls + // // > `done()` when finished. (Or remove this method from the + // // > adapter altogether + // setTimeout(function(){ + // return done(new Error('Adapter method (`join`) not implemented yet.')); + // }, 16); + + // }, + + + /** + * ╔═╗╔═╗╦ ╦╔╗╔╔╦╗ + * ║ ║ ║║ ║║║║ ║ + * ╚═╝╚═╝╚═╝╝╚╝ ╩ + * Get the number of matching records. + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {String} datastoreName The name of the datastore to perform the query on. + * @param {Dictionary} query The stage-3 query to perform. + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {Function} done Callback + * @param {Error?} + * @param {Number} [the number of matching records] + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + */ + count: async function (datastoreName, query, done) { + + // Look up the datastore entry (manager/driver/config). + var dsEntry = registeredDatastores[datastoreName]; + + // Sanity check: + if (_.isUndefined(dsEntry)) { + return done(new Error('Consistency violation: Cannot do that with datastore (`' + datastoreName + '`) because no matching datastore entry is registered in this adapter! This is usually due to a race condition (e.g. a lifecycle callback still running after the ORM has been torn down), or it could be due to a bug in this adapter. (If you get stumped, reach out at https://sailsjs.com/support.)')); + } + + try { + const tableName = query.using; + const schema = dsEntry.manager.schema[tableName]; + const model = dsEntry.manager.models[tableName]; + + const countQuery = new Query(tableName, schema, model); + const statement = countQuery.count(query.criteria, 'count_alias'); + + await spawnReadonlyConnection(dsEntry, async function __COUNT__(client) { + const row = await wrapAsyncStatements( + client.get.bind(client, statement.query, statement.values)); + + if (!row) throw new Error('No rows returned by count query?'); + + done(undefined, row.count_alias); + }); + } catch (err) { + done(err); + } + }, + + + /** + * ╔═╗╦ ╦╔╦╗ + * ╚═╗║ ║║║║ + * ╚═╝╚═╝╩ ╩ + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {String} datastoreName The name of the datastore to perform the query on. + * @param {Dictionary} query The stage-3 query to perform. + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {Function} done Callback + * @param {Error?} + * @param {Number} [the sum] + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + */ + sum: async function (datastoreName, query, done) { + + // Look up the datastore entry (manager/driver/config). + var dsEntry = registeredDatastores[datastoreName]; + + // Sanity check: + if (_.isUndefined(dsEntry)) { + return done(new Error('Consistency violation: Cannot do that with datastore (`' + datastoreName + '`) because no matching datastore entry is registered in this adapter! This is usually due to a race condition (e.g. a lifecycle callback still running after the ORM has been torn down), or it could be due to a bug in this adapter. (If you get stumped, reach out at https://sailsjs.com/support.)')); + } + + try { + const tableName = query.using; + const schema = dsEntry.manager.schema[tableName]; + const model = dsEntry.manager.models[tableName]; + + const sumQuery = new Query(tableName, schema, model); + const statement = sumQuery.sum(query.criteria, query.numericAttrName, 'sum_alias'); + + await spawnReadonlyConnection(dsEntry, async function __SUM__(client) { + const row = await wrapAsyncStatements( + client.get.bind(client, statement.query, statement.values)); + + if (!row) throw new Error('No rows returned by sum query?'); + + done(undefined, row.sum_alias); + }); + } catch (err) { + done(err); + } + + }, + + + /** + * ╔═╗╦ ╦╔═╗ + * ╠═╣╚╗╔╝║ ╦ + * ╩ ╩ ╚╝ ╚═╝ + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {String} datastoreName The name of the datastore to perform the query on. + * @param {Dictionary} query The stage-3 query to perform. + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {Function} done Callback + * @param {Error?} + * @param {Number} [the average ("mean")] + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + */ + avg: async function (datastoreName, query, done) { + + // Look up the datastore entry (manager/driver/config). + var dsEntry = registeredDatastores[datastoreName]; + + // Sanity check: + if (_.isUndefined(dsEntry)) { + return done(new Error('Consistency violation: Cannot do that with datastore (`' + datastoreName + '`) because no matching datastore entry is registered in this adapter! This is usually due to a race condition (e.g. a lifecycle callback still running after the ORM has been torn down), or it could be due to a bug in this adapter. (If you get stumped, reach out at https://sailsjs.com/support.)')); + } + + try { + const tableName = query.using; + const schema = dsEntry.manager.schema[tableName]; + const model = dsEntry.manager.models[tableName]; + + const avgQuery = new Query(tableName, schema, model); + const statement = avgQuery.avg(query.criteria, query.numericAttrName, 'avg_alias'); + + await spawnReadonlyConnection(dsEntry, async function __AVG__(client) { + const row = await wrapAsyncStatements( + client.get.bind(client, statement.query, statement.values)); + + if (!row) throw new Error('No rows returned by avg query?'); + + done(undefined, row.avg_alias); + }); + } catch (err) { + done(err); + } + + }, + + + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // ██████╗ ██████╗ ██╗ // + // ██╔══██╗██╔══██╗██║ // + // ██║ ██║██║ ██║██║ // + // ██║ ██║██║ ██║██║ // + // ██████╔╝██████╔╝███████╗ // + // ╚═════╝ ╚═════╝ ╚══════╝ // + // (D)ata (D)efinition (L)anguage // + // // + // DDL adapter methods: // + // Methods related to modifying the underlying structure of physical models in the database. // + ////////////////////////////////////////////////////////////////////////////////////////////////// + + /* ╔╦╗╔═╗╔═╗╔═╗╦═╗╦╔╗ ╔═╗ ┌┬┐┌─┐┌┐ ┬ ┌─┐ + * ║║║╣ ╚═╗║ ╠╦╝║╠╩╗║╣ │ ├─┤├┴┐│ ├┤ + * ═╩╝╚═╝╚═╝╚═╝╩╚═╩╚═╝╚═╝ ┴ ┴ ┴└─┘┴─┘└─┘ + * Describe a table and get back a normalized model schema format. + * (This is used to allow Sails to do auto-migrations) + */ + describe: async function describe(datastoreName, tableName, cb, meta) { + var datastore = registeredDatastores[datastoreName]; + spawnReadonlyConnection(datastore, async function __DESCRIBE__(client) { + // Get a list of all the tables in this database + // See: http://www.sqlite.org/faq.html#q7) + var query = `SELECT * FROM sqlite_master WHERE type="table" AND name="${tableName}" ORDER BY name`; + + try { + const schema = await wrapAsyncStatements(client.get.bind(client, query)); + if (!schema) return Promise.resolve(); + + // Query to get information about each table + // See: http://www.sqlite.org/pragma.html#pragma_table_info + var columnsQuery = `PRAGMA table_info("${schema.name}")`; + + // Query to get a list of indices for a given table + var indexListQuery = `PRAGMA index_list("${schema.name}")`; + + schema.indices = []; + schema.columns = []; + + var index = { columns: [] }; + + // Binding to the each method which takes a function that runs for every + // row returned, then a complete callback function + await wrapAsyncStatements(client.each.bind(client, indexListQuery, (err, currentIndex) => { + if (err) throw err; + // Query to get information about indices + var indexInfoQuery = + `PRAGMA index_info("${currentIndex.name}")`; + + // Retrieve detailed information for given index + client.each(indexInfoQuery, function (err, indexedCol) { + index.columns.push(indexedCol); + }); + + schema.indices.push(currentIndex); + })); + + await wrapAsyncStatements(client.each.bind(client, columnsQuery, (err, column) => { + if (err) throw err; + + // In SQLite3, AUTOINCREMENT only applies to PK columns of + // INTEGER type + column.autoIncrement = (column.type.toLowerCase() == 'integer' + && column.pk == 1); + + // By default, assume column is not indexed until we find that it + // is + column.indexed = false; + + // Search for indexed columns + schema.indices.forEach(function (idx) { + if (!column.indexed) { + index.columns.forEach(function (indexedCol) { + if (indexedCol.name == column.name) { + column.indexed = true; + if (idx.unique) column.unique = true; + } + }); + } + }); + + schema.columns.push(column); + })); + + var normalizedSchema = utils.normalizeSchema(schema); + // Set internal schema mapping + datastore.manager.schema[tableName] = normalizedSchema; + + return Promise.resolve(normalizedSchema); + } catch (err) { + return Promise.reject(err); + } + }) + .then(schema => cb(undefined, schema)) + .catch(err => cb(err)); + }, + + /** + * ╔╦╗╔═╗╔═╗╦╔╗╔╔═╗ + * ║║║╣ ╠╣ ║║║║║╣ + * ═╩╝╚═╝╚ ╩╝╚╝╚═╝ + * Build a new physical model (e.g. table/etc) to use for storing records in the database. + * + * (This is used for schema migrations.) + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {String} datastoreName The name of the datastore containing the table to define. + * @param {String} tableName The name of the table to define. + * @param {Dictionary} definition The physical model definition (not a normal Sails/Waterline model-- log this for details.) + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {Function} done Callback + * @param {Error?} + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + */ + define: async function (datastoreName, tableName, definition, done) { + + // Look up the datastore entry (manager/driver/config). + var datastore = registeredDatastores[datastoreName]; + + // Sanity check: + if (_.isUndefined(datastore)) { + return done(new Error('Consistency violation: Cannot do that with datastore (`' + datastoreName + '`) because no matching datastore entry is registered in this adapter! This is usually due to a race condition (e.g. a lifecycle callback still running after the ORM has been torn down), or it could be due to a bug in this adapter. (If you get stumped, reach out at https://sailsjs.com/support.)')); + } + + let tableQuery; + let outerSchema + try { + const client = datastore.manager.writeClient; + const escapedTable = utils.escapeTable(tableName); + + // Iterate through each attribute, building a query string + const _schema = utils.buildSchema(definition, datastore.manager.foreignKeys[tableName]); + outerSchema = _schema.schema; + + // Check for any index attributes + const indices = utils.buildIndexes(definition); + + // Build query + // const query = 'CREATE TABLE ' + escapedTable + ' (' + _schema.declaration + ')'; + tableQuery = 'CREATE TABLE ' + escapedTable + ' (' + _schema.declaration + ')'; + + // await wrapAsyncStatements(client.run.bind(client, query)); + await wrapAsyncStatements(client.run.bind(client, tableQuery)); + + await Promise.all(indices.map(async index => { + // Build a query to create a namespaced index tableName_key + const indexQuery = 'CREATE INDEX ' + tableName + '_' + index + ' on ' + + tableName + ' (' + index + ');'; + + await wrapAsyncStatements(client.run.bind(client, indexQuery)); + })); + + // Replacing if it already existed + datastore.manager.schema[tableName] = _schema.schema; + + done(); + } catch (err) { + done(err); + } + + }, + + + /** + * ╔╦╗╦═╗╔═╗╔═╗ + * ║║╠╦╝║ ║╠═╝ + * ═╩╝╩╚═╚═╝╩ + * Drop a physical model (table/etc.) from the database, including all of its records. + * + * (This is used for schema migrations.) + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {String} datastoreName The name of the datastore containing the table to drop. + * @param {String} tableName The name of the table to drop. + * @param {Ref} unused Currently unused (do not use this argument.) + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {Function} done Callback + * @param {Error?} + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + */ + drop: async function (datastoreName, tableName, unused, done) { + + // Look up the datastore entry (manager/driver/config). + var dsEntry = registeredDatastores[datastoreName]; + + // Sanity check: + if (_.isUndefined(dsEntry)) { + return done(new Error('Consistency violation: Cannot do that with datastore (`' + datastoreName + '`) because no matching datastore entry is registered in this adapter! This is usually due to a race condition (e.g. a lifecycle callback still running after the ORM has been torn down), or it could be due to a bug in this adapter. (If you get stumped, reach out at https://sailsjs.com/support.)')); + } + + // Build query + const query = 'DROP TABLE IF EXISTS ' + utils.escapeTable(tableName); + + + try { + const client = dsEntry.manager.writeClient; + await wrapAsyncStatements(client.run.bind(client, query)); + + delete dsEntry.manager.schema[tableName]; + done(); + } catch (err) { + done(err); + } + + }, + + + /** + * ╔═╗╔═╗╔╦╗ ┌─┐┌─┐┌─┐ ┬ ┬┌─┐┌┐┌┌─┐┌─┐ + * ╚═╗║╣ ║ └─┐├┤ │─┼┐│ │├┤ ││││ ├┤ + * ╚═╝╚═╝ ╩ └─┘└─┘└─┘└└─┘└─┘┘└┘└─┘└─┘ + * Set a sequence in a physical model (specifically, the auto-incrementing + * counter for the primary key) to the specified value. + * + * (This is used for schema migrations.) + * + * > NOTE - removing method. SQLite can support setting a sequence on + * > primary key fields (or other autoincrement fields), however the + * > need is slim and I don't have time. + * > Leaving shell here for future developers if necessary + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {String} datastoreName The name of the datastore containing the table/etc. + * @param {String} sequenceName The name of the sequence to update. + * @param {Number} sequenceValue The new value for the sequence (e.g. 1) + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * @param {Function} done + * @param {Error?} + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + */ + // setSequence: function (datastoreName, sequenceName, sequenceValue, done) { + + // // Look up the datastore entry (manager/driver/config). + // var dsEntry = registeredDatastores[datastoreName]; + + // // Sanity check: + // if (_.isUndefined(dsEntry)) { + // return done(new Error('Consistency violation: Cannot do that with datastore (`'+datastoreName+'`) because no matching datastore entry is registered in this adapter! This is usually due to a race condition (e.g. a lifecycle callback still running after the ORM has been torn down), or it could be due to a bug in this adapter. (If you get stumped, reach out at https://sailsjs.com/support.)')); + // } + + // // Update the sequence. + // // + // // > TODO: Replace this setTimeout with real logic that calls + // // > `done()` when finished. (Or remove this method from the + // // > adapter altogether + // setTimeout(function(){ + // return done(new Error('Adapter method (`setSequence`) not implemented yet.')); + // }, 16); + + // }, +}; + +/** + * Spawns temporary connection and executes given logic. Returns promise for + * use with async/await + * @param {*} datastore + * @param {Function} logic Takes the client as its only argument. Can return a + * value or a Promise + * @param {*} cb + * @return Promise + */ +function spawnReadonlyConnection(datastore, logic) { + let client; + return new Promise((resolve, reject) => { + if (!datastore) reject(Errors.InvalidConnection); + + var datastoreConfig = datastore.config; + + // Check if we want to run in verbose mode + // Note that once you go verbose, you can't go back. + // See: https://github.com/mapbox/node-sqlite3/wiki/API + if (datastoreConfig.verbose) sqlite3.verbose(); + + // Make note whether the database already exists + exists = fs.existsSync(datastoreConfig.filename); + + // Create a new handle to our database + client = new sqlite3.Database( + datastoreConfig.filename, + sqlite3.OPEN_READONLY, + err => { + if (err) reject(err); + else resolve(client); + } + ); + }) + .then(logic) + .catch(err => { + return Promise.reject(err); //we want the user process to get this error as well + }) + .finally(() => { + if (client) client.close(); + }); +} + +/** + * Simple utility function that wraps an async function in a promise + * @param {Function} func Async function which takes 1 argument: a callback + * function that takes err, value as args (in that order) + * @return Promise + */ +function wrapAsyncStatements(func) { + return new Promise((resolve, reject) => { + func((err, value) => { + if (err) reject(err); + else resolve(value); + }); + }); +} + +/** + * Utility function that wraps an async function in a promise. In contrast + * to the above, this method specifically resolves with the `this` value + * passed to the callback function + * @param {Function} func Async function which takes 1 argument: a callback + * function that takes an err and invokes its callback with a `this` property + */ +function wrapAsyncForThis(func) { + return new Promise((resolve, reject) => { + func(function (err) { + if (err) reject(err); + else resolve(this); + }); + }) +} + +module.exports = adapter; \ No newline at end of file diff --git a/lib/query.js b/lib/query.js new file mode 100644 index 0000000..7055bee --- /dev/null +++ b/lib/query.js @@ -0,0 +1,484 @@ +/** + * Dependencies + */ + +var _ = require('@sailshq/lodash'), + utils = require('./utils'); + +/** + * Query Builder for creating parameterized queries for use + * with the SQLite3 adapter + * + * Most of this code was adapted from the Query class of + * Postgres adapter + * + * If you have any questions, contact Andrew Jo + */ + +const Query = function (tableName, schema, model) { + this._values = []; + this._paramCount = 1; + this._query = ''; + this._tableName = tableName; + this._escapedTable = utils.escapeTable(tableName); + /** Waterline model - provides info on type */ + this._modelByColumnName = {}; + if (!!model) { + for (let prop in model) { + if (prop !== 'definition') { + this._modelByColumnName[prop] = model[prop]; + } else { + const definitions = this._modelByColumnName[prop] = {}; + const attrs = model[prop]; + + for (let attrName in attrs) { + const attrDef = attrs[attrName]; + definitions[attrDef.columnName] = attrDef; + } + } + } + } + + this._schema = _.clone(schema); + + return this; +}; + +/** + * SELECT Statement + */ + +Query.prototype.find = function(criteria = {}) { + + const selectKeys = []; + if (criteria.select && criteria.select.length >>> 0 > 0) { + for (let key of criteria.select) { + selectKeys.push({ table: this._escapedTable, key }); + } + } else { + for (let columnName in this._schema) { + selectKeys.push({ table: this._escapedTable, key: columnName }); + } + } + const selects = + selectKeys + .map(keyObj => `${keyObj.table}.${utils.escapeName(keyObj.key)}`) + .join(', '); + + this._query = `SELECT ${selects} FROM ${this._escapedTable} `; + + this._build(criteria); + + return { + query: this._query, + values: this._values + }; +}; + +/** + * COUNT Statement + * Waterline only supports counting based on criteria, so we only need + * count(*) syntax + */ +Query.prototype.count = function(criteria = {}, alias = 'count_alias') { + this._query = `SELECT COUNT(*) as ${alias} FROM ${this._escapedTable} `; + this._build(criteria); + + return { + query: this._query, + values: this._values + }; +}; + +/** + * SUM Statement + * No Group By in Waterline api v1. Odd... + */ +Query.prototype.sum = function(criteria = {}, columnName, alias = 'sum_alias') { + this._query = `SELECT TOTAL(${utils.escapeName(columnName)}) as ${alias} FROM ${this._escapedTable} `; + this._build(criteria); + + return { + query: this._query, + values: this._values + }; +} + +/** + * AVG Statement + * No Group By in Waterline api v1. Odd... + */ +Query.prototype.avg = function (criteria = {}, columnName, alias = 'avg_alias') { + this._query = `SELECT AVG(${utils.escapeName(columnName)}) as ${alias} FROM ${this._escapedTable} `; + this._build(criteria); + + return { + query: this._query, + values: this._values + }; +} + +/** + * UPDATE Statement + */ + +Query.prototype.update = function(criteria, data) { + this._query = 'UPDATE ' + utils.escapeTable(this._tableName) + ' '; + + // Transform the Data object into arrays used in a parameterized query + var attributes = utils.mapAllAttributes([data], this._schema); + + const params = attributes.paramLists[0]; + + // Update the paramCount + this._paramCount = params.length + 1; + + // Build SET string + const assignments = []; + for (var i = 0; i < attributes.keys.length; i++) { + assignments.push(`${attributes.keys[i]} = ${params[i]}`); + } + + this._query += `SET ${assignments.join(', ')} `; + + // Add data values to this._values + this._values = attributes.values; + // Build criteria clause + if (criteria) this._build({ where: criteria.where }); + + return { + query: this._query, + values: this._values + }; +}; + + +/** + * DELETE Statement + */ + +Query.prototype.destroy = function(criteria) { + this._query = `DELETE FROM ${utils.escapeTable(this._tableName)} `; + if (criteria) { + criteria = Object.assign({}, criteria); + delete criteria.limit; //we don't want a limit in a delete query + this._build(criteria); + } + + return { + query: this._query, + values: this._values + }; +}; + + +/** + * String Builder + */ + +Query.prototype._build = function(criteria) { + + // Evaluate criteria in correct order + if (criteria.where) this.where(criteria.where); + if (criteria.sort) this.sort(criteria.sort); + if (criteria.limit) this.limit(criteria.limit); + if (criteria.skip) this.skip(criteria.skip); + + return { + query: this._query, + values: this._values + } +}; + +/** + * Specifiy a `where` condition + * + * `Where` conditions may use key/value model attributes for simple query + * look ups. Complex conditions are grouped in 'AND' and 'OR' arrays + * + * The following conditions are supported along with simple criteria: + * + * Conditions: + * [And, Or] + * + * Criteria Operators: + * [<, <=, >, >=, !=, nin, in, like] + * + * ####Example + * + * where: { + * and: [ + * {name: 'foo'}, + * {age: { '>': 25 }}, + * {desc: {like: '%hello%'}} + * ] + * } + */ +Query.prototype.where = function(criteria) { + if (Object.keys(criteria).length > 0) { + const criteriaTree = new Criterion(this._schema); + criteriaTree.addCriterion(criteria); + + criteriaTree.setParamIndex(this._paramCount); + + const parsedCriteria = criteriaTree.generateCriteria(); + this._query += parsedCriteria.whereClause; + this._values = this._values.concat(parsedCriteria.values); + } +} + +/** + * Utility class for building criteria. Constructs a tree and recurses down + * it to build the final string + */ +class Criterion { + constructor(schema, joinString = '', rawCriterion){ + this._schema = schema; + this._joinString = joinString; + this._isLeaf = !!rawCriterion; + this._criteriaList = []; + this._rawCriterion = rawCriterion + this._paramIndex = 1; + } + + setParamIndex(index) { + this._paramIndex = index; + } + + addCriterion(rawCriterion) { + const newCriterion = this._constructCriterionTree(rawCriterion); + + this._criteriaList.push(newCriterion); + } + + /** + * Private method + * @param {Object} rawCriterion + */ + _constructCriterionTree(rawCriterion) { + let subCriterion; + if (!!rawCriterion.and) { + subCriterion = new Criterion(this._schema, ' AND '); + + for (let andElement of rawCriterion.and) { + subCriterion.addCriterion(andElement); + } + } else if (!!rawCriterion.or) { + subCriterion = new Criterion(this._schema, ' OR '); + + for (let orElement of rawCriterion.or) { + subCriterion.addCriterion(orElement); + } + } else { + subCriterion = new Criterion(this._schema, '', rawCriterion); + } + + return subCriterion; + } + + /** + * Private Method. Converts list to string. + * Increments _paramIndex + * @param {Array} list + * @return {Object} + * @param {String} setString + * @param {Array} dedupedValues + */ + _arrayToSqlSet(list) { + const valueSet = new Set(list); + const dedupedValues = []; + const paramList = []; + + for (let val of valueSet) { + dedupedValues.push(val); + paramList.push(`$${this._paramIndex++}`); + } + + return { + setString: `(${paramList.map(obj => obj.toString()).join(', ')})`, + dedupedValues + }; + } + + /** + * Performs the logic of generating the criteria. If not a leaf, recurses + * down and wraps sub-criteria in parentheses + * @return {Object} + * @property {String} whereClause The constructed string + * @property {Array} values The values matching params + * @property {Number} paramIndex The number of parameters generated by this function + */ + generateCriteria(paramIndex = -1) { + //differentiate from root call + let isRoot = false; + if (paramIndex > 0) { + this._paramIndex = paramIndex; + } else { + isRoot = true; + } + + if (!this._isLeaf) { + const subCriteriaResults = this._criteriaList + .map(criterion => { + const result = criterion.generateCriteria(this._paramIndex); + this._paramIndex = result.paramIndex; + + return result; + }); + + const values = subCriteriaResults.reduce((previous, next) => { + return previous.concat(next.values); + }, []); + + const whereClause = (isRoot ? 'WHERE ' : '') + + subCriteriaResults + .map(sub => `(${sub.whereClause})`) + .join(this._joinString); + + return {whereClause, values, paramIndex: this._paramIndex}; + } + + /**** Leaf Node Code *****/ + if (Object.keys(this._rawCriterion).length !== 1) { + throw new Error('Unexpected query object: exactly one column key expected'); + } + let columnName = Object.keys(this._rawCriterion)[0]; + let condition = this._rawCriterion[columnName]; + let values = []; + let whereClause; + // get schema / model type so we know how to process + const columnType = this._schema[columnName].type; + + if (condition == null) { + whereClause = `${columnName} IS NULL`; + } else if (typeof condition != 'object' || columnType === 'BLOB') { + whereClause = `${columnName} = $${this._paramIndex++}`; + values.push(condition); + } else { + const conditionKeys = Object.keys(condition); + if (conditionKeys.length !== 1) { + throw new Error('Multiple conditions detected for one column condition. The adapter is confused'); + } + + const operator = conditionKeys[0]; + let dedupedValues, setString; + switch (operator) { + case '>': + case '>=': + case '<': + case '<=': + case 'like': + whereClause = `${columnName} ${operator.toUpperCase()} $${this._paramIndex++}`; + values.push(condition[operator]); + break; + case '!=': + whereClause = `${columnName} <> $${this._paramIndex++}`; + values.push(condition[operator]); + break; + case 'in': + ({dedupedValues, setString} = this._arrayToSqlSet(condition[operator])) + values = dedupedValues; + whereClause = `${columnName} IN ${setString}`; + break; + case 'nin': + ({ dedupedValues, setString } = this._arrayToSqlSet(condition[operator])) + values = dedupedValues; + whereClause = `${columnName} NOT IN ${setString}`; + break; + } + } + + return {whereClause, values, paramIndex: this._paramIndex}; + } +} + +/** + * Specify a `limit` condition + */ + +Query.prototype.limit = function(options) { + this._query += ' LIMIT ' + options; +}; + +/** + * Specify a `skip` condition + */ + +Query.prototype.skip = function(options) { + this._query += ' OFFSET ' + options; +}; + +/** + * Specify a `sort` condition + */ + +Query.prototype.sort = function(options) { + if (options.length >>> 0 === 0) return; + var self = this; + + this._query += ' ORDER BY '; + const sortItems = []; + + for (let sortItem of options) { + for (let column in sortItem) { + sortItems.push(`"${column}" ${sortItem[column]}`); + } + } + + this._query += sortItems.join(', '); +}; + +/** + * Cast special values to proper types. + * + * Ex: Array is stored as "[0,1,2,3]" and should be cast to proper + * array for return values. + */ +Query.prototype.castRow = function(values) { + const newModel = {}; + + for (let columnName in values) { + const attrDef = this._modelByColumnName.definition[columnName]; + const value = values[columnName]; + + if (value === null) { + newModel[columnName] = null; + continue; + } + + switch(attrDef.type) { + case 'json': + let parsedVal; + try { + parsedVal = JSON.parse(value); + } catch (err) { + // edge case of just string + if (!value.startsWith('{') && !value.startsWith('[')) { + parsedVal = JSON.parse(`"${value}"`); + } else { + throw err; + } + } + + newModel[columnName] = parsedVal; + break; + case 'boolean': + newModel[columnName] = !!value; + break; + case 'number': + newModel[columnName] = parseFloat(value); + break; + case 'numberkey': + newModel[columnName] = parseInt(value, 10); + break; + case 'string': + newModel[columnName] = value.toString(); + default: + newModel[columnName] = value; + } + } + + return newModel; +}; + +module.exports = Query; diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..f3741c0 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,295 @@ +const _ = require('@sailshq/lodash'); + +const utils = module.exports = {}; + +// CONSTANT Regular Expressions for data types +const coreAffinities = /TEXT|INTEGER|REAL|BLOB|NUMERIC/i; +const intTypes = /INT/i; +const textTypes = /CHAR|CLOB/i; +const realTypes = /DOUBLE|FLOAT/i; + +/** + * Build a schema from an attributes object + * @return Object {declaration: string, schema: object} + */ +utils.buildSchema = function(obj, foreignKeys) { + let schema = {}; + let columnDefs = []; + let constraintDefs = []; + + // Iterate through the Object Keys and build a string + Object.keys(obj).forEach(function(key) { + const attr = obj[key]; + + const sqliteType = utils.sqlTypeCast(attr.columnType, key); + const affinity = utils.getAffinity(sqliteType); + + schema[key] = { + primaryKey: attr.primaryKey, + unique: attr.unique, + indexed: attr.unique || attr.primaryKey, // indexing rules in sqlite + type: affinity + } + + // Note: we are ignoring autoincrement b/c it is only supported on + // primary key defs, but probably shouldn't be used there anyway + // https://sqlite.org/autoinc.html + const def = [ + '"' + key + '"', // attribute name + sqliteType, // attribute type + attr.primaryKey ? 'PRIMARY KEY' : '', // primary key + attr.unique ? 'UNIQUE' : '' // unique constraint + ].join(' ').trim(); + + columnDefs.push(def); + }); + + for (let columnName in foreignKeys) { + const keyDef = foreignKeys[columnName]; + constraintDefs.push(`FOREIGN KEY(${columnName}) REFERENCES ${keyDef.table}(${keyDef.column})`); + } + + return { + declaration: columnDefs.concat(constraintDefs).join(', '), + schema + }; +}; + +/** + * Sqlite3 defines types as "affinities" since all columns can contain + * all types + */ +utils.getAffinity = sqliteType => { + if (!sqliteType) return 'BLOB'; // essentially no type + + const matches = coreAffinities.exec(sqliteType); + if (matches !== null) return matches[0].toUpperCase(); + + if (intTypes.exec(sqliteType) !== null) return 'INTEGER'; + if (textTypes.exec(sqliteType) !== null) return 'TEXT'; + if (realTypes.exec(sqliteType) !== null) return 'REAL'; + + return 'NUMERIC'; +}; + +/** + * @return Map by unescaped tablename of the foreign key fields and the tables + * they look up to + */ +utils.buildForeignKeyMap = physicalModelsReport => { + const foreignKeyMap = {}; + + for (let tableName in physicalModelsReport) { + tableKeys = {}; + foreignKeyMap[tableName] = tableKeys; + + for (let columnName in physicalModelsReport[tableName].definition) { + const column = physicalModelsReport[tableName].definition[columnName]; + + if (column.foreignKey) { + tableKeys[column.columnName] = { + table: column.references, + column: column.on + } + } + } + } + + return foreignKeyMap; +} + +/** +* Escape Name +* +* Wraps a name in quotes to allow reserved +* words as table or column names such as user. +*/ +utils.escapeName = utils.escapeTable = name => `"${name}"`; + +/** + * Build an Index array from any attributes that + * have an index key set. + */ +utils.buildIndexes = function(obj) { + var indexes = []; + + // Iterate through the Object keys and pull out any index attributes + Object.keys(obj).forEach(function(key) { + if (obj[key].hasOwnProperty('index')) indexes.push(key); + }); + + return indexes; +}; + +/** + * Map data from a stage-3 query to its representation + * in Sqlite. Collects a list of columns and processes + * an group of records. + * @param recordList An array of records to be processed + * @param schema The schema for the table + * @return Object with below properties: + * - keys: array of keys (column names) in the order the data will be represented + * - values: array of values. Values are ordered such that they line up + * exactly with a flattened version of the paramLists property + * - paramLists: array of arrays. Each element of the outer array is an array of + * values in the same order as keys. + */ +utils.mapAllAttributes = function(recordList, schema) { + const keys = new Map(); + const valueMaps = []; + + for (let record of recordList) { + const recordValueMap = {} + valueMaps.push(recordValueMap); + + for (let columnName in record) { + keys.set(columnName, `"${columnName}"`); + recordValueMap[columnName] = + utils.prepareValue(record[columnName], schema[columnName].type); + } + } + + const keyList = []; + const objKeys = []; + + // create set order of columns (keys) + for (let entry of keys) { + objKeys.push(entry[0]); + keyList.push(entry[1]); + } + + const paramLists = []; + let i = 1; + const valueList = []; + for (let values of valueMaps) { + const paramList = []; + paramLists.push(paramList); + + for (let key of objKeys) { + let nextValue = values[key]; + + if (nextValue === undefined || nextValue === null) { + valueList.push(null); + } else { + valueList.push(nextValue); + } + + paramList.push('$' + i); + i++; + } + } + + return ({ keys: keyList, values: valueList, paramLists: paramLists }); +}; + +utils.normalizeSchema = function(schema) { + var normalized = {}; + var clone = _.clone(schema); + + clone.columns.forEach(function(column) { + + // Set type + normalized[column.name] = { type: column.type }; + + // Check for primary key + normalized[column.name].primaryKey = column.pk ? true : false; + + // Indicate whether the column is indexed + normalized[column.name].indexed = !!column.indexed; + + // Show unique constraint + normalized[column.name].unique = !!column.unique; + + normalized[column.name].autoIncrement = !!column.autoIncrement; + }); + + return normalized; +}; + +/** + * Prepare values + * + * Transform a JS date to SQL date and functions + * to strings. + */ + +utils.prepareValue = function(value, columnType) { + if (value === null) return null; + + // Cast dates to SQL + if (_.isDate(value)) { + switch(columnType) { + case 'TEXT': + value = value.toUTCString(); + break; + case 'INTEGER': + case 'REAL': + value = value.valueOf(); + default: + throw new Error(`Cannot cast date to ${columnType}`); + } + + return value; + } + + // Cast functions to strings + if (_.isFunction(value)) { + if (columnType !== 'TEXT') throw new Error('Function can only cast to TEXT'); + return value.toString(); + } + + // Check buffers for BLOB typs + if (Buffer.isBuffer(value)) { + if (columnType !== 'BLOB') throw new Error('Buffers may only represent BLOB types'); + } + + // Store Arrays / Objects as JSON strings + if (typeof value === 'object' && columnType !== 'BLOB') { + return JSON.stringify(value); + } + + switch(columnType) { + case 'TEXT': + return value.toString(); + case 'INTEGER': + if (typeof value === 'boolean') return value ? 1 : 0; + return parseInt(value, 0); + case 'REAL': + return parseFloat(value); + } + + return value; //BLOB or NUMERIC +}; + +/** + * Cast waterline types to SQLite3 data types + */ +utils.sqlTypeCast = function(type, columnName) { + // type has been explicitly specified by the user + if (!type.startsWith('_')) return type; + + switch (type.toLowerCase()) { + case '_string': + return 'TEXT'; + + case '_boolean': + return `INTEGER CHECK(${columnName} IN (0, 1))`; + + case '_numberkey': + case '_numbertimestamp': //dates + return 'INTEGER'; + + case '_number': + return 'REAL'; + + case '_ref': + return 'BLOB'; + + case '_json': + return 'TEXT'; + + default: + console.error("Warning: Unregistered type given: " + type); + return 'TEXT'; + } +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a4ae65c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2007 @@ +{ + "name": "sails-sqlite3", + "version": "0.1.3", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@sailshq/lodash": { + "version": "3.10.3", + "resolved": "https://registry.npmjs.org/@sailshq/lodash/-/lodash-3.10.3.tgz", + "integrity": "sha512-XTF5BtsTSiSpTnfqrCGS5Q8FvSHWCywA0oRxFAZo8E1a8k1MMFUvk3VlRk3q/SusEYwy7gvVdyt9vvNlTa2VuA==" + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "anchor": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/anchor/-/anchor-1.3.0.tgz", + "integrity": "sha512-mA+EfMr/WVT69u1HisKqQED7+LmTxpb0Lm9Lo/qTT/uf7AOFA3qYYb/ZPiMi3aQqWn2ji4fC6UQuRIP0XBV9ZA==", + "dev": true, + "requires": { + "@sailshq/lodash": "^3.10.2", + "validator": "5.7.0" + } + }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bluebird": { + "version": "3.2.1", + "resolved": "http://registry.npmjs.org/bluebird/-/bluebird-3.2.1.tgz", + "integrity": "sha1-POzzUEkEwwzj55wXCHfok6EZEP0=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "chownr": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==" + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "encrypted-attr": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/encrypted-attr/-/encrypted-attr-1.0.6.tgz", + "integrity": "sha512-12WE8GDkbhKcGmVp6+TyJXCcFj9NF7db33nutjOSBLlMuYY4oCGricgTEUAuRSI1xLeE1nhoDD6jSx20WgFVYg==", + "dev": true, + "requires": { + "lodash": "^4.17.4" + } + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "flaverr": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/flaverr/-/flaverr-1.9.2.tgz", + "integrity": "sha512-14CoGOGUhFkhzDCgGdpFFJE9PrdMPhGmhuS39WxxgTpTKVzfWW3DAVfolUiHwYpaROz7UFrJuaSJtsxhem+i9g==", + "dev": true, + "requires": { + "@sailshq/lodash": "^3.10.2" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs-extra": { + "version": "0.30.0", + "resolved": "http://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, + "fs-minipass": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz", + "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==", + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basecreate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true, + "requires": { + "lodash._baseassign": "^3.0.0", + "lodash._basecreate": "^3.0.0", + "lodash._isiterateecall": "^3.0.0" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.issafeinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.issafeinteger/-/lodash.issafeinteger-4.0.4.tgz", + "integrity": "sha1-sXbVmQ7GSdBr7cvOLwKOeJBJT5A=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "minipass": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.0.tgz", + "integrity": "sha512-qwfFgY+7EKAAUAdv7VYMZQknI7YJSGesxHyhn6qD52DV8UcSZs5XwCifcZGMVIE4a5fbmhvbotxC0DLQ0oKohQ==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.2.2", + "yargs-parser": "13.0.0", + "yargs-unparser": "1.5.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + }, + "needle": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", + "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "node-pre-gyp": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", + "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==" + }, + "npm-packlist": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.4.tgz", + "integrity": "sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==", + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parley": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/parley/-/parley-3.8.0.tgz", + "integrity": "sha512-WhNhNMPoxTydFg7U/MCAraE4JJLVSYeCJ3Wg5xHb1Z3EKC5N+SXldq6NYAtrgBSLpo4jHKQI2SWOhWEHJ82CGw==", + "dev": true, + "requires": { + "@sailshq/lodash": "^3.10.2", + "bluebird": "3.2.1", + "flaverr": "^1.5.1" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "psl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.3.0.tgz", + "integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "requires": { + "glob": "^7.0.5" + } + }, + "rttc": { + "version": "10.0.0-4", + "resolved": "https://registry.npmjs.org/rttc/-/rttc-10.0.0-4.tgz", + "integrity": "sha512-HroJ9z+RVipbPCeFdglopiVM18w9BM5PFqXivM6ZceNQEphjpDGZ154srk1JhviNacrmFqhdzDbGQZsB13g6JA==", + "dev": true, + "requires": { + "@sailshq/lodash": "^3.10.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sqlite3": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.1.0.tgz", + "integrity": "sha512-RvqoKxq+8pDHsJo7aXxsFR18i+dU2Wp5o12qAJOV5LNcDt+fgJsc2QKKg3sIRfXrN9ZjzY1T7SNe/DFVqAXjaw==", + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.11.0", + "request": "^2.87.0" + } + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tar": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", + "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.5", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "validator": { + "version": "5.7.0", + "resolved": "http://registry.npmjs.org/validator/-/validator-5.7.0.tgz", + "integrity": "sha1-eoelgUa2laxIYHEUHAxJ1n2gXlw=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "waterline": { + "version": "github:balderdashy/waterline#90b8a0a9132862faaa3b6851a8c4db1f8c41b23c", + "from": "github:balderdashy/waterline", + "dev": true, + "requires": { + "@sailshq/lodash": "^3.10.2", + "anchor": "^1.2.0", + "async": "2.0.1", + "encrypted-attr": "1.0.6", + "flaverr": "^1.8.3", + "lodash.issafeinteger": "4.0.4", + "parley": "^3.3.2", + "rttc": "^10.0.0-1", + "waterline-schema": "^1.0.0-20", + "waterline-utils": "^1.3.7" + }, + "dependencies": { + "async": { + "version": "2.0.1", + "resolved": "http://registry.npmjs.org/async/-/async-2.0.1.tgz", + "integrity": "sha1-twnMAoCpw28J9FNr6CPIOKkEniU=", + "dev": true, + "requires": { + "lodash": "^4.8.0" + } + } + } + }, + "waterline-adapter-tests": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/waterline-adapter-tests/-/waterline-adapter-tests-1.0.1.tgz", + "integrity": "sha512-dBD2410D1bYWXVTcBqRSVvOF3MNEmUDMKry7L5p4PlT4tIkmaR5JfKvSMWvXr5m3edSfL4VaEVhWAEJ27+VoqQ==", + "dev": true, + "requires": { + "@sailshq/lodash": "3.10.2", + "async": "2.0.1", + "bluebird": "3.2.1", + "mocha": "3.0.2", + "waterline": "github:balderdashy/waterline", + "waterline-utils": "^1.3.2" + }, + "dependencies": { + "@sailshq/lodash": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@sailshq/lodash/-/lodash-3.10.2.tgz", + "integrity": "sha1-FWfUc0U2TCwuIHe8ETSHsd/mIVQ=", + "dev": true + }, + "async": { + "version": "2.0.1", + "resolved": "http://registry.npmjs.org/async/-/async-2.0.1.tgz", + "integrity": "sha1-twnMAoCpw28J9FNr6CPIOKkEniU=", + "dev": true, + "requires": { + "lodash": "^4.8.0" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "commander": { + "version": "2.9.0", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": ">= 1.0.0" + } + }, + "debug": { + "version": "2.2.0", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true + }, + "glob": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.5.tgz", + "integrity": "sha1-tCAqaQmbu00pKnwblbZoK2fr3JU=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "mocha": { + "version": "3.0.2", + "resolved": "http://registry.npmjs.org/mocha/-/mocha-3.0.2.tgz", + "integrity": "sha1-Y6l/Phj00+ZZ1HphdnfQiYdFV/A=", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.2.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.5", + "glob": "7.0.5", + "growl": "1.9.2", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "supports-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "waterline-errors": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/waterline-errors/-/waterline-errors-0.10.1.tgz", + "integrity": "sha1-7mNjKq3emTJxt1FLfKmNn9W4ai4=" + }, + "waterline-schema": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/waterline-schema/-/waterline-schema-1.0.0.tgz", + "integrity": "sha512-dSz/CvOLYMULKieB91+ZSv415+AVgrLhlSWbhpVHfpczIbKyj+zorsB5AG+ukGw1z0CPs6F1ib8MicBNjtwv6g==", + "dev": true, + "requires": { + "@sailshq/lodash": "^3.10.2", + "flaverr": "^1.8.1", + "rttc": "^10.0.0-1" + } + }, + "waterline-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/waterline-utils/-/waterline-utils-1.4.2.tgz", + "integrity": "sha512-WsS1yRw8xT6O7iIK8oAcYr3/vhMYiTHdA/vQnU/JsGbcad2Xi4p8bie8/F+BoEyN7SEpBZ8nFT9OdszDAt7Ugg==", + "dev": true, + "requires": { + "@sailshq/lodash": "^3.10.2", + "async": "2.0.1", + "flaverr": "^1.1.1", + "fs-extra": "0.30.0", + "qs": "6.4.0" + }, + "dependencies": { + "async": { + "version": "2.0.1", + "resolved": "http://registry.npmjs.org/async/-/async-2.0.1.tgz", + "integrity": "sha1-twnMAoCpw28J9FNr6CPIOKkEniU=", + "dev": true, + "requires": { + "lodash": "^4.8.0" + } + }, + "qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", + "dev": true + } + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + }, + "yargs": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", + "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", + "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", + "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.11", + "yargs": "^12.0.5" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + } + } +} diff --git a/package.json b/package.json index a9f0670..d68816a 100755 --- a/package.json +++ b/package.json @@ -1,26 +1,54 @@ { - "name": "sails-adapter-boilerplate", - "version": "0.0.1", - "description": "Boilerplate adapter for Sails.js", - "main": "BoilerplateAdapter.js", - "scripts": { - "test": "echo \"Adapter should be tested using Sails.js core.\" && exit 1" - }, + "name": "sails-sqlite3", + "version": "0.1.3", + "description": "Waterline Adapter for SQLite in Sails.js", + "main": "lib/adapter.js", "repository": { "type": "git", - "url": "https://github.com/balderdashy/sails-adapter-boilerplate.git" + "url": "https://github.com/AndrewJo/sails-sqlite3.git" }, "keywords": [ "orm", "waterline", "sails", "sailsjs", - "sails.js" + "sails.js", + "sqlite" + ], + "contributors": [ + { + "name": "Andrew Jo", + "email": "andrewjo@gmail.com>" + }, + { + "name": "Kevin C. Gall", + "email": "kcg245@gmail.com" + } ], - "author": "Your name here", "license": "MIT", "readmeFilename": "README.md", "dependencies": { - "async": "0.1.22" + "@sailshq/lodash": "^3.10.3", + "sqlite3": "^4.1.0", + "waterline-errors": "^0.10.1" + }, + "devDependencies": { + "mocha": "^6.2.0", + "waterline-adapter-tests": "^1.0.1" + }, + "scripts": { + "test": "node test/runner.js" + }, + "sails": { + "adapter": { + "type": "sqlite3", + "sailsVersion": "^1.0.0", + "implements": [ + "semantic", + "queryable", + "associations", + "migratable" + ] + } } } diff --git a/test/register.js b/test/register.js deleted file mode 100644 index b4fcbb7..0000000 --- a/test/register.js +++ /dev/null @@ -1,14 +0,0 @@ -describe('registerCollection', function () { - - it('should not hang or encounter any errors', function (cb) { - var adapter = require('../index.js'); - adapter.registerCollection({ - identity: 'foo' - }, cb); - }); - - // e.g. - // it('should create a mysql connection pool', function () {}) - // it('should create an HTTP connection pool', function () {}) - // ... and so on. -}); \ No newline at end of file diff --git a/test/runner.js b/test/runner.js new file mode 100644 index 0000000..a22f854 --- /dev/null +++ b/test/runner.js @@ -0,0 +1,24 @@ +var tests = require('waterline-adapter-tests'), + sqlite3 = require('sqlite3'), + adapter = require('../lib/adapter'), + mocha = require('mocha'); + +/** + * SQLite3 configuration + */ + +var config = { + filename: "sailssqlite.db", + mode: sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, + verbose: true +}; + +/** + * Run Tests + */ + +var suite = new tests({ + adapter: adapter, + config: config, + interfaces: ['semantic','queryable','migratable','associations'], +}); \ No newline at end of file