mirror of
https://github.com/typicode/json-server.git
synced 2025-07-27 20:23:34 +08:00
Merge branch '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",
|
"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",
|
||||||
|
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 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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -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. ` +
|
||||||
|
Reference in New Issue
Block a user