This commit is contained in:
typicode
2020-11-15 23:50:27 +01:00
parent 8928b6af34
commit f1621c38fe
31 changed files with 481 additions and 557 deletions

View File

@ -8,7 +8,7 @@ module.exports = {
singleQuote: true, singleQuote: true,
semi: false, semi: false,
}, },
] ],
}, },
env: { jest: true } env: { jest: true },
} }

View File

@ -1,4 +1,4 @@
module.exports = function(req, res, next) { module.exports = function (req, res, next) {
res.header('X-Hello', 'World') res.header('X-Hello', 'World')
next() next()
} }

View File

@ -1,4 +1,4 @@
module.exports = function(req, res, next) { module.exports = function (req, res, next) {
res.header('X-Konnichiwa', 'Sekai') res.header('X-Konnichiwa', 'Sekai')
next() next()
} }

View File

@ -1,4 +1,4 @@
module.exports = function(req, res, next) { module.exports = function (req, res, next) {
res.header('name', req.body.name) res.header('name', req.body.name)
next() next()
} }

View File

@ -1,37 +1,37 @@
// Need some fake data for the gzip test to work // Need some fake data for the gzip test to work
module.exports = function() { module.exports = function () {
return { return {
posts: [ posts: [
{ {
id: 1, id: 1,
content: content:
"Oh! The garbage chute was a really wonderful idea. What an incredible smell you've discovered! Let's get out of here! Get away from there... No! wait! Will you forget it? I already tried it. It's magnetically sealed! Put that thing away! You're going to get us all killed. Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us. It could be worst... It's worst. There's something alive in here! That's your imagination. Something just moves past my leg! Look! Did you see that? What? Help!" "Oh! The garbage chute was a really wonderful idea. What an incredible smell you've discovered! Let's get out of here! Get away from there... No! wait! Will you forget it? I already tried it. It's magnetically sealed! Put that thing away! You're going to get us all killed. Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us. It could be worst... It's worst. There's something alive in here! That's your imagination. Something just moves past my leg! Look! Did you see that? What? Help!",
}, },
{ {
id: 2, id: 2,
content: content:
"Oh! The garbage chute was a really wonderful idea. What an incredible smell you've discovered! Let's get out of here! Get away from there... No! wait! Will you forget it? I already tried it. It's magnetically sealed! Put that thing away! You're going to get us all killed. Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us. It could be worst... It's worst. There's something alive in here! That's your imagination. Something just moves past my leg! Look! Did you see that? What? Help!" "Oh! The garbage chute was a really wonderful idea. What an incredible smell you've discovered! Let's get out of here! Get away from there... No! wait! Will you forget it? I already tried it. It's magnetically sealed! Put that thing away! You're going to get us all killed. Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us. It could be worst... It's worst. There's something alive in here! That's your imagination. Something just moves past my leg! Look! Did you see that? What? Help!",
}, },
{ {
id: 3, id: 3,
content: content:
"Oh! The garbage chute was a really wonderful idea. What an incredible smell you've discovered! Let's get out of here! Get away from there... No! wait! Will you forget it? I already tried it. It's magnetically sealed! Put that thing away! You're going to get us all killed. Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us. It could be worst... It's worst. There's something alive in here! That's your imagination. Something just moves past my leg! Look! Did you see that? What? Help!" "Oh! The garbage chute was a really wonderful idea. What an incredible smell you've discovered! Let's get out of here! Get away from there... No! wait! Will you forget it? I already tried it. It's magnetically sealed! Put that thing away! You're going to get us all killed. Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us. It could be worst... It's worst. There's something alive in here! That's your imagination. Something just moves past my leg! Look! Did you see that? What? Help!",
}, },
{ {
id: 4, id: 4,
content: content:
"Oh! The garbage chute was a really wonderful idea. What an incredible smell you've discovered! Let's get out of here! Get away from there... No! wait! Will you forget it? I already tried it. It's magnetically sealed! Put that thing away! You're going to get us all killed. Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us. It could be worst... It's worst. There's something alive in here! That's your imagination. Something just moves past my leg! Look! Did you see that? What? Help!" "Oh! The garbage chute was a really wonderful idea. What an incredible smell you've discovered! Let's get out of here! Get away from there... No! wait! Will you forget it? I already tried it. It's magnetically sealed! Put that thing away! You're going to get us all killed. Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us. It could be worst... It's worst. There's something alive in here! That's your imagination. Something just moves past my leg! Look! Did you see that? What? Help!",
}, },
{ {
id: 5, id: 5,
content: content:
"Oh! The garbage chute was a really wonderful idea. What an incredible smell you've discovered! Let's get out of here! Get away from there... No! wait! Will you forget it? I already tried it. It's magnetically sealed! Put that thing away! You're going to get us all killed. Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us. It could be worst... It's worst. There's something alive in here! That's your imagination. Something just moves past my leg! Look! Did you see that? What? Help!" "Oh! The garbage chute was a really wonderful idea. What an incredible smell you've discovered! Let's get out of here! Get away from there... No! wait! Will you forget it? I already tried it. It's magnetically sealed! Put that thing away! You're going to get us all killed. Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us. It could be worst... It's worst. There's something alive in here! That's your imagination. Something just moves past my leg! Look! Did you see that? What? Help!",
}, },
{ {
id: 6, id: 6,
content: content:
"Oh! The garbage chute was a really wonderful idea. What an incredible smell you've discovered! Let's get out of here! Get away from there... No! wait! Will you forget it? I already tried it. It's magnetically sealed! Put that thing away! You're going to get us all killed. Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us. It could be worst... It's worst. There's something alive in here! That's your imagination. Something just moves past my leg! Look! Did you see that? What? Help!" "Oh! The garbage chute was a really wonderful idea. What an incredible smell you've discovered! Let's get out of here! Get away from there... No! wait! Will you forget it? I already tried it. It's magnetically sealed! Put that thing away! You're going to get us all killed. Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us. It could be worst... It's worst. There's something alive in here! That's your imagination. Something just moves past my leg! Look! Did you see that? What? Help!",
} },
] ],
} }
} }

View File

