mirror of
https://github.com/typicode/json-server.git
synced 2025-07-25 11:12:22 +08:00
Merge branch 'master' of https://github.com/ruifortes/json-server into ruifortes-master
This commit is contained in:
10544
package-lock.json
generated
10544
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||
|
208
src/cli/run.js
208
src/cli/run.js
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
module.exports = {
|
||||
JSON,
|
||||
FILE,
|
||||
JS,
|
||||
URL
|
||||
}
|
||||
|
||||
function JSON(s) {
|
||||
function FILE(s) {
|
||||
return !URL(s) && /\.json$/.test(s)
|
||||
}
|
||||
|
||||
|
@ -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}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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. ` +
|
||||
|
Reference in New Issue
Block a user