diff --git a/bin/cli.js b/bin/cli.js deleted file mode 100755 index 8089406..0000000 --- a/bin/cli.js +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env node -var program = require('commander'), - request = require('superagent'), - server = require('../server'), - logger = require('../utils/logger'), - moment = require('moment'), - fs = require('fs'), - pkg = require('../package.json'), - options = {}; - -function loadFile(file, cb) { - var path = process.cwd() + '/' + file, - db; - - if (/\.json$/.test(file)) db = require(path); - if (/\.js$/.test(file)) db = require(path).run(); - - cb(db); -} - -function loadURL(url, cb) { - logger.info('Fetching ' + url + '...') - request - .get(url) - .end(function(error, res) { - if (error) { - logger.error(error); - } else { - cb(JSON.parse(res.text)); - } - }); -} - -function saveDbOnCommand(app) { - console.assert(app, 'expected app object'); - - process.stdin.resume(); - process.stdin.setEncoding('utf8'); - logger.notice('To save live database at any moment, enter `s`'); - - process.stdin.on('data', function (userInput) { - if (userInput.trim().toLowerCase() == 's') { - var liveDB = app.db(); - var now = moment().format('YYYY-MM-DD-HH:mm:ss') - var filename = 'json-server.' + now + '.json'; - console.assert(liveDB, 'expected live db object'); - fs.writeFileSync(filename, - JSON.stringify(liveDB, null, 2), - 'utf-8'); - console.log('saved db to', filename); - } - }); -} - -function onDatabaseLoaded(db) { - var app = server.run(db, options); - saveDbOnCommand(app); - return app; -} - -program - .version(pkg.version) - .option('-f --file ', 'load db from a js or json file') - .option('-u --url ', 'load db from a URL') - .option('-p --port [port]', 'server port') - .option('--read-only', 'read only mode'); - -program.on('--help', function () { - var examples = - ' Examples:\n\n' + - ' json-server --file db.json\n' + - ' json-server --file seed.js\n' + - ' json-server --url http://example.com/db.json\n' - console.log(examples); -}); - -program.parse(process.argv); - -if (program.port) options.port = program.port; -if (program.readOnly) options.readOnly = true; -if (program.file) loadFile(program.file, onDatabaseLoaded); -if (program.url) loadURL(program.url, onDatabaseLoaded); diff --git a/package.json b/package.json index 06f7d5b..5ded30f 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,9 @@ "superagent": "~0.15.7", "underscore": "~1.5.2", "underscore.inflections": "~0.2.1", - "moment": "~2.4.0" + "moment": "~2.4.0", + "low": "^0.4.2", + "restify": "^2.6.3" }, "devDependencies": { "supertest": "~0.8.1", diff --git a/routes/read-only.js b/routes/read-only.js deleted file mode 100644 index da0b2f7..0000000 --- a/routes/read-only.js +++ /dev/null @@ -1,71 +0,0 @@ -var _ = require('underscore'), - utils = require('../utils/utils'), - db = {}; - -_.mixin(require('../utils/db-mixins')); - -exports.setDatabase = function(object) { - db = object; -} - -exports.database = function(req, res) { - res.jsonp(db) -} - -// GET /:resource?attr=&attr= -exports.list = function(req, res) { - var collection = db[req.params.resource], - properties = {}, - result; - - Object.keys(req.query).forEach(function (key) { - var value = req.query[key]; - properties[key] = utils.toNative(value); - }); - - if (_(properties).isEmpty()) { - result = collection; - } else { - result = _(collection).where(properties); - } - - res.jsonp(result); -} - -// GET /:parent/:parentId/:resource -exports.nestedList = function(req, res) { - var properties = {}, - resource; - - // Set parentID - properties[req.params.parent.slice(0, - 1) + 'Id'] = +req.params.parentId; - - // Filter using parentID - resource = _.where(db[req.params.resource], properties); - - res.jsonp(resource); -} - -// GET /:resource/:id -exports.show = function(req, res) { - var resource = _.get(db, req.params.resource, +req.params.id); - - res.jsonp(resource); -} - -exports.create = function(req, res) { - req.body.id = Math.round(new Date().getTime() / 1000); - res.jsonp(req.body); -} - -exports.update = function(req, res) { - var resource = _.get(db, req.params.resource, +req.params.id), - clonedResource = _.clone(resource), - result = _.extend(clonedResource, req.body); - - res.jsonp(result); -} - -exports.destroy = function(req, res) { - res.send(204) -} diff --git a/routes/read-write.js b/routes/read-write.js deleted file mode 100644 index 61f89a8..0000000 --- a/routes/read-write.js +++ /dev/null @@ -1,83 +0,0 @@ -var _ = require('underscore'), - utils = require('../utils/utils'), - db = {}; - -_.mixin(require('../utils/db-mixins')); - -exports.setDatabase = function(object) { - db = object; -} - -exports.database = function(req, res) { - res.jsonp(db) -} - -exports.db = function() { - return db; -} - -// GET /:resource?attr=&attr= -exports.list = function(req, res) { - var collection = db[req.params.resource], - properties = {}, - result; - - Object.keys(req.query).forEach(function (key) { - var value = req.query[key]; - properties[key] = utils.toNative(value); - }); - - if (_(properties).isEmpty()) { - result = collection; - } else { - result = _(collection).where(properties); - } - - res.jsonp(result); -} - -// GET /:parent/:parentId/:resource -exports.nestedList = function(req, res) { - var properties = {}, - resource; - - // Set parentID - properties[req.params.parent.slice(0, - 1) + 'Id'] = +req.params.parentId; - - // Filter using parentID - resource = _.where(db[req.params.resource], properties); - - res.jsonp(resource); -} - -// GET /:resource/:id -exports.show = function(req, res) { - var resource = _.get(db, req.params.resource, +req.params.id); - - res.jsonp(resource); -} - -// POST /:resource -exports.create = function(req, res) { - var resource = _.create(db, req.params.resource, req.body); - - res.jsonp(resource); -} - -// PUT /:resource/:id -// PATCH /:resource/:id -exports.update = function(req, res) { - _.update(db, req.params.resource, +req.params.id, req.body); - var resource = _.get(db, req.params.resource, +req.params.id); - - res.jsonp(resource); -} - -// DELETE /:resource/:id -exports.destroy = function(req, res) { - _.remove(db, req.params.resource, +req.params.id); - _.clean(db); - - res.send(204); -} - diff --git a/server.js b/server.js deleted file mode 100644 index f1dc744..0000000 --- a/server.js +++ /dev/null @@ -1,96 +0,0 @@ -var express = require('express'), - cors = require('cors'), - http = require('http'), - path = require('path'), - fs = require('fs'), - _ = require('underscore'), - logger = require('./utils/logger'); - - -var defaultOptions = { - port: process.env.PORT || 3000, - readOnly: false -} - -function createApp(db, options) { - // Create app - var app = express(), - options = options || {}, - routes; - - // Configure all environments - app.use(express.favicon()); - app.use(express.logger('dev')); - app.use(express.json()); - app.use(express.urlencoded()); - app.use(express.methodOverride()); - - - // Configure development - if ('development' == app.get('env')) { - app.use(express.errorHandler()); - } - - // Configure using options provided - app.set('port', options.port); - routes = options.readOnly ? './routes/read-only' : './routes/read-write'; - routes = require(routes); - - // Use default or user public directory - // Note: should be done before CORS and app.router setting - if (fs.existsSync(process.cwd() + '/public')) { - app.use(express.static(process.cwd() + '/public')); - } else { - app.use(express.static(path.join(__dirname, './public'))); - } - - // Enable CORS for everything - app.use(cors()); - app.options('*', cors()); - - // Set app.router - app.use(app.router); - - // Set API entry points - app.get('/db', routes.database) - app.get('/:resource', routes.list); - app.get('/:parent/:parentId/:resource', routes.nestedList); - app.get('/:resource/:id', routes.show); - app.post('/:resource', routes.create); - app.put('/:resource/:id', routes.update); - app.patch('/:resource/:id', routes.update); - app.del('/:resource/:id', routes.destroy); - - // Set database - routes.setDatabase(db); - app.db = routes.db; - - // And done! Ready to serve JSON! - return app; -} - -function run(db, options) { - options = _.defaults(options || {}, defaultOptions); - - var app = createApp(db, options); - - if (_.isEmpty(db)) { - logger.error('No resources found!'); - } else { - logger.success('Available resources'); - for (var prop in db) { - logger.url(options.port, prop); - } - } - - http - .createServer(app) - .listen((options.port), function(){ - logger.success('Express server listening on port ' + options.port); - logger.success('Congrats! Open http://localhost:' + options.port); - }); - return app; -} - -exports.createApp = createApp; -exports.run = run; diff --git a/src/server.js b/src/server.js new file mode 100644 index 0000000..5af9ab5 --- /dev/null +++ b/src/server.js @@ -0,0 +1,127 @@ +var _ = require('underscore') +var low = require('low') +var restify = require('restify') +var utils = require('./utils') + +low._.createId = utils.createId + +var server = restify.createServer() + +server.use(restify.acceptParser(server.acceptable)) +server.use(restify.queryParser()) +server.use(restify.bodyParser()) +server.use(restify.CORS()) +server.use(restify.jsonp()) +server.use(restify.gzipResponse()) + +routes = {} + +// GET /db +routes.db = function(req, res, next) { + res.send(low.db) + next() +} + +// GET /:resource?attr=&attr= +routes.list = function(req, res, next) { + var properties = {} + var query + + Object.keys(req.query).forEach(function (key) { + var value = req.query[key] + properties[key] = utils.toNative(value) + }) + + if (_(properties).isEmpty()) { + query = low(req.params.resource) + } else { + query = low(req.params.resource).where(properties) + } + + res.send(query.value()) + next() +} + +// GET /:parent/:parentId/:resource +routes.nestedList = function(req, res, next) { + var properties = {} + var resource + + // Set parentID + properties[req.params.parent.slice(0, - 1) + 'Id'] = +req.params.parentId + + // Filter using parentID + resource = low(req.params.resource) + .where(properties) + .value() + + res.send(resource) + next() +} + +// GET /:resource/:id +routes.show = function(req, res, next) { + var resource = low(req.params.resource) + .get(+req.params.id) + .value() + + res.send(resource) + next() +} + +// POST /:resource +routes.create = function(req, res, next) { + var resource = low(req.params.resource) + .insert(req.body) + .value() + + res.send(resource) + next() +} + +// PUT /:resource/:id +// PATCH /:resource/:id +routes.update = function(req, res, next) { + var resource = low(req.params.resource) + .update(+req.params.id, req.body) + .value() + + res.send(resource) + next() +} + +// DELETE /:resource/:id +routes.destroy = function(req, res, next) { + try { + low(req.params.resource).remove(+req.params.id) + utils.clean() + + res.send(204) + next() + } catch(e) { + console.trace(e) + } +} + +server.get('/db', routes.db) +server.get('/:resource', routes.list) +server.get('/:parent/:parentId/:resource', routes.nestedList) +server.get('/:resource/:id', routes.show) +server.post('/:resource', routes.create) +server.put('/:resource/:id', routes.update) +server.patch('/:resource/:id', routes.update) +server.del('/:resource/:id', routes.destroy) + +server.get('/', restify.serveStatic({ + directory: './public', + default: 'index.html' +})); + +server.on('after', function (req, res, route, err) { + var latency = Date.now() - req.time() + console.log('%s %s %s - %sms', + req.method, req.url, res.statusCode, latency + ) +}) + +module.exports = server \ No newline at end of file diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..45bc2aa --- /dev/null +++ b/src/utils.js @@ -0,0 +1,60 @@ +var low = require('low') +var _ = require('underscore') +_.mixin(require('underscore.inflections')) + + +// Turns string to native. +// Example: +// 'true' -> true +// '1' -> 1 +function toNative(value) { + if (value === 'true' || value === 'false') { + return value === 'true' + } else if (!isNaN(+value)) { + return +value + } else { + return value + } +} + +// Creates incremental id. +function createId(coll) { + if (_.isEmpty(coll)) { + return 1 + } else { + return _.max(coll, function(doc) { + return doc.id + }).id + 1 + } +} + +// Removes empty relations +function clean() { + var toBeRemoved = [] + + _(low.db).each(function(coll, collName) { + _(coll).each(function(doc) { + _(doc).each(function(value, key) { + if (/Id$/.test(key)) { + var reference = _.pluralize(key.slice(0, - 2)) + if (!_.isUndefined(low(reference).get(doc[key]).value())) { + toBeRemoved.push({ + collName: collName, + id: doc.id + }) + } + } + }) + }) + }) + + _(toBeRemoved).each(function(item) { + low(item.collName).remove(item.id); + }) +} + +module.exports = { + toNative: toNative, + createId: createId, + clean: clean +} \ No newline at end of file diff --git a/test/read-only.js b/test/read-only.js deleted file mode 100644 index aca0e33..0000000 --- a/test/read-only.js +++ /dev/null @@ -1,138 +0,0 @@ -var request = require('supertest'), - assert = require('assert'), - server = require('../server'), - fixture = require('./fixture'), - db, - app; - -describe('Read only routes', function() { - - beforeEach(function() { - db = fixture(); - app = server.createApp(db, { readOnly: true }); - }); - - describe('GET /:resource', function() { - it('should respond with json and resources and corresponding resources', function(done) { - request(app) - .get('/posts') - .expect('Content-Type', /json/) - .expect(db.posts) - .expect(200, done); - }); - }); - - describe('GET /:resource?attr=&attr=', function() { - it('should respond with json and filter resources', function(done) { - request(app) - .get('/comments?postId=1&published=true') - .expect('Content-Type', /json/) - .expect([db.comments[0]]) - .expect(200, done); - }); - }); - - describe('GET /:parent/:parentId/:resource', function() { - it('should respond with json and corresponding nested resources', function(done) { - request(app) - .get('/posts/1/comments') - .expect('Content-Type', /json/) - .expect([db.comments[0], db.comments[1]]) - .expect(200, done); - }); - }); - - - describe('GET /:resource/:id', function() { - it('should respond with json and corresponding resource', function(done) { - request(app) - .get('/posts/1') - .expect('Content-Type', /json/) - .expect(db.posts[0]) - .expect(200, done); - }); - }); - - describe('GET /db', function() { - it('should respond with json and full database', function(done) { - request(app) - .get('/db') - .expect('Content-Type', /json/) - .expect(db) - .expect(200, done); - }); - }); - - describe('POST /:resource', function() { - it('should respond with fake json and not create a resource', function(done) { - request(app) - .post('/posts') - .send({body: '...'}) - .expect('Content-Type', /json/) - .expect(200) - .end(function(err, res){ - if (err) return done(err); - assert(res.body.hasOwnProperty('id')); - assert.equal(res.body.body, '...'); - assert.equal(db.posts.length, 2); - done() - }); - }); - }); - - describe('PUT /:resource/:id', function() { - it('should respond with fake json and not update resource', function(done) { - request(app) - .put('/posts/1') - .send({id: 999, body: '...'}) - .expect('Content-Type', /json/) - .expect({id: 999, body: '...'}) - .expect(200) - .end(function(err, res){ - if (err) return done(err); - // Checking that first post wasn't updated - assert.deepEqual(db.posts[0], {id: 1, body: 'foo'}); - done() - }); - }); - }); - - describe('PATCH /:resource/:id', function() { - it('should respond with fake json and not update resource', function(done) { - request(app) - .patch('/posts/1') - .send({body: '...'}) - .expect('Content-Type', /json/) - .expect({id: 1, body: '...'}) - .expect(200) - .end(function(err, res){ - if (err) return done(err); - // Checking that first post wasn't updated - assert.deepEqual(db.posts[0], {id: 1, body: 'foo'}); - done() - }); - }); - }); - - describe('DELETE /:resource/:id', function() { - it('should respond with empty data and not destroy resource', function(done) { - request(app) - .del('/posts/1') - .expect(204) - .end(function(err, res){ - if (err) return done(err); - assert.equal(db.posts.length, 2); - assert.equal(db.comments.length, 4); - done() - }); - }); - }); - - describe('OPTIONS /:resource/:id', function() { - it('should respond with empty data and not destroy resource', function(done) { - request(app) - .options('/posts/1') - .expect(204, done); - }); - }); -}); \ No newline at end of file diff --git a/test/read-write.js b/test/server.js similarity index 60% rename from test/read-write.js rename to test/server.js index 388d4da..28b16ad 100644 --- a/test/read-write.js +++ b/test/server.js @@ -1,130 +1,129 @@ -var request = require('supertest'), - assert = require('assert'), - server = require('../server'), - routes = require('../routes/read-write'), - fixture = require('./fixture'), - db, - app; +var request = require('supertest') +var assert = require('assert') +var low = require('low') +var server = require('../src/server') +var fixture = require('./fixture') +var db -describe('Read write routes', function() { +describe('Server', function() { beforeEach(function() { - db = fixture(); - app = server.createApp(db); - }); - - describe('GET /:resource', function() { - it('should respond with json and corresponding resources', function(done) { - request(app) - .get('/posts') - .expect('Content-Type', /json/) - .expect(db.posts) - .expect(200, done); - }); - }); - - describe('GET /:resource?attr=&attr=', function() { - it('should respond with json and filter resources', function(done) { - request(app) - .get('/comments?postId=1&published=true') - .expect('Content-Type', /json/) - .expect([db.comments[0]]) - .expect(200, done); - }); - }); - - describe('GET /:parent/:parentId/:resource', function() { - it('should respond with json and corresponding nested resources', function(done) { - request(app) - .get('/posts/1/comments') - .expect('Content-Type', /json/) - .expect([ - db.comments[0], - db.comments[1] - ]) - .expect(200, done); - }); - }); - - describe('GET /:resource/:id', function() { - it('should respond with json and corresponding resource', function(done) { - request(app) - .get('/posts/1') - .expect('Content-Type', /json/) - .expect(db.posts[0]) - .expect(200, done); - }); - }); + low.db = fixture() + server + }) describe('GET /db', function() { it('should respond with json and full database', function(done) { - request(app) + request(server) .get('/db') .expect('Content-Type', /json/) - .expect(db) - .expect(200, done); - }); - }); + .expect(low.db) + .expect(200, done) + }) + }) + + describe('GET /:resource', function() { + it('should respond with json and corresponding resources', function(done) { + request(server) + .get('/posts') + .expect('Content-Type', /json/) + .expect(low.db.posts) + .expect(200, done) + }) + }) + + describe('GET /:resource?attr=&attr=', function() { + it('should respond with json and filter resources', function(done) { + request(server) + .get('/comments?postId=1&published=true') + .expect('Content-Type', /json/) + .expect([low.db.comments[0]]) + .expect(200, done) + }) + }) + + describe('GET /:parent/:parentId/:resource', function() { + it('should respond with json and corresponding nested resources', function(done) { + request(server) + .get('/posts/1/comments') + .expect('Content-Type', /json/) + .expect([ + low.db.comments[0], + low.db.comments[1] + ]) + .expect(200, done) + }) + }) + + describe('GET /:resource/:id', function() { + it('should respond with json and corresponding resource', function(done) { + request(server) + .get('/posts/1') + .expect('Content-Type', /json/) + .expect(low.db.posts[0]) + .expect(200, done) + }) + }) describe('POST /:resource', function() { it('should respond with json and create a resource', function(done) { - request(app) + request(server) .post('/posts') .send({body: 'foo'}) .expect('Content-Type', /json/) .expect({id: 3, body: 'foo'}) .expect(200) .end(function(err, res){ - if (err) return done(err); - assert.equal(db.posts.length, 3); - done(); - }); - }); - }); + if (err) return done(err) + assert.equal(low.db.posts.length, 3) + done() + }) + }) + }) describe('PUT /:resource/:id', function() { it('should respond with json and update resource', function(done) { - request(app) + request(server) .put('/posts/1') .send({id: 1, body: 'foo'}) .expect('Content-Type', /json/) .expect({id: 1, body: 'foo'}) .expect(200) .end(function(err, res){ - if (err) return done(err); - assert.deepEqual(db.posts[0], {id: 1, body: 'foo'}); - done(); - }); - }); - }); + if (err) return done(err) + assert.deepEqual(low.db.posts[0], {id: 1, body: 'foo'}) + done() + }) + }) + }) describe('PATCH /:resource/:id', function() { it('should respond with json and update resource', function(done) { - request(app) + request(server) .patch('/posts/1') .send({body: 'bar'}) .expect('Content-Type', /json/) .expect({id: 1, body: 'bar'}) .expect(200) .end(function(err, res){ - if (err) return done(err); - assert.deepEqual(db.posts[0], {id: 1, body: 'bar'}); - done(); - }); - }); - }); + if (err) return done(err) + assert.deepEqual(low.db.posts[0], {id: 1, body: 'bar'}) + done() + }) + }) + }) describe('DELETE /:resource/:id', function() { it('should respond with empty data, destroy resource and dependent resources', function(done) { - request(app) + request(server) .del('/posts/1') .expect(204) .end(function(err, res){ - if (err) return done(err); - assert.equal(db.posts.length, 1); - assert.equal(db.comments.length, 2); - done(); - }); - }); - }); -}); \ No newline at end of file + if (err) return done(err) + assert.equal(low.db.posts.length, 1) + assert.equal(low.db.comments.length, 2) + done() + }) + }) + }) +}) \ No newline at end of file diff --git a/test/static.js b/test/static.js index f623d76..88dcf93 100644 --- a/test/static.js +++ b/test/static.js @@ -1,18 +1,12 @@ -var request = require('supertest'), - assert = require('assert'), - server = require('../server'), - routes = require('../routes/read-write'), - app; +var request = require('supertest') +var assert = require('assert') +var server = require('../src/server') describe('Static routes', function() { - beforeEach(function() { - app = server.createApp({}, routes); - }); - describe('GET /', function() { it('should respond with html', function(done) { - request(app) + request(server) .get('/') .expect('Content-Type', /html/) .expect(200, done); @@ -21,7 +15,7 @@ describe('Static routes', function() { describe('GET /stylesheets/style.css', function() { it('should respond with css', function(done) { - request(app) + request(server) .get('/stylesheets/style.css') .expect('Content-Type', /css/) .expect(200, done); diff --git a/utils/db-mixins.js b/utils/db-mixins.js deleted file mode 100644 index d06a395..0000000 --- a/utils/db-mixins.js +++ /dev/null @@ -1,86 +0,0 @@ -(function(root) { - - var _ = root._ || require('underscore'); - - if (!root._) { - _.mixin(require('underscore.inflections')); - } - - function get(db, table, id) { - return _.find(db[table], function (row) { - return row.id === id - }); - } - - function exist(db, table, id) { - return !_.isUndefined(_.get(db, table, id)); - } - - function createId(db, table) { - if (_.isEmpty(db[table])) { - return 1; - } else { - return _.max(db[table], function(row) { - return row.id; - }).id + 1; - } - } - - function create(db, table, obj) { - var clone = _.clone(obj); - - if (_.isUndefined(clone.id)) clone.id = _.createId(db, table); - - db[table].push(clone); - - return clone; - } - - function update(db, table, id, attrs) { - var row = get(db, table, id), - updatedRow = _.extend(row, attrs), - index = _.indexOf(db[table], row); - - db[table][index] = updatedRow; - } - - function clean(db) { - var toBeRemoved = []; - - _(db).each(function(table, tableName) { - _(table).each(function(row) { - _(row).each(function(value, key) { - if (/Id$/.test(key)) { - var reference = _.pluralize(key.slice(0, - 2)); - if (!_.exist(db, reference, row[key])) { - toBeRemoved.push({ - tableName: tableName, - id: row.id - }); - } - } - }); - }); - }); - - _(toBeRemoved).each(function(row) { - _.remove(db, row.tableName, row.id); - }); - } - - function remove(db, table, id) { - var newTable = _.reject(db[table], function(row) { - return row.id === id; - }); - - db[table] = newTable; - } - - _.get = get; - _.exist = exist; - _.createId = createId; - _.create = create; - _.update = update; - _.clean = clean; - _.remove = remove; -})(this); \ No newline at end of file diff --git a/utils/logger.js b/utils/logger.js deleted file mode 100644 index 20a1200..0000000 --- a/utils/logger.js +++ /dev/null @@ -1,11 +0,0 @@ -var logan = require('logan'); - -logan.set({ - error: ['%', 'red'], - success: ['%', 'green'], - info: ['%', 'grey'], - notice: ['%', 'yellow'], - url: [' http://localhost:%/'.grey + '%'.cyan, '.'] -}) - -module.exports = logan \ No newline at end of file diff --git a/utils/utils.js b/utils/utils.js deleted file mode 100644 index c70615f..0000000 --- a/utils/utils.js +++ /dev/null @@ -1,11 +0,0 @@ -function toNative(value) { - if (value === 'true' || value === 'false') { - return value === 'true'; - } else if (!isNaN(+value)) { - return +value; - } else { - return value; - } -} - -exports.toNative = toNative; \ No newline at end of file