Merge branch 'master' of https://github.com/ruifortes/json-server into ruifortes-master

This commit is contained in:
typicode
2018-09-11 23:47:47 +02:00
6 changed files with 8349 additions and 2521 deletions

10544
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,7 @@
"json-parse-helpfulerror": "^1.0.3",
"lodash": "^4.17.10",
"lodash-id": "^0.14.0",
"lowdb": "^0.15.0",
"lowdb": "^1.0.0",
"method-override": "^2.3.10",
"morgan": "^1.9.0",
"nanoid": "^1.1.1",

View File

@ -9,12 +9,6 @@ const is = require('./utils/is')
const load = require('./utils/load')
const jsonServer = require('../server')
const example = {
posts: [{ id: 1, title: 'json-server', author: 'typicode' }],
comments: [{ id: 1, body: 'some comment', postId: 1 }],
profile: { name: 'typicode' }
}
function prettyPrint(argv, object, rules) {
const root = `http://${argv.host}:${argv.port}`
@ -38,22 +32,15 @@ function prettyPrint(argv, object, rules) {
console.log()
}
function createApp(source, object, routes, middlewares, argv) {
function createApp(db, routes, middlewares, argv) {
const app = jsonServer.create()
let router
const { foreignKeySuffix } = argv
try {
router = jsonServer.router(
is.JSON(source) ? source : object,
foreignKeySuffix ? { foreignKeySuffix } : undefined
)
} catch (e) {
console.log()
console.error(chalk.red(e.message.replace(/^/gm, ' ')))
process.exit(1)
}
let router = jsonServer.router(
db,
foreignKeySuffix ? { foreignKeySuffix } : undefined
)
const defaultsOpts = {
logger: !argv.quiet,
@ -111,20 +98,12 @@ module.exports = function(argv) {
function start(cb) {
console.log()
// Be nice and create a default db.json if it doesn't exist
if (is.JSON(source) && !fs.existsSync(source)) {
console.log(chalk.yellow(` Oops, ${source} doesn't seem to exist`))
console.log(chalk.yellow(` Creating ${source} with some default data`))
console.log()
fs.writeFileSync(source, JSON.stringify(example, null, 2))
}
console.log(chalk.gray(' Loading', source))
// Load JSON, JS or HTTP database
load(source, (err, data) => {
if (err) throw err
server = undefined
// create db and load object, JSON file, JS or HTTP database
return load(source).then(db => {
// Load additional routes
let routes
if (argv.routes) {
@ -145,109 +124,112 @@ module.exports = function(argv) {
console.log(chalk.gray(' Done'))
// Create app and server
app = createApp(source, data, routes, middlewares, argv)
app = createApp(db, routes, middlewares, argv)
server = app.listen(argv.port, argv.host)
// Enhance with a destroy function
enableDestroy(server)
// Display server informations
prettyPrint(argv, data, routes)
cb && cb()
prettyPrint(argv, db.getState(), routes)
})
}
// Start server
start(() => {
// Snapshot
console.log(
chalk.gray(
' Type s + enter at any time to create a snapshot of the database'
)
)
// Support nohup
// https://github.com/typicode/json-server/issues/221
process.stdin.on('error', () => {
console.log(` Error, can't read from stdin`)
console.log(` Creating a snapshot from the CLI won't be possible`)
})
process.stdin.setEncoding('utf8')
process.stdin.on('data', chunk => {
if (chunk.trim().toLowerCase() === 's') {
const filename = `db-${Date.now()}.json`
const file = path.join(argv.snapshots, filename)
const state = app.db.getState()
fs.writeFileSync(file, JSON.stringify(state, null, 2), 'utf-8')
console.log(
` Saved snapshot to ${path.relative(process.cwd(), file)}\n`
start()
.then(() => {
// Snapshot
console.log(
chalk.gray(
' Type s + enter at any time to create a snapshot of the database'
)
}
})
)
// Watch files
if (argv.watch) {
console.log(chalk.gray(' Watching...'))
console.log()
const source = argv._[0]
// Can't watch URL
if (is.URL(source)) throw new Error("Can't watch URL")
// Watch .js or .json file
// Since lowdb uses atomic writing, directory is watched instead of file
const watchedDir = path.dirname(source)
let readError = false
fs.watch(watchedDir, (event, file) => {
// https://github.com/typicode/json-server/issues/420
// file can be null
if (file) {
const watchedFile = path.resolve(watchedDir, file)
if (watchedFile === path.resolve(source)) {
if (is.JSON(watchedFile)) {
let obj
try {
obj = jph.parse(fs.readFileSync(watchedFile))
if (readError) {
console.log(chalk.green(` Read error has been fixed :)`))
readError = false
}
} catch (e) {
readError = true
console.log(chalk.red(` Error reading ${watchedFile}`))
console.error(e.message)
return
}
// Compare .json file content with in memory database
const isDatabaseDifferent = !_.isEqual(obj, app.db.getState())
if (isDatabaseDifferent) {
console.log(chalk.gray(` ${source} has changed, reloading...`))
server && server.destroy()
start()
}
}
}
// Support nohup
// https://github.com/typicode/json-server/issues/221
process.stdin.on('error', () => {
console.log(` Error, can't read from stdin`)
console.log(` Creating a snapshot from the CLI won't be possible`)
})
process.stdin.setEncoding('utf8')
process.stdin.on('data', chunk => {
if (chunk.trim().toLowerCase() === 's') {
const filename = `db-${Date.now()}.json`
const file = path.join(argv.snapshots, filename)
const state = app.db.getState()
fs.writeFileSync(file, JSON.stringify(state, null, 2), 'utf-8')
console.log(
` Saved snapshot to ${path.relative(process.cwd(), file)}\n`
)
}
})
// Watch routes
if (argv.routes) {
const watchedDir = path.dirname(argv.routes)
// Watch files
if (argv.watch) {
console.log(chalk.gray(' Watching...'))
console.log()
const source = argv._[0]
// Can't watch URL
if (is.URL(source)) throw new Error("Can't watch URL")
// Watch .js or .json file
// Since lowdb uses atomic writing, directory is watched instead of file
const watchedDir = path.dirname(source)
let readError = false
fs.watch(watchedDir, (event, file) => {
// https://github.com/typicode/json-server/issues/420
// file can be null
if (file) {
const watchedFile = path.resolve(watchedDir, file)
if (watchedFile === path.resolve(argv.routes)) {
console.log(
chalk.gray(` ${argv.routes} has changed, reloading...`)
)
server && server.destroy()
start()
if (watchedFile === path.resolve(source)) {
if (is.FILE(watchedFile)) {
let obj
try {
obj = jph.parse(fs.readFileSync(watchedFile))
if (readError) {
console.log(chalk.green(` Read error has been fixed :)`))
readError = false
}
} catch (e) {
readError = true
console.log(chalk.red(` Error reading ${watchedFile}`))
console.error(e.message)
return
}
// Compare .json file content with in memory database
const isDatabaseDifferent = !_.isEqual(obj, app.db.getState())
if (isDatabaseDifferent) {
console.log(
chalk.gray(` ${source} has changed, reloading...`)
)
server && server.destroy(() => start())
}
}
}
}
})
// Watch routes
if (argv.routes) {
const watchedDir = path.dirname(argv.routes)
fs.watch(watchedDir, (event, file) => {
if (file) {
const watchedFile = path.resolve(watchedDir, file)
if (watchedFile === path.resolve(argv.routes)) {
console.log(
chalk.gray(` ${argv.routes} has changed, reloading...`)
)
server && server.destroy(() => start())
}
}
})
}
}
}
})
})
.catch(err => {
console.log(err)
process.exit(1)
})
}

View File

@ -1,10 +1,10 @@
module.exports = {
JSON,
FILE,
JS,
URL
}
function JSON(s) {
function FILE(s) {
return !URL(s) && /\.json$/.test(s)
}

View File

@ -1,41 +1,57 @@
const fs = require('fs')
const path = require('path')
const request = require('request')
const low = require('lowdb')
const fileAsync = require('lowdb/lib/storages/file-async')
const FileAsync = require('lowdb/adapters/FileAsync')
const Memory = require('lowdb/adapters/Memory')
const is = require('./is')
const chalk = require('chalk')
module.exports = function(source, cb) {
if (is.URL(source)) {
// Load remote data
const opts = {
url: source,
json: true
}
request(opts, (err, response) => {
if (err) return cb(err)
cb(null, response.body)
})
} else if (is.JS(source)) {
// Clear cache
const filename = path.resolve(source)
delete require.cache[filename]
const dataFn = require(filename)
if (typeof dataFn !== 'function') {
throw new Error(
'The database is a JavaScript file but the export is not a function.'
)
}
// Run dataFn to generate data
const data = dataFn()
cb(null, data)
} else if (is.JSON(source)) {
// Load JSON using lowdb
const data = low(source, { storage: fileAsync }).getState()
cb(null, data)
} else {
throw new Error(`Unsupported source ${source}`)
}
const example = {
posts: [{ id: 1, title: 'json-server', author: 'typicode' }],
comments: [{ id: 1, body: 'some comment', postId: 1 }],
profile: { name: 'typicode' }
}
module.exports = function(source) {
return new Promise((resolve, reject) => {
if (is.FILE(source)) {
if (!fs.existsSync(source)) {
console.log(chalk.yellow(` Oops, ${source} doesn't seem to exist`))
console.log(chalk.yellow(` Creating ${source} with some default data`))
console.log()
fs.writeFileSync(source, JSON.stringify(example, null, 2))
}
resolve(low(new FileAsync(source)))
} else if (is.URL(source)) {
// Load remote data
const opts = {
url: source,
json: true
}
request(opts, (err, response) => {
if (err) return reject(err)
resolve(low(new Memory()).setState(response.body))
})
} else if (is.JS(source)) {
// Clear cache
const filename = path.resolve(source)
delete require.cache[filename]
const dataFn = require(filename)
if (typeof dataFn !== 'function') {
throw new Error(
'The database is a JavaScript file but the export is not a function.'
)
}
// Run dataFn to generate data
const data = dataFn()
resolve(low(new Memory()).setState(data))
} else {
throw new Error(`Unsupported source ${source}`)
}
})
}

View File

@ -3,7 +3,8 @@ const methodOverride = require('method-override')
const _ = require('lodash')
const lodashId = require('lodash-id')
const low = require('lowdb')
const fileAsync = require('lowdb/lib/storages/file-async')
const Memory = require('lowdb/adapters/Memory')
const FileSync = require('lowdb/adapters/FileSync')
const bodyParser = require('../body-parser')
const validateData = require('./validate-data')
const plural = require('./plural')
@ -11,7 +12,13 @@ const nested = require('./nested')
const singular = require('./singular')
const mixins = require('../mixins')
module.exports = (source, opts = { foreignKeySuffix: 'Id' }) => {
module.exports = (db, opts = { foreignKeySuffix: 'Id' }) => {
if (typeof db === 'string') {
db = low(new FileSync(db))
} else if (!_.has(db, '__chain__') || !_.has(db, '__wrapped__')) {
db = low(new Memory()).setState(db)
}
// Create router
const router = express.Router()
@ -19,15 +26,6 @@ module.exports = (source, opts = { foreignKeySuffix: 'Id' }) => {
router.use(methodOverride())
router.use(bodyParser)
// Create database
let db
if (_.isObject(source)) {
db = low()
db.setState(source)
} else {
db = low(source, { storage: fileAsync })
}
validateData(db.getState())
// Add lodash-id methods to db
@ -65,9 +63,9 @@ module.exports = (source, opts = { foreignKeySuffix: 'Id' }) => {
}
var sourceMessage = ''
if (!_.isObject(source)) {
sourceMessage = `in ${source}`
}
// if (!_.isObject(source)) {
// sourceMessage = `in ${source}`
// }
const msg =
`Type of "${key}" (${typeof value}) ${sourceMessage} is not supported. ` +