@ -1,311 +1,308 @@
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const cp = require('child_process') const cp = require('child_process')
const assert = require('assert') const assert = require('assert')
const supertest = require('supertest') const supertest = require('supertest')
const osTmpdir = require('os-tmpdir') const osTmpdir = require('os-tmpdir')
const tempWrite = require('temp-write') const tempWrite = require('temp-write')
const mkdirp = require('mkdirp') const mkdirp = require('mkdirp')
const rimraf = require('rimraf') const rimraf = require('rimraf')
const serverReady = require('server-ready') const serverReady = require('server-ready')
let PORT = 3100 let PORT = 3100
const middlewareFiles = { const middlewareFiles = {
en: './../../__fixtures__/middlewares/en.js', en: './../../__fixtures__/middlewares/en.js',
jp: './../../__fixtures__/middlewares/jp.js', jp: './../../__fixtures__/middlewares/jp.js',
postbody: './../../__fixtures__/middlewares/postbody.js' postbody: './../../__fixtures__/middlewares/postbody.js',
} }
const bin = path.join(__dirname, '../../lib/cli/bin') const bin = path.join(__dirname, '../../lib/cli/bin')
function cli(args) { function cli(args) {
return cp.spawn('node', ['--', bin, '-p', PORT].concat(args), { return cp.spawn('node', ['--', bin, '-p', PORT].concat(args), {
cwd: __dirname, cwd: __dirname,
stdio: ['pipe', process.stdout, process.stderr] stdio: ['pipe', process.stdout, process.stderr],
}) })
} }
describe('cli', () => { describe('cli', () => {
let child let child
let request let request
let dbFile let dbFile
let routesFile let routesFile
beforeEach(() => { beforeEach(() => {
dbFile = tempWrite.sync( dbFile = tempWrite.sync(
JSON.stringify({ JSON.stringify({
posts: [{ id: 1 }, { _id: 2 }], posts: [{ id: 1 }, { _id: 2 }],
comments: [{ id: 1, post_id: 1 }] comments: [{ id: 1, post_id: 1 }],
}), }),
'db.json' 'db.json'
) )
routesFile = tempWrite.sync( routesFile = tempWrite.sync(
JSON.stringify({ '/blog/*': '/$1' }), JSON.stringify({ '/blog/*': '/$1' }),
'routes.json' 'routes.json'
) )
++PORT ++PORT
request = supertest(`http://localhost:${PORT}`) request = supertest(`http://localhost:${PORT}`)
}) })
afterEach(() => { afterEach(() => {
child.kill('SIGKILL') child.kill('SIGKILL')
}) })
describe('db.json', () => { describe('db.json', () => {
beforeEach(done => { beforeEach((done) => {
child = cli([dbFile]) child = cli([dbFile])
serverReady(PORT, done) serverReady(PORT, done)
}) })
test('should support JSON file', done => { test('should support JSON file', (done) => {
request.get('/posts').expect(200, done) request.get('/posts').expect(200, done)
}) })
test('should send CORS headers', done => { test('should send CORS headers', (done) => {
const origin = 'http://example.com' const origin = 'http://example.com'
request request
.get('/posts') .get('/posts')
.set('Origin', origin) .set('Origin', origin)
.expect('access-control-allow-origin', origin) .expect('access-control-allow-origin', origin)
.expect(200, done) .expect(200, done)
}) })
test('should update JSON file', done => { test('should update JSON file', (done) => {
request request
.post('/posts') .post('/posts')
.send({ title: 'hello' }) .send({ title: 'hello' })
.end(() => { .end(() => {
setTimeout(() => { setTimeout(() => {
const str = fs.readFileSync(dbFile, 'utf8') const str = fs.readFileSync(dbFile, 'utf8')
assert(str.indexOf('hello') !== -1) assert(str.indexOf('hello') !== -1)
done() done()
}, 1000) }, 1000)
}) })
}) })
}) })
describe('seed.js', () => { describe('seed.js', () => {
beforeEach(done => { beforeEach((done) => {
child = cli(['../../__fixtures__/seed.js']) child = cli(['../../__fixtures__/seed.js'])
serverReady(PORT, done) serverReady(PORT, done)
}) })
test('should support JS file', done => { test('should support JS file', (done) => {
request.get('/posts').expect(200, done) request.get('/posts').expect(200, done)
}) })
}) })
describe('remote db', () => { describe('remote db', () => {
beforeEach(done => { beforeEach((done) => {
child = cli(['https://jsonplaceholder.typicode.com/db']) child = cli(['https://jsonplaceholder.typicode.com/db'])
serverReady(PORT, done) serverReady(PORT, done)
}) })
test('should support URL file', done => { test('should support URL file', (done) => {
request.get('/posts').expect(200, done) request.get('/posts').expect(200, done)
}) })
}) })
describe('db.json -r routes.json -m middleware.js -i _id --foreignKeySuffix _id --read-only', () => { describe('db.json -r routes.json -m middleware.js -i _id --foreignKeySuffix _id --read-only', () => {
beforeEach(done => { beforeEach((done) => {
child = cli([ child = cli([
dbFile, dbFile,
'-r', '-r',
routesFile, routesFile,
'-m', '-m',
middlewareFiles.en, middlewareFiles.en,
'-i', '-i',
'_id', '_id',
'--read-only', '--read-only',
'--foreignKeySuffix', '--foreignKeySuffix',
'_id' '_id',
]) ])
serverReady(PORT, done) serverReady(PORT, done)
}) })
test('should use routes.json and _id as the identifier', done => { test('should use routes.json and _id as the identifier', (done) => {
request.get('/blog/posts/2').expect(200, done) request.get('/blog/posts/2').expect(200, done)
}) })
test('should use _id as foreignKeySuffix', async () => { test('should use _id as foreignKeySuffix', async () => {
const response = await request.get('/posts/1/comments') const response = await request.get('/posts/1/comments')
assert.strictEqual(response.body.length, 1) assert.strictEqual(response.body.length, 1)
}) })
test('should apply middlewares', done => { test('should apply middlewares', (done) => {
request.get('/blog/posts/2').expect('X-Hello', 'World', done) request.get('/blog/posts/2').expect('X-Hello', 'World', done)
}) })
test('should allow only GET requests', done => { test('should allow only GET requests', (done) => {
request.post('/blog/posts').expect(403, done) request.post('/blog/posts').expect(403, done)
}) })
}) })
describe('db.json -m first-middleware.js second-middleware.js', () => { describe('db.json -m first-middleware.js second-middleware.js', () => {
beforeEach(done => { beforeEach((done) => {
child = cli([dbFile, '-m', middlewareFiles.en, middlewareFiles.jp]) child = cli([dbFile, '-m', middlewareFiles.en, middlewareFiles.jp])
serverReady(PORT, done) serverReady(PORT, done)
}) })
test('should apply all middlewares', done => { test('should apply all middlewares', (done) => {
request request
.get('/posts') .get('/posts')
.expect('X-Hello', 'World') .expect('X-Hello', 'World')
.expect('X-Konnichiwa', 'Sekai', done) .expect('X-Konnichiwa', 'Sekai', done)
}) })
}) })
describe('db.json -m postbody-middleware.js', () => { describe('db.json -m postbody-middleware.js', () => {
beforeEach(done => { beforeEach((done) => {
child = cli([dbFile, '-m', middlewareFiles.postbody]) child = cli([dbFile, '-m', middlewareFiles.postbody])
serverReady(PORT, done) serverReady(PORT, done)
}) })
test('should have post body in middleware', done => { test('should have post body in middleware', (done) => {
request request.post('/posts').send({ name: 'test' }).expect('name', 'test', done)
.post('/posts') })
.send({ name: 'test' }) })
.expect('name', 'test', done)
}) describe('db.json -d 1000', () => {
}) beforeEach((done) => {
child = cli([dbFile, '-d', 1000])
describe('db.json -d 1000', () => { serverReady(PORT, done)
beforeEach(done => { })
child = cli([dbFile, '-d', 1000])
serverReady(PORT, done) test('should delay response', (done) => {
}) const start = new Date()
request.get('/posts').expect(200, function (err) {
test('should delay response', done => { const end = new Date()
const start = new Date() done(end - start > 1000 ? err : new Error("Request wasn't delayed"))
request.get('/posts').expect(200, function(err) { })
const end = new Date() })
done(end - start > 1000 ? err : new Error("Request wasn't delayed")) })
})
}) describe('db.json -s ../../__fixtures__/public -S /some/path/snapshots', () => {
}) const snapshotsDir = path.join(osTmpdir(), 'snapshots')
const publicDir = '../../__fixtures__/public'
describe('db.json -s ../../__fixtures__/public -S /some/path/snapshots', () => {
const snapshotsDir = path.join(osTmpdir(), 'snapshots') beforeEach((done) => {
const publicDir = '../../__fixtures__/public' rimraf.sync(snapshotsDir)
mkdirp.sync(snapshotsDir)
beforeEach(done => {
rimraf.sync(snapshotsDir) child = cli([dbFile, '-s', publicDir, '-S', snapshotsDir])
mkdirp.sync(snapshotsDir) serverReady(PORT, () => {
child.stdin.write('s\n')
child = cli([dbFile, '-s', publicDir, '-S', snapshotsDir]) setTimeout(done, 100)
serverReady(PORT, () => { })
child.stdin.write('s\n') })
setTimeout(done, 100)
}) test('should serve ../../__fixtures__/public', (done) => {
}) request.get('/').expect(/Hello/, done)
})
test('should serve ../../__fixtures__/public', done => {
request.get('/').expect(/Hello/, done) test('should save a snapshot in snapshots dir', () => {
}) assert.strictEqual(fs.readdirSync(snapshotsDir).length, 1)
})
test('should save a snapshot in snapshots dir', () => { })
assert.strictEqual(fs.readdirSync(snapshotsDir).length, 1)
}) describe('../../__fixtures__/seed.json --no-cors=true', () => {
}) beforeEach((done) => {
child = cli(['../../__fixtures__/seed.js', '--no-cors=true'])
describe('../../__fixtures__/seed.json --no-cors=true', () => { serverReady(PORT, done)
beforeEach(done => { })
child = cli(['../../__fixtures__/seed.js', '--no-cors=true'])
serverReady(PORT, done) test('should not send Access-Control-Allow-Origin headers', (done) => {
}) const origin = 'http://example.com'
test('should not send Access-Control-Allow-Origin headers', done => { request
const origin = 'http://example.com' .get('/posts')
.set('Origin', origin)
request .expect(200)
.get('/posts') .end((err, res) => {
.set('Origin', origin) if (err) {
.expect(200) done(err)
.end((err, res) => { }
if (err) { if ('access-control-allow-origin' in res.headers) {
done(err) done(new Error('CORS headers were not excluded from response'))
} } else {
if ('access-control-allow-origin' in res.headers) { done()
done(new Error('CORS headers were not excluded from response')) }
} else { })
done() })
} })
})
}) describe('../../__fixtures__/seed.json --no-gzip=true', () => {
}) beforeEach((done) => {
child = cli(['../../__fixtures__/seed.js', '--no-gzip=true'])
describe('../../__fixtures__/seed.json --no-gzip=true', () => { serverReady(PORT, done)
beforeEach(done => { })
child = cli(['../../__fixtures__/seed.js', '--no-gzip=true'])
serverReady(PORT, done) test('should not set Content-Encoding to gzip', (done) => {
}) request
.get('/posts')
test('should not set Content-Encoding to gzip', done => { .expect(200)
request .end(function (err, res) {
.get('/posts') if (err) {
.expect(200) done(err)
.end(function(err, res) { } else if ('content-encoding' in res.headers) {
if (err) { done(new Error('Content-Encoding is set to gzip'))
done(err) } else {
} else if ('content-encoding' in res.headers) { done()
done(new Error('Content-Encoding is set to gzip')) }
} else { })
done() })
} })
})
}) describe('--watch db.json -r routes.json', () => {
}) beforeEach((done) => {
child = cli([dbFile, '-r', routesFile, '--watch'])
describe('--watch db.json -r routes.json', () => { serverReady(PORT, done)
beforeEach(done => { })
child = cli([dbFile, '-r', routesFile, '--watch'])
serverReady(PORT, done) test('should watch db file', (done) => {
}) fs.writeFileSync(dbFile, JSON.stringify({ foo: [] }))
setTimeout(() => {
test('should watch db file', done => { request.get('/foo').expect(200, done)
fs.writeFileSync(dbFile, JSON.stringify({ foo: [] })) }, 1000)
setTimeout(() => { })
request.get('/foo').expect(200, done)
}, 1000) test('should watch routes file', (done) => {
}) fs.writeFileSync(routesFile, JSON.stringify({ '/api/*': '/$1' }))
setTimeout(() => {
test('should watch routes file', done => { request.get('/api/posts').expect(200, done)
fs.writeFileSync(routesFile, JSON.stringify({ '/api/*': '/$1' })) }, 1000)
setTimeout(() => { })
request.get('/api/posts').expect(200, done) })
}, 1000)
}) describe('non existent db.json', () => {
}) beforeEach((done) => {
fs.unlinkSync(dbFile)
describe('non existent db.json', () => { child = cli([dbFile])
beforeEach(done => { serverReady(PORT, done)
fs.unlinkSync(dbFile) })
child = cli([dbFile])
serverReady(PORT, done) test("should create JSON file if it doesn't exist", (done) => {
}) request.get('/posts').expect(200, done)
})
test("should create JSON file if it doesn't exist", done => { })
request.get('/posts').expect(200, done)
}) describe('db.json with error', () => {
}) beforeEach(() => {
dbFile = tempWrite.sync(JSON.stringify({ 'a/b': [] }), 'db-error.json')
describe('db.json with error', () => { })
beforeEach(() => {
dbFile = tempWrite.sync(JSON.stringify({ 'a/b': [] }), 'db-error.json') test('should exit with an error', (done) => {
}) child = cli([dbFile])
child.on('exit', (code) => {
test('should exit with an error', done => { if (code === 1) {
child = cli([dbFile]) return done()
child.on('exit', code => { }
if (code === 1) { return done(new Error('should exit with error code'))
return done() })
} })
return done(new Error('should exit with error code')) })
}) })
})
})
})

