Merge branch 'ruifortes-master'

This commit is contained in:
typicode
2018-09-11 23:49:08 +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", "json-parse-helpfulerror": "^1.0.3",
"lodash": "^4.17.10", "lodash": "^4.17.10",
"lodash-id": "^0.14.0", "lodash-id": "^0.14.0",
"lowdb": "^0.15.0", "lowdb": "^1.0.0",
"method-override": "^2.3.10", "method-override": "^2.3.10",
"morgan": "^1.9.0", "morgan": "^1.9.0",
"nanoid": "^1.1.1", "nanoid": "^1.1.1",

View File

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

View File

@ -1,41 +1,57 @@
const fs = require('fs')
const path = require('path') const path = require('path')
const request = require('request') const request = require('request')
const low = require('lowdb') 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 is = require('./is')
const chalk = require('chalk')
module.exports = function(source, cb) { const example = {
if (is.URL(source)) { posts: [{ id: 1, title: 'json-server', author: 'typicode' }],
// Load remote data comments: [{ id: 1, body: 'some comment', postId: 1 }],
const opts = { profile: { name: 'typicode' }
url: source, }
json: true
} module.exports = function(source) {
return new Promise((resolve, reject) => {
request(opts, (err, response) => { if (is.FILE(source)) {
if (err) return cb(err) if (!fs.existsSync(source)) {
cb(null, response.body) console.log(chalk.yellow(` Oops, ${source} doesn't seem to exist`))
}) console.log(chalk.yellow(` Creating ${source} with some default data`))
} else if (is.JS(source)) { console.log()
// Clear cache fs.writeFileSync(source, JSON.stringify(example, null, 2))
const filename = path.resolve(source) }
delete require.cache[filename]
const dataFn = require(filename) resolve(low(new FileAsync(source)))
} else if (is.URL(source)) {
if (typeof dataFn !== 'function') { // Load remote data
throw new Error( const opts = {
'The database is a JavaScript file but the export is not a function.' url: source,
) json: true
} }
// Run dataFn to generate data request(opts, (err, response) => {
const data = dataFn() if (err) return reject(err)
cb(null, data) resolve(low(new Memory()).setState(response.body))
} else if (is.JSON(source)) { })
// Load JSON using lowdb } else if (is.JS(source)) {
const data = low(source, { storage: fileAsync }).getState() // Clear cache
cb(null, data) const filename = path.resolve(source)
} else { delete require.cache[filename]
throw new Error(`Unsupported source ${source}`) 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 _ = require('lodash')
const lodashId = require('lodash-id') const lodashId = require('lodash-id')
const low = require('lowdb') 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 bodyParser = require('../body-parser')
const validateData = require('./validate-data') const validateData = require('./validate-data')
const plural = require('./plural') const plural = require('./plural')
@ -11,7 +12,13 @@ const nested = require('./nested')
const singular = require('./singular') const singular = require('./singular')
const mixins = require('../mixins') 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 // Create router
const router = express.Router() const router = express.Router()
@ -19,15 +26,6 @@ module.exports = (source, opts = { foreignKeySuffix: 'Id' }) => {
router.use(methodOverride()) router.use(methodOverride())
router.use(bodyParser) router.use(bodyParser)
// Create database
let db
if (_.isObject(source)) {
db = low()
db.setState(source)
} else {
db = low(source, { storage: fileAsync })
}
validateData(db.getState()) validateData(db.getState())
// Add lodash-id methods to db // Add lodash-id methods to db
@ -65,9 +63,9 @@ module.exports = (source, opts = { foreignKeySuffix: 'Id' }) => {
} }
var sourceMessage = '' var sourceMessage = ''
if (!_.isObject(source)) { // if (!_.isObject(source)) {
sourceMessage = `in ${source}` // sourceMessage = `in ${source}`
} // }
const msg = const msg =
`Type of "${key}" (${typeof value}) ${sourceMessage} is not supported. ` + `Type of "${key}" (${typeof value}) ${sourceMessage} is not supported. ` +