View File

@ -18,9 +18,9 @@ describe('mixins', () => {
{ id: 1, postId: 1 }, { id: 1, postId: 1 },
// Comments below references a post that doesn't exist // Comments below references a post that doesn't exist
{ id: 2, postId: 2 }, { id: 2, postId: 2 },
{ id: 3, postId: 2 } { id: 3, postId: 2 },
], ],
photos: [{ id: '1' }, { id: '2' }] photos: [{ id: '1' }, { id: '2' }],
} }
}) })
@ -28,7 +28,7 @@ describe('mixins', () => {
test('should return removable documents', () => { test('should return removable documents', () => {
const expected = [ const expected = [
{ name: 'comments', id: 2 }, { name: 'comments', id: 2 },
{ name: 'comments', id: 3 } { name: 'comments', id: 3 },
] ]
assert.deepStrictEqual( assert.deepStrictEqual(
@ -40,7 +40,7 @@ describe('mixins', () => {
test('should support custom foreignKeySuffix', () => { test('should support custom foreignKeySuffix', () => {
const expected = [ const expected = [
{ name: 'comments', id: 2 }, { name: 'comments', id: 2 },
{ name: 'comments', id: 3 } { name: 'comments', id: 3 },
] ]
assert.deepStrictEqual( assert.deepStrictEqual(

View File

@ -12,7 +12,7 @@ describe('Fake server', () => {
db.posts = [ db.posts = [
{ id: 1, body: 'foo' }, { id: 1, body: 'foo' },
{ id: 2, body: 'bar' } { id: 2, body: 'bar' },
] ]
db.comments = [ db.comments = [
@ -20,7 +20,7 @@ describe('Fake server', () => {
{ id: 2, body: 'bar', published: false, postId: 1, userId: 2 }, { id: 2, body: 'bar', published: false, postId: 1, userId: 2 },
{ id: 3, body: 'baz', published: false, postId: 2, userId: 1 }, { id: 3, body: 'baz', published: false, postId: 2, userId: 1 },
{ id: 4, body: 'qux', published: true, postId: 2, userId: 2 }, { id: 4, body: 'qux', published: true, postId: 2, userId: 2 },
{ id: 5, body: 'quux', published: false, postId: 2, userId: 1 } { id: 5, body: 'quux', published: false, postId: 2, userId: 1 },
] ]
server = jsonServer.create() server = jsonServer.create()
@ -87,10 +87,7 @@ describe('Fake server', () => {
describe('DELETE /:resource/:id', () => { describe('DELETE /:resource/:id', () => {
test('should not destroy resource', async () => { test('should not destroy resource', async () => {
await request(server) await request(server).del('/posts/1').expect({}).expect(200)
.del('/posts/1')
.expect({})
.expect(200)
assert.strictEqual(db.posts.length, 2) assert.strictEqual(db.posts.length, 2)
}) })
}) })

View File

@ -13,13 +13,13 @@ describe('Server with custom foreign key', () => {
db.posts = [ db.posts = [
{ id: 1, body: 'foo' }, { id: 1, body: 'foo' },
{ id: 2, body: 'bar' } { id: 2, body: 'bar' },
] ]
db.comments = [ db.comments = [
{ id: 1, post_id: 1 }, { id: 1, post_id: 1 },
{ id: 2, post_id: 1 }, { id: 2, post_id: 1 },
{ id: 3, post_id: 2 } { id: 3, post_id: 2 },
] ]
server = jsonServer.create() server = jsonServer.create()
@ -109,10 +109,7 @@ describe('Server with custom foreign key', () => {
describe('DELETE /:resource/:id', () => { describe('DELETE /:resource/:id', () => {
test('should respond with empty data, destroy resource and dependent resources', async () => { test('should respond with empty data, destroy resource and dependent resources', async () => {
await request(server) await request(server).del('/posts/1').expect({}).expect(200)
.del('/posts/1')
.expect({})
.expect(200)
assert.strictEqual(db.posts.length, 1) assert.strictEqual(db.posts.length, 1)
assert.strictEqual(db.comments.length, 1) assert.strictEqual(db.comments.length, 1)
}) })

View File

@ -12,7 +12,7 @@ describe('Server', () => {
'/blog/posts/:id/show': '/posts/:id', '/blog/posts/:id/show': '/posts/:id',
'/comments/special/:userId-:body': '/comments/?userId=:userId&body=:body', '/comments/special/:userId-:body': '/comments/?userId=:userId&body=:body',
'/firstpostwithcomments': '/posts/1?_embed=comments', '/firstpostwithcomments': '/posts/1?_embed=comments',
'/articles\\?_id=:id': '/posts/:id' '/articles\\?_id=:id': '/posts/:id',
} }
beforeEach(() => { beforeEach(() => {
@ -20,18 +20,18 @@ describe('Server', () => {
db.posts = [ db.posts = [
{ id: 1, body: 'foo' }, { id: 1, body: 'foo' },
{ id: 2, body: 'bar' } { id: 2, body: 'bar' },
] ]
db.tags = [ db.tags = [
{ id: 1, body: 'Technology' }, { id: 1, body: 'Technology' },
{ id: 2, body: 'Photography' }, { id: 2, body: 'Photography' },
{ id: 3, body: 'photo' } { id: 3, body: 'photo' },
] ]
db.users = [ db.users = [
{ id: 1, username: 'Jim', tel: '0123' }, { id: 1, username: 'Jim', tel: '0123' },
{ id: 2, username: 'George', tel: '123' } { id: 2, username: 'George', tel: '123' },
] ]
db.comments = [ db.comments = [
@ -39,7 +39,7 @@ describe('Server', () => {
{ id: 2, body: 'bar', published: false, postId: 1, userId: 2 }, { id: 2, body: 'bar', published: false, postId: 1, userId: 2 },
{ id: 3, body: 'baz', published: false, postId: 2, userId: 1 }, { id: 3, body: 'baz', published: false, postId: 2, userId: 1 },
{ id: 4, body: 'qux', published: true, postId: 2, userId: 2 }, { id: 4, body: 'qux', published: true, postId: 2, userId: 2 },
{ id: 5, body: 'quux', published: false, postId: 2, userId: 1 } { id: 5, body: 'quux', published: false, postId: 2, userId: 1 },
] ]
db.buyers = [ db.buyers = [
@ -51,11 +51,11 @@ describe('Server', () => {
{ id: 6, name: 'Frank', country: 'Belize', total: 10 }, { id: 6, name: 'Frank', country: 'Belize', total: 10 },
{ id: 7, name: 'Grace', country: 'Argentina', total: 1 }, { id: 7, name: 'Grace', country: 'Argentina', total: 1 },
{ id: 8, name: 'Henry', country: 'Argentina', total: 2 }, { id: 8, name: 'Henry', country: 'Argentina', total: 2 },
{ id: 9, name: 'Isabelle', country: 'Argentina', total: 3 } { id: 9, name: 'Isabelle', country: 'Argentina', total: 3 },
] ]
db.refs = [ db.refs = [
{ id: 'abcd-1234', url: 'http://example.com', postId: 1, userId: 1 } { id: 'abcd-1234', url: 'http://example.com', postId: 1, userId: 1 },
] ]
db.stringIds = [{ id: '1234' }] db.stringIds = [{ id: '1234' }]
@ -65,7 +65,7 @@ describe('Server', () => {
db.nested = [ db.nested = [
{ resource: { name: 'dewey' } }, { resource: { name: 'dewey' } },
{ resource: { name: 'cheatem' } }, { resource: { name: 'cheatem' } },
{ resource: { name: 'howe' } } { resource: { name: 'howe' } },
] ]
db.list = [ db.list = [
@ -83,7 +83,7 @@ describe('Server', () => {
{ id: 12 }, { id: 12 },
{ id: 13 }, { id: 13 },
{ id: 14 }, { id: 14 },
{ id: 15 } { id: 15 },
] ]
server = jsonServer.create() server = jsonServer.create()
@ -114,9 +114,7 @@ describe('Server', () => {
.expect(200)) .expect(200))
test('should respond with 404 if resource is not found', () => test('should respond with 404 if resource is not found', () =>
request(server) request(server).get('/undefined').expect(404))
.get('/undefined')
.expect(404))
}) })
describe('GET /:resource?attr=&attr=', () => { describe('GET /:resource?attr=&attr=', () => {
@ -277,7 +275,7 @@ describe('Server', () => {
db.buyers[5], db.buyers[5],
db.buyers[2], db.buyers[2],
db.buyers[1], db.buyers[1],
db.buyers[0] db.buyers[0],
]) ])
.expect(200)) .expect(200))
}) })
@ -321,7 +319,7 @@ describe('Server', () => {
'<http://localhost/list?_page=1&_limit=1>; rel="first"', '<http://localhost/list?_page=1&_limit=1>; rel="first"',
'<http://localhost/list?_page=1&_limit=1>; rel="prev"', '<http://localhost/list?_page=1&_limit=1>; rel="prev"',
'<http://localhost/list?_page=3&_limit=1>; rel="next"', '<http://localhost/list?_page=3&_limit=1>; rel="next"',
'<http://localhost/list?_page=15&_limit=1>; rel="last"' '<http://localhost/list?_page=15&_limit=1>; rel="last"',
].join(', ') ].join(', ')
return request(server) return request(server)
.get('/list?_page=2&_limit=1') .get('/list?_page=2&_limit=1')
@ -518,11 +516,11 @@ describe('Server', () => {
}) })
describe('GET /:resource>_delay=', () => { describe('GET /:resource>_delay=', () => {
test('should delay response', done => { test('should delay response', (done) => {
const start = new Date() const start = new Date()
request(server) request(server)
.get('/posts?_delay=1100') .get('/posts?_delay=1100')
.expect(200, function(err) { .expect(200, function (err) {
const end = new Date() const end = new Date()
done(end - start > 1000 ? err : new Error("Request wasn't delayed")) done(end - start > 1000 ? err : new Error("Request wasn't delayed"))
}) })
@ -575,12 +573,12 @@ describe('Server', () => {
}) })
describe('POST /:resource?_delay=', () => { describe('POST /:resource?_delay=', () => {
test('should delay response', done => { test('should delay response', (done) => {
const start = new Date() const start = new Date()
request(server) request(server)
.post('/posts?_delay=1100') .post('/posts?_delay=1100')
.send({ body: 'foo', booleanValue: true, integerValue: 1 }) .send({ body: 'foo', booleanValue: true, integerValue: 1 })
.expect(201, function(err) { .expect(201, function (err) {
const end = new Date() const end = new Date()
done(end - start > 1000 ? err : new Error("Request wasn't delayed")) done(end - start > 1000 ? err : new Error("Request wasn't delayed"))
}) })
@ -615,13 +613,13 @@ describe('Server', () => {
}) })
describe('PUT /:resource:id?_delay=', () => { describe('PUT /:resource:id?_delay=', () => {
test('should delay response', done => { test('should delay response', (done) => {
const start = new Date() const start = new Date()
request(server) request(server)
.put('/posts/1?_delay=1100') .put('/posts/1?_delay=1100')
.set('Accept', 'application/json') .set('Accept', 'application/json')
.send({ id: 1, booleanValue: true, integerValue: 1 }) .send({ id: 1, booleanValue: true, integerValue: 1 })
.expect(200, function(err) { .expect(200, function (err) {
const end = new Date() const end = new Date()
done(end - start > 1000 ? err : new Error("Request wasn't delayed")) done(end - start > 1000 ? err : new Error("Request wasn't delayed"))
}) })
@ -653,13 +651,13 @@ describe('Server', () => {
}) })
describe('PATCH /:resource:id?_delay=', () => { describe('PATCH /:resource:id?_delay=', () => {
test('should delay response', done => { test('should delay response', (done) => {
const start = new Date() const start = new Date()
request(server) request(server)
.patch('/posts/1?_delay=1100') .patch('/posts/1?_delay=1100')
.send({ body: 'bar' }) .send({ body: 'bar' })
.send({ id: 1, booleanValue: true, integerValue: 1 }) .send({ id: 1, booleanValue: true, integerValue: 1 })
.expect(200, function(err) { .expect(200, function (err) {
const end = new Date() const end = new Date()
done(end - start > 1000 ? err : new Error("Request wasn't delayed")) done(end - start > 1000 ? err : new Error("Request wasn't delayed"))
}) })
@ -668,10 +666,7 @@ describe('Server', () => {
describe('DELETE /:resource/:id', () => { describe('DELETE /:resource/:id', () => {
test('should respond with empty data, destroy resource and dependent resources', async () => { test('should respond with empty data, destroy resource and dependent resources', async () => {
await request(server) await request(server).del('/posts/1').expect({}).expect(200)
.del('/posts/1')
.expect({})
.expect(200)
assert.strictEqual(db.posts.length, 1) assert.strictEqual(db.posts.length, 1)
assert.strictEqual(db.comments.length, 3) assert.strictEqual(db.comments.length, 3)
}) })
@ -685,12 +680,12 @@ describe('Server', () => {
}) })
describe('DELETE /:resource:id?_delay=', () => { describe('DELETE /:resource:id?_delay=', () => {
test('should delay response', done => { test('should delay response', (done) => {
const start = new Date() const start = new Date()
request(server) request(server)
.del('/posts/1?_delay=1100') .del('/posts/1?_delay=1100')
.send({ id: 1, booleanValue: true, integerValue: 1 }) .send({ id: 1, booleanValue: true, integerValue: 1 })
.expect(200, function(err) { .expect(200, function (err) {
const end = new Date() const end = new Date()
done(end - start > 1000 ? err : new Error("Request wasn't delayed")) done(end - start > 1000 ? err : new Error("Request wasn't delayed"))
}) })
@ -740,37 +735,25 @@ describe('Server', () => {
describe('Rewriter', () => { describe('Rewriter', () => {
test('should rewrite using prefix', () => test('should rewrite using prefix', () =>
request(server) request(server).get('/api/posts/1').expect(db.posts[0]))
.get('/api/posts/1')
.expect(db.posts[0]))
test('should rewrite using params', () => test('should rewrite using params', () =>
request(server) request(server).get('/blog/posts/1/show').expect(db.posts[0]))
.get('/blog/posts/1/show')
.expect(db.posts[0]))
test('should rewrite using query without params', () => { test('should rewrite using query without params', () => {
const expectedPost = _.cloneDeep(db.posts[0]) const expectedPost = _.cloneDeep(db.posts[0])
expectedPost.comments = [db.comments[0], db.comments[1]] expectedPost.comments = [db.comments[0], db.comments[1]]
return request(server) return request(server).get('/firstpostwithcomments').expect(expectedPost)
.get('/firstpostwithcomments')
.expect(expectedPost)
}) })
test('should rewrite using params and query', () => test('should rewrite using params and query', () =>
request(server) request(server).get('/comments/special/1-quux').expect([db.comments[4]]))
.get('/comments/special/1-quux')
.expect([db.comments[4]]))
test('should rewrite query params', () => test('should rewrite query params', () =>
request(server) request(server).get('/articles?_id=1').expect(db.posts[0]))
.get('/articles?_id=1')
.expect(db.posts[0]))
test('should expose routes', () => test('should expose routes', () =>
request(server) request(server).get('/__rules').expect(rewriterRules))
.get('/__rules')
.expect(rewriterRules))
}) })
describe('router.render', () => { describe('router.render', () => {
@ -791,7 +774,7 @@ describe('Server', () => {
describe('router.db._.id', () => { describe('router.db._.id', () => {
beforeEach(() => { beforeEach(() => {
router.db.setState({ router.db.setState({
posts: [{ _id: 1 }] posts: [{ _id: 1 }],
}) })
router.db._.id = '_id' router.db._.id = '_id'

View File

@ -12,7 +12,7 @@ describe('Fake server', () => {
db.user = { db.user = {
name: 'foo', name: 'foo',
email: 'foo@example.com' email: 'foo@example.com',
} }
server = jsonServer.create() server = jsonServer.create()
@ -24,11 +24,7 @@ describe('Fake server', () => {
describe('POST /:resource', () => { describe('POST /:resource', () => {
test('should not create resource', async () => { test('should not create resource', async () => {
const user = { name: 'bar' } const user = { name: 'bar' }
await request(server) await request(server).post('/user').send(user).expect(user).expect(201)
.post('/user')
.send(user)
.expect(user)
.expect(201)
assert.notDeepStrictEqual(db.user, user) assert.notDeepStrictEqual(db.user, user)
}) })
}) })
@ -36,11 +32,7 @@ describe('Fake server', () => {
describe('PUT /:resource', () => { describe('PUT /:resource', () => {
test('should not update resource', async () => { test('should not update resource', async () => {
const user = { name: 'bar' } const user = { name: 'bar' }
await request(server) await request(server).put('/user').send(user).expect(user).expect(200)
.put('/user')
.send(user)
.expect(user)
.expect(200)
assert.notDeepStrictEqual(db.user, user) assert.notDeepStrictEqual(db.user, user)
}) })
}) })

View File

@ -11,7 +11,7 @@ describe('Server', () => {
db.user = { db.user = {
name: 'foo', name: 'foo',
email: 'foo@example.com' email: 'foo@example.com',
} }
server = jsonServer.create() server = jsonServer.create()
@ -22,31 +22,20 @@ describe('Server', () => {
describe('GET /:resource', () => { describe('GET /:resource', () => {
test('should respond with corresponding resource', () => test('should respond with corresponding resource', () =>
request(server) request(server).get('/user').expect(db.user).expect(200))
.get('/user')
.expect(db.user)
.expect(200))
}) })
describe('POST /:resource', () => { describe('POST /:resource', () => {
test('should create resource', () => { test('should create resource', () => {
const user = { name: 'bar' } const user = { name: 'bar' }
return request(server) return request(server).post('/user').send(user).expect(user).expect(201)
.post('/user')
.send(user)
.expect(user)
.expect(201)
}) })
}) })
describe('PUT /:resource', () => { describe('PUT /:resource', () => {
test('should update resource', () => { test('should update resource', () => {
const user = { name: 'bar' } const user = { name: 'bar' }
return request(server) return request(server).put('/user').send(user).expect(user).expect(200)
.put('/user')
.send(user)
.expect(user)
.expect(200)
}) })
}) })

View File

@ -12,7 +12,7 @@ describe('utils', () => {
current: 1, current: 1,
first: 1, first: 1,
next: 2, next: 2,
last: 3 last: 3,
}) })
}) })
@ -23,7 +23,7 @@ describe('utils', () => {
first: 1, first: 1,
prev: 1, prev: 1,
next: 3, next: 3,
last: 3 last: 3,
}) })
}) })
@ -33,25 +33,25 @@ describe('utils', () => {
current: 3, current: 3,
first: 1, first: 1,
prev: 2, prev: 2,
last: 3 last: 3,
}) })
}) })
test('should return an empty array if page is greater than the last page', () => { test('should return an empty array if page is greater than the last page', () => {
assert.deepStrictEqual(utils.getPage(array, 99, perPage), { assert.deepStrictEqual(utils.getPage(array, 99, perPage), {
items: [] items: [],
}) })
}) })
test('should return the array if perPage is greater than the array size', () => { test('should return the array if perPage is greater than the array size', () => {
assert.deepStrictEqual(utils.getPage(array, 1, 99), { assert.deepStrictEqual(utils.getPage(array, 1, 99), {
items: array items: array,
}) })
}) })
test('should return an empty array if the array is empty', () => { test('should return an empty array if the array is empty', () => {
assert.deepStrictEqual(utils.getPage([], 1, 1), { assert.deepStrictEqual(utils.getPage([], 1, 1), {
items: [] items: [],
}) })
}) })
}) })

View File

@ -22,52 +22,52 @@
}, },
"dependencies": { "dependencies": {
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"chalk": "^3.0.0", "chalk": "^4.1.0",
"compression": "^1.7.4", "compression": "^1.7.4",
"connect-pause": "^0.1.1", "connect-pause": "^0.1.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"errorhandler": "^1.5.1", "errorhandler": "^1.5.1",
"express": "^4.17.1", "express": "^4.17.1",
"express-urlrewrite": "^1.2.0", "express-urlrewrite": "^1.3.0",
"json-parse-helpfulerror": "^1.0.3", "json-parse-helpfulerror": "^1.0.3",
"lodash": "^4.17.15", "lodash": "^4.17.20",
"lodash-id": "^0.14.0", "lodash-id": "^0.14.0",
"lowdb": "^1.0.0", "lowdb": "^1.0.0",
"method-override": "^3.0.0", "method-override": "^3.0.0",
"morgan": "^1.9.1", "morgan": "^1.10.0",
"nanoid": "^2.1.11", "nanoid": "^3.1.16",
"please-upgrade-node": "^3.2.0", "please-upgrade-node": "^3.2.0",
"pluralize": "^8.0.0", "pluralize": "^8.0.0",
"server-destroy": "^1.0.1", "server-destroy": "^1.0.1",
"update-notifier": "^4.0.0", "update-notifier": "^5.0.1",
"yargs": "^15.1.0" "yargs": "^16.1.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.8.4", "@babel/cli": "^7.12.1",
"@babel/core": "^7.8.4", "@babel/core": "^7.12.3",
"@babel/node": "^7.8.4", "@babel/node": "^7.12.6",
"@babel/preset-env": "^7.8.4", "@babel/preset-env": "^7.12.1",
"cross-env": "^7.0.0", "cross-env": "^7.0.2",
"eslint": "^6.8.0", "eslint": "^7.13.0",
"eslint-config-prettier": "^6.10.0", "eslint-config-prettier": "^6.15.0",
"eslint-config-standard": "^14.1.0", "eslint-config-standard": "^16.0.1",
"eslint-plugin-import": "^2.20.1", "eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.0.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.1.2", "eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1", "eslint-plugin-standard": "^4.1.0",
"husky": "^5.0.0-beta.0", "husky": "^5.0.0-beta.0",
"jest": "^25.1.0", "jest": "^26.6.3",
"markdown-toc": "^1.2.0", "markdown-toc": "^1.2.0",
"mkdirp": "^1.0.3", "mkdirp": "^1.0.4",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"os-tmpdir": "^2.0.0", "os-tmpdir": "^2.0.0",
"pinst": "^2.1.1", "pinst": "^2.1.1",
"pkg-ok": "^2.3.1", "pkg-ok": "^2.3.1",
"prettier": "^1.19.1", "prettier": "^2.1.2",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"server-ready": "^0.3.1", "server-ready": "^0.3.1",
"supertest": "^4.0.2", "supertest": "^6.0.1",
"temp-write": "^4.0.0" "temp-write": "^4.0.0"
}, },
"repository": { "repository": {

View File

@ -11,10 +11,10 @@ function ResourceList({ db }) {
return ` return `
<ul> <ul>
${Object.keys(db) ${Object.keys(db)
.map(name => .map((name) =>
ResourceItem({ ResourceItem({
name, name,
length: Array.isArray(db[name]) && db[name].length length: Array.isArray(db[name]) && db[name].length,
}) })
) )
.join('')} .join('')}
@ -37,9 +37,9 @@ function ResourcesBlock({ db }) {
window window
.fetch('db') .fetch('db')
.then(response => response.json()) .then((response) => response.json())
.then( .then(
db => (db) =>
(document.getElementById('resources').innerHTML = ResourcesBlock({ db })) (document.getElementById('resources').innerHTML = ResourcesBlock({ db }))
) )
@ -52,7 +52,7 @@ function CustomRoutesBlock({ customRoutes }) {
<table> <table>
${rules ${rules
.map( .map(
rule => (rule) =>
`<tr> `<tr>
<td>${rule}</td> <td>${rule}</td>
<td><code>⇢</code> ${customRoutes[rule]}</td> <td><code>⇢</code> ${customRoutes[rule]}</td>
@ -67,10 +67,10 @@ function CustomRoutesBlock({ customRoutes }) {
window window
.fetch('__rules') .fetch('__rules')
.then(response => response.json()) .then((response) => response.json())
.then( .then(
customRoutes => (customRoutes) =>
(document.getElementById('custom-routes').innerHTML = CustomRoutesBlock({ (document.getElementById('custom-routes').innerHTML = CustomRoutesBlock({
customRoutes customRoutes,
})) }))
) )

View File

@ -3,7 +3,7 @@ const yargs = require('yargs')
const run = require('./run') const run = require('./run')
const pkg = require('../../package.json') const pkg = require('../../package.json')
module.exports = function() { module.exports = function () {
updateNotifier({ pkg }).notify() updateNotifier({ pkg }).notify()
const argv = yargs const argv = yargs
@ -13,70 +13,70 @@ module.exports = function() {
port: { port: {
alias: 'p', alias: 'p',
description: 'Set port', description: 'Set port',
default: 3000 default: 3000,
}, },
host: { host: {
alias: 'H', alias: 'H',
description: 'Set host', description: 'Set host',
default: 'localhost' default: 'localhost',
}, },
watch: { watch: {
alias: 'w', alias: 'w',
description: 'Watch file(s)' description: 'Watch file(s)',
}, },
routes: { routes: {
alias: 'r', alias: 'r',
description: 'Path to routes file' description: 'Path to routes file',
}, },
middlewares: { middlewares: {
alias: 'm', alias: 'm',
array: true, array: true,
description: 'Paths to middleware files' description: 'Paths to middleware files',
}, },
static: { static: {
alias: 's', alias: 's',
description: 'Set static files directory' description: 'Set static files directory',
}, },
'read-only': { 'read-only': {
alias: 'ro', alias: 'ro',
description: 'Allow only GET requests' description: 'Allow only GET requests',
}, },
'no-cors': { 'no-cors': {
alias: 'nc', alias: 'nc',
description: 'Disable Cross-Origin Resource Sharing' description: 'Disable Cross-Origin Resource Sharing',
}, },
'no-gzip': { 'no-gzip': {
alias: 'ng', alias: 'ng',
description: 'Disable GZIP Content-Encoding' description: 'Disable GZIP Content-Encoding',
}, },
snapshots: { snapshots: {
alias: 'S', alias: 'S',
description: 'Set snapshots directory', description: 'Set snapshots directory',
default: '.' default: '.',
}, },
delay: { delay: {
alias: 'd', alias: 'd',
description: 'Add delay to responses (ms)' description: 'Add delay to responses (ms)',
}, },
id: { id: {
alias: 'i', alias: 'i',
description: 'Set database id property (e.g. _id)', description: 'Set database id property (e.g. _id)',
default: 'id' default: 'id',
}, },
foreignKeySuffix: { foreignKeySuffix: {
alias: 'fks', alias: 'fks',
description: 'Set foreign key suffix (e.g. _id as in post_id)', description: 'Set foreign key suffix (e.g. _id as in post_id)',
default: 'Id' default: 'Id',
}, },
quiet: { quiet: {
alias: 'q', alias: 'q',
description: 'Suppress log messages from output' description: 'Suppress log messages from output',
}, },
config: { config: {
alias: 'c', alias: 'c',
description: 'Path to config file', description: 'Path to config file',
default: 'json-server.json' default: 'json-server.json',
} },
}) })
.boolean('watch') .boolean('watch')
.boolean('read-only') .boolean('read-only')

View File

@ -21,7 +21,7 @@ function prettyPrint(argv, object, rules) {
if (rules) { if (rules) {
console.log() console.log()
console.log(chalk.bold(' Other routes')) console.log(chalk.bold(' Other routes'))
for (var rule in rules) { for (const rule in rules) {
console.log(` ${rule} -> ${rules[rule]}`) console.log(` ${rule} -> ${rules[rule]}`)
} }
} }
@ -47,7 +47,7 @@ function createApp(db, routes, middlewares, argv) {
readOnly: argv.readOnly, readOnly: argv.readOnly,
noCors: argv.noCors, noCors: argv.noCors,
noGzip: argv.noGzip, noGzip: argv.noGzip,
bodyParser: true bodyParser: true,
} }
if (argv.static) { if (argv.static) {
@ -77,7 +77,7 @@ function createApp(db, routes, middlewares, argv) {
return app return app
} }
module.exports = function(argv) { module.exports = function (argv) {
const source = argv._[0] const source = argv._[0]
let app let app
let server let server
@ -103,7 +103,7 @@ module.exports = function(argv) {
server = undefined server = undefined
// create db and load object, JSON file, JS or HTTP database // create db and load object, JSON file, JS or HTTP database
return load(source).then(db => { return load(source).then((db) => {
// Load additional routes // Load additional routes
let routes let routes
if (argv.routes) { if (argv.routes) {
@ -114,7 +114,7 @@ module.exports = function(argv) {
// Load middlewares // Load middlewares
let middlewares let middlewares
if (argv.middlewares) { if (argv.middlewares) {
middlewares = argv.middlewares.map(function(m) { middlewares = argv.middlewares.map(function (m) {
console.log(chalk.gray(' Loading', m)) console.log(chalk.gray(' Loading', m))
return require(path.resolve(m)) return require(path.resolve(m))
}) })
@ -134,7 +134,7 @@ module.exports = function(argv) {
prettyPrint(argv, db.getState(), routes) prettyPrint(argv, db.getState(), routes)
// Catch and handle any error occurring in the server process // Catch and handle any error occurring in the server process
process.on('uncaughtException', error => { process.on('uncaughtException', (error) => {
if (error.errno === 'EADDRINUSE') if (error.errno === 'EADDRINUSE')
console.log( console.log(
chalk.red( chalk.red(
@ -164,7 +164,7 @@ module.exports = function(argv) {
console.log(` Creating a snapshot from the CLI won't be possible`) console.log(` Creating a snapshot from the CLI won't be possible`)
}) })
process.stdin.setEncoding('utf8') process.stdin.setEncoding('utf8')
process.stdin.on('data', chunk => { process.stdin.on('data', (chunk) => {
if (chunk.trim().toLowerCase() === 's') { if (chunk.trim().toLowerCase() === 's') {
const filename = `db-${Date.now()}.json` const filename = `db-${Date.now()}.json`
const file = path.join(argv.snapshots, filename) const file = path.join(argv.snapshots, filename)
@ -240,7 +240,7 @@ module.exports = function(argv) {
} }
} }
}) })
.catch(err => { .catch((err) => {
console.log(err) console.log(err)
process.exit(1) process.exit(1)
}) })

View File

@ -1,7 +1,7 @@
module.exports = { module.exports = {
FILE, FILE,
JS, JS,
URL URL,
} }
function FILE(s) { function FILE(s) {

View File

@ -11,10 +11,10 @@ const chalk = require('chalk')
const example = { const example = {
posts: [{ id: 1, title: 'json-server', author: 'typicode' }], posts: [{ id: 1, title: 'json-server', author: 'typicode' }],
comments: [{ id: 1, body: 'some comment', postId: 1 }], comments: [{ id: 1, body: 'some comment', postId: 1 }],
profile: { name: 'typicode' } profile: { name: 'typicode' },
} }
module.exports = function(source) { module.exports = function (source) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (is.FILE(source)) { if (is.FILE(source)) {
if (!fs.existsSync(source)) { if (!fs.existsSync(source)) {
@ -32,9 +32,9 @@ module.exports = function(source) {
const client = sourceUrl.protocol === 'https:' ? https : http const client = sourceUrl.protocol === 'https:' ? https : http
client client
.get(sourceUrl, res => { .get(sourceUrl, (res) => {
let dbData = '' let dbData = ''
res.on('data', data => { res.on('data', (data) => {
dbData += data dbData += data
}) })
@ -42,7 +42,7 @@ module.exports = function(source) {
resolve(low(new Memory()).setState(JSON.parse(dbData))) resolve(low(new Memory()).setState(JSON.parse(dbData)))
}) })
}) })
.on('error', error => { .on('error', (error) => {
return reject(error) return reject(error)
}) })
} else if (is.JS(source)) { } else if (is.JS(source)) {

View File

@ -2,5 +2,5 @@ const bodyParser = require('body-parser')
module.exports = [ module.exports = [
bodyParser.json({ limit: '10mb', extended: false }), bodyParser.json({ limit: '10mb', extended: false }),
bodyParser.urlencoded({ extended: false }) bodyParser.urlencoded({ extended: false }),
] ]

View File

@ -7,7 +7,7 @@ const compression = require('compression')
const errorhandler = require('errorhandler') const errorhandler = require('errorhandler')
const bodyParser = require('./body-parser') const bodyParser = require('./body-parser')
module.exports = function(opts) { module.exports = function (opts) {
const userDir = path.join(process.cwd(), 'public') const userDir = path.join(process.cwd(), 'public')
const defaultDir = path.join(__dirname, '../../public') const defaultDir = path.join(__dirname, '../../public')
const staticDir = fs.existsSync(userDir) ? userDir : defaultDir const staticDir = fs.existsSync(userDir) ? userDir : defaultDir
@ -38,8 +38,8 @@ module.exports = function(opts) {
if (opts.logger) { if (opts.logger) {
arr.push( arr.push(
logger('dev', { logger('dev', {
skip: req => skip: (req) =>
process.env.NODE_ENV === 'test' || req.path === '/favicon.ico' process.env.NODE_ENV === 'test' || req.path === '/favicon.ico',
}) })
) )
} }

View File

@ -5,5 +5,5 @@ module.exports = {
defaults: require('./defaults'), defaults: require('./defaults'),
router: require('./router'), router: require('./router'),
rewriter: require('./rewriter'), rewriter: require('./rewriter'),
bodyParser: require('./body-parser') bodyParser: require('./body-parser'),
} }

View File

@ -1,10 +1,10 @@
const nanoid = require('nanoid') const { nanoid } = require('nanoid')
const pluralize = require('pluralize') const pluralize = require('pluralize')
module.exports = { module.exports = {
getRemovable, getRemovable,
createId, createId,
deepQuery deepQuery,
} }
// Returns document ids that have unsatisfied relations // Returns document ids that have unsatisfied relations
@ -13,7 +13,7 @@ function getRemovable(db, opts) {
const _ = this const _ = this
const removable = [] const removable = []
_.each(db, (coll, collName) => { _.each(db, (coll, collName) => {
_.each(coll, doc => { _.each(coll, (doc) => {
_.each(doc, (value, key) => { _.each(doc, (value, key) => {
if (new RegExp(`${opts.foreignKeySuffix}$`).test(key)) { if (new RegExp(`${opts.foreignKeySuffix}$`).test(key)) {
// Remove foreign key suffix and pluralize it // Remove foreign key suffix and pluralize it
@ -67,12 +67,7 @@ function deepQuery(value, q) {
return true return true
} }
} }
} else if ( } else if (value.toString().toLowerCase().indexOf(q) !== -1) {
value
.toString()
.toLowerCase()
.indexOf(q) !== -1
) {
return true return true
} }
} }

View File

@ -1,14 +1,14 @@
const express = require('express') const express = require('express')
const rewrite = require('express-urlrewrite') const rewrite = require('express-urlrewrite')
module.exports = routes => { module.exports = (routes) => {
const router = express.Router() const router = express.Router()
router.get('/__rules', (req, res) => { router.get('/__rules', (req, res) => {
res.json(routes) res.json(routes)
}) })
Object.keys(routes).forEach(key => { Object.keys(routes).forEach((key) => {
router.use(rewrite(key, routes[key])) router.use(rewrite(key, routes[key]))
}) })

View File

@ -3,7 +3,7 @@ const url = require('url')
module.exports = function getFullURL(req) { module.exports = function getFullURL(req) {
const root = url.format({ const root = url.format({
protocol: req.protocol, protocol: req.protocol,
host: req.get('host') host: req.get('host'),
}) })
return `${root}${req.originalUrl}` return `${root}${req.originalUrl}`

View File

@ -64,7 +64,7 @@ module.exports = (db, opts) => {
return return
} }
var sourceMessage = '' const sourceMessage = ''
// if (!_.isObject(source)) { // if (!_.isObject(source)) {
// sourceMessage = `in ${source}` // sourceMessage = `in ${source}`
// } // }

View File

@ -2,7 +2,7 @@ const express = require('express')
const pluralize = require('pluralize') const pluralize = require('pluralize')
const delay = require('./delay') const delay = require('./delay')
module.exports = opts => { module.exports = (opts) => {
const router = express.Router() const router = express.Router()
router.use(delay) router.use(delay)

View File

@ -14,7 +14,7 @@ module.exports = (db, name, opts) => {
// Embed function used in GET /name and GET /name/id // Embed function used in GET /name and GET /name/id
function embed(resource, e) { function embed(resource, e) {
e && e &&
[].concat(e).forEach(externalResource => { [].concat(e).forEach((externalResource) => {
if (db.get(externalResource).value) { if (db.get(externalResource).value) {
const query = {} const query = {}
const singularResource = pluralize.singular(name) const singularResource = pluralize.singular(name)
@ -30,7 +30,7 @@ module.exports = (db, name, opts) => {
// Expand function used in GET /name and GET /name/id // Expand function used in GET /name and GET /name/id
function expand(resource, e) { function expand(resource, e) {
e && e &&
[].concat(e).forEach(innerResource => { [].concat(e).forEach((innerResource) => {
const plural = pluralize(innerResource) const plural = pluralize(innerResource)
if (db.get(plural).value()) { if (db.get(plural).value()) {
const prop = `${innerResource}${opts.foreignKeySuffix}` const prop = `${innerResource}${opts.foreignKeySuffix}`
@ -74,7 +74,7 @@ module.exports = (db, name, opts) => {
// Automatically delete query parameters that can't be found // Automatically delete query parameters that can't be found
// in the database // in the database
Object.keys(req.query).forEach(query => { Object.keys(req.query).forEach((query) => {
const arr = db.get(name).value() const arr = db.get(name).value()
for (const i in arr) { for (const i in arr) {
if ( if (
@ -99,17 +99,18 @@ module.exports = (db, name, opts) => {
q = q.toLowerCase() q = q.toLowerCase()
chain = chain.filter(obj => { chain = chain.filter((obj) => {
for (const key in obj) { for (const key in obj) {
const value = obj[key] const value = obj[key]
if (db._.deepQuery(value, q)) { if (db._.deepQuery(value, q)) {
return true return true
} }
} }
return false
}) })
} }
Object.keys(req.query).forEach(key => { Object.keys(req.query).forEach((key) => {
// Don't take into account JSONP query parameters // Don't take into account JSONP query parameters
// jQuery adds a '_' query parameter too // jQuery adds a '_' query parameter too
if (key !== 'callback' && key !== '_') { if (key !== 'callback' && key !== '_') {
@ -121,16 +122,16 @@ module.exports = (db, name, opts) => {
const isLike = /_like$/.test(key) const isLike = /_like$/.test(key)
const path = key.replace(/(_lte|_gte|_ne|_like)$/, '') const path = key.replace(/(_lte|_gte|_ne|_like)$/, '')
chain = chain.filter(element => { chain = chain.filter((element) => {
return arr return arr
.map(function(value) { .map(function (value) {
// get item value based on path // get item value based on path
// i.e post.title -> 'foo' // i.e post.title -> 'foo'
const elementValue = _.get(element, path) const elementValue = _.get(element, path)
// Prevent toString() failing on undefined or null values // Prevent toString() failing on undefined or null values
if (elementValue === undefined || elementValue === null) { if (elementValue === undefined || elementValue === null) {
return return undefined
} }
if (isRange) { if (isRange) {
@ -155,7 +156,7 @@ module.exports = (db, name, opts) => {
// Sort // Sort
if (_sort) { if (_sort) {
const _sortSet = _sort.split(',') const _sortSet = _sort.split(',')
const _orderSet = (_order || '').split(',').map(s => s.toLowerCase()) const _orderSet = (_order || '').split(',').map((s) => s.toLowerCase())
chain = chain.orderBy(_sortSet, _orderSet) chain = chain.orderBy(_sortSet, _orderSet)
} }
@ -217,7 +218,7 @@ module.exports = (db, name, opts) => {
} }
// embed and expand // embed and expand
chain = chain.cloneDeep().forEach(function(element) { chain = chain.cloneDeep().forEach(function (element) {
embed(element, _embed) embed(element, _embed)
expand(element, _expand) expand(element, _expand)
}) })
@ -231,10 +232,7 @@ module.exports = (db, name, opts) => {
function show(req, res, next) { function show(req, res, next) {
const _embed = req.query._embed const _embed = req.query._embed
const _expand = req.query._expand const _expand = req.query._expand
const resource = db const resource = db.get(name).getById(req.params.id).value()
.get(name)
.getById(req.params.id)
.value()
if (resource) { if (resource) {
// Clone resource to avoid making changes to the underlying object // Clone resource to avoid making changes to the underlying object
@ -258,16 +256,10 @@ module.exports = (db, name, opts) => {
function create(req, res, next) { function create(req, res, next) {
let resource let resource
if (opts._isFake) { if (opts._isFake) {
const id = db const id = db.get(name).createId().value()
.get(name)
.createId()
.value()
resource = { ...req.body, id } resource = { ...req.body, id }
} else { } else {
resource = db resource = db.get(name).insert(req.body).value()
.get(name)
.insert(req.body)
.value()
} }
res.setHeader('Access-Control-Expose-Headers', 'Location') res.setHeader('Access-Control-Expose-Headers', 'Location')
@ -286,10 +278,7 @@ module.exports = (db, name, opts) => {
let resource let resource
if (opts._isFake) { if (opts._isFake) {
resource = db resource = db.get(name).getById(id).value()
.get(name)
.getById(id)
.value()
if (req.method === 'PATCH') { if (req.method === 'PATCH') {
resource = { ...resource, ...req.body } resource = { ...resource, ...req.body }
@ -321,17 +310,12 @@ module.exports = (db, name, opts) => {
if (opts._isFake) { if (opts._isFake) {
resource = db.get(name).value() resource = db.get(name).value()
} else { } else {
resource = db resource = db.get(name).removeById(req.params.id).value()
.get(name)
.removeById(req.params.id)
.value()
// Remove dependents documents // Remove dependents documents
const removable = db._.getRemovable(db.getState(), opts) const removable = db._.getRemovable(db.getState(), opts)
removable.forEach(item => { removable.forEach((item) => {
db.get(item.name) db.get(item.name).removeById(item.id).value()
.removeById(item.id)
.value()
}) })
} }
@ -344,10 +328,7 @@ module.exports = (db, name, opts) => {
const w = write(db) const w = write(db)
router router.route('/').get(list).post(create, w)
.route('/')
.get(list)
.post(create, w)
router router
.route('/:id') .route('/:id')

View File

@ -39,9 +39,7 @@ module.exports = (db, name, opts) => {
if (req.method === 'PUT') { if (req.method === 'PUT') {
db.set(name, req.body).value() db.set(name, req.body).value()
} else { } else {
db.get(name) db.get(name).assign(req.body).value()
.assign(req.body)
.value()
} }
res.locals.data = db.get(name).value() res.locals.data = db.get(name).value()
@ -52,12 +50,7 @@ module.exports = (db, name, opts) => {
const w = write(db) const w = write(db)
router router.route('/').get(show).post(create, w).put(update, w).patch(update, w)
.route('/')
.get(show)
.post(create, w)
.put(update, w)
.patch(update, w)
return router return router
} }

View File

@ -6,13 +6,13 @@ function validateKey(key) {
`Oops, found / character in database property '${key}'.`, `Oops, found / character in database property '${key}'.`,
'', '',
"/ aren't supported, if you want to tweak default routes, see", "/ aren't supported, if you want to tweak default routes, see",
'https://github.com/typicode/json-server/#add-custom-routes' 'https://github.com/typicode/json-server/#add-custom-routes',
].join('\n') ].join('\n')
throw new Error(msg) throw new Error(msg)
} }
} }
module.exports = obj => { module.exports = (obj) => {
if (_.isPlainObject(obj)) { if (_.isPlainObject(obj)) {
Object.keys(obj).forEach(validateKey) Object.keys(obj).forEach(validateKey)
} else { } else {

View File

@ -1,11 +1,11 @@
module.exports = { module.exports = {
getPage getPage,
} }
function getPage(array, page, perPage) { function getPage(array, page, perPage) {
var obj = {} const obj = {}
var start = (page - 1) * perPage const start = (page - 1) * perPage
var end = page * perPage const end = page * perPage
obj.items = array.slice(start, end) obj.items = array.slice(start, end)
if (obj.items.length === 0) { if (obj.items.length === 0) {