json-server 0.17.2 → 0.17.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -17
- package/lib/cli/bin.js +0 -1
- package/lib/cli/index.js +0 -4
- package/lib/cli/run.js +34 -54
- package/lib/cli/utils/is.js +0 -3
- package/lib/cli/utils/load.js +4 -17
- package/lib/server/body-parser.js +0 -1
- package/lib/server/defaults.js +15 -22
- package/lib/server/index.js +0 -1
- package/lib/server/mixins.js +10 -19
- package/lib/server/rewriter.js +0 -2
- package/lib/server/router/delay.js +0 -2
- package/lib/server/router/get-full-url.js +0 -1
- package/lib/server/router/index.js +24 -31
- package/lib/server/router/nested.js +4 -7
- package/lib/server/router/plural.js +43 -75
- package/lib/server/router/singular.js +5 -17
- package/lib/server/router/validate-data.js +1 -3
- package/lib/server/utils.js +0 -6
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -35,22 +35,6 @@ See also:
|
|
|
35
35
|
|
|
36
36
|
<p> </p>
|
|
37
37
|
|
|
38
|
-
<p align="center">
|
|
39
|
-
<a href="https://megafamous.com/buy-instagram-followers" target="_blank">
|
|
40
|
-
<img src="https://jsonplaceholder.typicode.com/megafamous.png" height="70px">
|
|
41
|
-
</a>
|
|
42
|
-
</p>
|
|
43
|
-
|
|
44
|
-
<p> </p>
|
|
45
|
-
|
|
46
|
-
<p align="center">
|
|
47
|
-
<a href="http://asocks.com/c/TFw5bz" target="_blank">
|
|
48
|
-
<img src="https://jsonplaceholder.typicode.com/asocks.png" height="70px">
|
|
49
|
-
</a>
|
|
50
|
-
</p>
|
|
51
|
-
|
|
52
|
-
<p> </p>
|
|
53
|
-
|
|
54
38
|
<p> </p>
|
|
55
39
|
|
|
56
40
|
<h2 align="center">Silver sponsors 🥈</h2>
|
|
@@ -371,7 +355,7 @@ Create a `routes.json` file. Pay attention to start every route with `/`.
|
|
|
371
355
|
"/api/*": "/$1",
|
|
372
356
|
"/:resource/:id/show": "/:resource/:id",
|
|
373
357
|
"/posts/:category": "/posts?category=:category",
|
|
374
|
-
"/articles
|
|
358
|
+
"/articles?id=:id": "/posts/:id"
|
|
375
359
|
}
|
|
376
360
|
```
|
|
377
361
|
|
package/lib/cli/bin.js
CHANGED
package/lib/cli/index.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const yargs = require('yargs');
|
|
4
|
-
|
|
5
4
|
const run = require('./run');
|
|
6
|
-
|
|
7
5
|
const pkg = require('../../package.json');
|
|
8
|
-
|
|
9
6
|
module.exports = function () {
|
|
10
7
|
const argv = yargs.config('config').usage('$0 [options] <source>').options({
|
|
11
8
|
port: {
|
|
@@ -76,6 +73,5 @@ module.exports = function () {
|
|
|
76
73
|
default: 'json-server.json'
|
|
77
74
|
}
|
|
78
75
|
}).boolean('watch').boolean('read-only').boolean('quiet').boolean('no-cors').boolean('no-gzip').help('help').alias('help', 'h').version(pkg.version).alias('version', 'v').example('$0 db.json', '').example('$0 file.js', '').example('$0 http://example.com/db.json', '').epilog('https://github.com/typicode/json-server').require(1, 'Missing <source> argument').argv;
|
|
79
|
-
|
|
80
76
|
run(argv);
|
|
81
77
|
};
|
package/lib/cli/run.js
CHANGED
|
@@ -1,49 +1,36 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
-
|
|
5
4
|
const path = require('path');
|
|
6
|
-
|
|
7
5
|
const jph = require('json-parse-helpfulerror');
|
|
8
|
-
|
|
9
6
|
const _ = require('lodash');
|
|
10
|
-
|
|
11
7
|
const chalk = require('chalk');
|
|
12
|
-
|
|
13
8
|
const enableDestroy = require('server-destroy');
|
|
14
|
-
|
|
15
9
|
const pause = require('connect-pause');
|
|
16
|
-
|
|
17
10
|
const is = require('./utils/is');
|
|
18
|
-
|
|
19
11
|
const load = require('./utils/load');
|
|
20
|
-
|
|
21
12
|
const jsonServer = require('../server');
|
|
22
|
-
|
|
23
13
|
function prettyPrint(argv, object, rules) {
|
|
24
14
|
const root = `http://${argv.host}:${argv.port}`;
|
|
25
15
|
console.log();
|
|
26
16
|
console.log(chalk.bold(' Resources'));
|
|
27
|
-
|
|
28
17
|
for (const prop in object) {
|
|
18
|
+
// skip printing $schema nodes
|
|
19
|
+
if (prop === '$schema') continue;
|
|
29
20
|
console.log(` ${root}/${prop}`);
|
|
30
21
|
}
|
|
31
|
-
|
|
32
22
|
if (rules) {
|
|
33
23
|
console.log();
|
|
34
24
|
console.log(chalk.bold(' Other routes'));
|
|
35
|
-
|
|
36
25
|
for (const rule in rules) {
|
|
37
26
|
console.log(` ${rule} -> ${rules[rule]}`);
|
|
38
27
|
}
|
|
39
28
|
}
|
|
40
|
-
|
|
41
29
|
console.log();
|
|
42
30
|
console.log(chalk.bold(' Home'));
|
|
43
31
|
console.log(` ${root}`);
|
|
44
32
|
console.log();
|
|
45
33
|
}
|
|
46
|
-
|
|
47
34
|
function createApp(db, routes, middlewares, argv) {
|
|
48
35
|
const app = jsonServer.create();
|
|
49
36
|
const {
|
|
@@ -59,98 +46,92 @@ function createApp(db, routes, middlewares, argv) {
|
|
|
59
46
|
noGzip: argv.noGzip,
|
|
60
47
|
bodyParser: true
|
|
61
48
|
};
|
|
62
|
-
|
|
63
49
|
if (argv.static) {
|
|
64
50
|
defaultsOpts.static = path.join(process.cwd(), argv.static);
|
|
65
51
|
}
|
|
66
|
-
|
|
67
52
|
const defaults = jsonServer.defaults(defaultsOpts);
|
|
68
53
|
app.use(defaults);
|
|
69
|
-
|
|
70
54
|
if (routes) {
|
|
71
55
|
const rewriter = jsonServer.rewriter(routes);
|
|
72
56
|
app.use(rewriter);
|
|
73
57
|
}
|
|
74
|
-
|
|
75
58
|
if (middlewares) {
|
|
76
59
|
app.use(middlewares);
|
|
77
60
|
}
|
|
78
|
-
|
|
79
61
|
if (argv.delay) {
|
|
80
62
|
app.use(pause(argv.delay));
|
|
81
63
|
}
|
|
82
|
-
|
|
83
64
|
router.db._.id = argv.id;
|
|
84
65
|
app.db = router.db;
|
|
85
66
|
app.use(router);
|
|
86
67
|
return app;
|
|
87
68
|
}
|
|
88
|
-
|
|
89
69
|
module.exports = function (argv) {
|
|
90
70
|
const source = argv._[0];
|
|
91
71
|
let app;
|
|
92
72
|
let server;
|
|
93
|
-
|
|
94
73
|
if (!fs.existsSync(argv.snapshots)) {
|
|
95
74
|
console.log(`Error: snapshots directory ${argv.snapshots} doesn't exist`);
|
|
96
75
|
process.exit(1);
|
|
97
|
-
}
|
|
98
|
-
|
|
76
|
+
}
|
|
99
77
|
|
|
78
|
+
// noop log fn
|
|
100
79
|
if (argv.quiet) {
|
|
101
80
|
console.log = () => {};
|
|
102
81
|
}
|
|
103
|
-
|
|
104
82
|
console.log();
|
|
105
83
|
console.log(chalk.cyan(' \\{^_^}/ hi!'));
|
|
106
|
-
|
|
107
84
|
function start(cb) {
|
|
108
85
|
console.log();
|
|
109
86
|
console.log(chalk.gray(' Loading', source));
|
|
110
|
-
server = undefined;
|
|
87
|
+
server = undefined;
|
|
111
88
|
|
|
89
|
+
// create db and load object, JSON file, JS or HTTP database
|
|
112
90
|
return load(source).then(db => {
|
|
113
91
|
// Load additional routes
|
|
114
92
|
let routes;
|
|
115
|
-
|
|
116
93
|
if (argv.routes) {
|
|
117
94
|
console.log(chalk.gray(' Loading', argv.routes));
|
|
118
95
|
routes = JSON.parse(fs.readFileSync(argv.routes));
|
|
119
|
-
}
|
|
120
|
-
|
|
96
|
+
}
|
|
121
97
|
|
|
98
|
+
// Load middlewares
|
|
122
99
|
let middlewares;
|
|
123
|
-
|
|
124
100
|
if (argv.middlewares) {
|
|
125
101
|
middlewares = argv.middlewares.map(function (m) {
|
|
126
102
|
console.log(chalk.gray(' Loading', m));
|
|
127
103
|
return require(path.resolve(m));
|
|
128
104
|
});
|
|
129
|
-
}
|
|
130
|
-
|
|
105
|
+
}
|
|
131
106
|
|
|
132
|
-
|
|
107
|
+
// Done
|
|
108
|
+
console.log(chalk.gray(' Done'));
|
|
133
109
|
|
|
110
|
+
// Create app and server
|
|
134
111
|
app = createApp(db, routes, middlewares, argv);
|
|
135
|
-
server = app.listen(argv.port, argv.host);
|
|
112
|
+
server = app.listen(argv.port, argv.host);
|
|
136
113
|
|
|
137
|
-
|
|
114
|
+
// Enhance with a destroy function
|
|
115
|
+
enableDestroy(server);
|
|
138
116
|
|
|
139
|
-
|
|
117
|
+
// Display server informations
|
|
118
|
+
prettyPrint(argv, db.getState(), routes);
|
|
140
119
|
|
|
120
|
+
// Catch and handle any error occurring in the server process
|
|
141
121
|
process.on('uncaughtException', error => {
|
|
142
122
|
if (error.errno === 'EADDRINUSE') console.log(chalk.red(`Cannot bind to the port ${error.port}. Please specify another port number either through --port argument or through the json-server.json configuration file`));else console.log('Some error occurred', error);
|
|
143
123
|
process.exit(1);
|
|
144
124
|
});
|
|
145
125
|
});
|
|
146
|
-
}
|
|
147
|
-
|
|
126
|
+
}
|
|
148
127
|
|
|
128
|
+
// Start server
|
|
149
129
|
start().then(() => {
|
|
150
130
|
// Snapshot
|
|
151
|
-
console.log(chalk.gray(' Type s + enter at any time to create a snapshot of the database'));
|
|
152
|
-
// https://github.com/typicode/json-server/issues/221
|
|
131
|
+
console.log(chalk.gray(' Type s + enter at any time to create a snapshot of the database'));
|
|
153
132
|
|
|
133
|
+
// Support nohup
|
|
134
|
+
// https://github.com/typicode/json-server/issues/221
|
|
154
135
|
process.stdin.on('error', () => {
|
|
155
136
|
console.log(` Error, can't read from stdin`);
|
|
156
137
|
console.log(` Creating a snapshot from the CLI won't be possible`);
|
|
@@ -164,16 +145,19 @@ module.exports = function (argv) {
|
|
|
164
145
|
fs.writeFileSync(file, JSON.stringify(state, null, 2), 'utf-8');
|
|
165
146
|
console.log(` Saved snapshot to ${path.relative(process.cwd(), file)}\n`);
|
|
166
147
|
}
|
|
167
|
-
});
|
|
148
|
+
});
|
|
168
149
|
|
|
150
|
+
// Watch files
|
|
169
151
|
if (argv.watch) {
|
|
170
152
|
console.log(chalk.gray(' Watching...'));
|
|
171
153
|
console.log();
|
|
172
|
-
const source = argv._[0];
|
|
154
|
+
const source = argv._[0];
|
|
173
155
|
|
|
174
|
-
|
|
175
|
-
|
|
156
|
+
// Can't watch URL
|
|
157
|
+
if (is.URL(source)) throw new Error("Can't watch URL");
|
|
176
158
|
|
|
159
|
+
// Watch .js or .json file
|
|
160
|
+
// Since lowdb uses atomic writing, directory is watched instead of file
|
|
177
161
|
const watchedDir = path.dirname(source);
|
|
178
162
|
let readError = false;
|
|
179
163
|
fs.watch(watchedDir, (event, file) => {
|
|
@@ -181,14 +165,11 @@ module.exports = function (argv) {
|
|
|
181
165
|
// file can be null
|
|
182
166
|
if (file) {
|
|
183
167
|
const watchedFile = path.resolve(watchedDir, file);
|
|
184
|
-
|
|
185
168
|
if (watchedFile === path.resolve(source)) {
|
|
186
169
|
if (is.FILE(watchedFile)) {
|
|
187
170
|
let obj;
|
|
188
|
-
|
|
189
171
|
try {
|
|
190
172
|
obj = jph.parse(fs.readFileSync(watchedFile));
|
|
191
|
-
|
|
192
173
|
if (readError) {
|
|
193
174
|
console.log(chalk.green(` Read error has been fixed :)`));
|
|
194
175
|
readError = false;
|
|
@@ -198,11 +179,10 @@ module.exports = function (argv) {
|
|
|
198
179
|
console.log(chalk.red(` Error reading ${watchedFile}`));
|
|
199
180
|
console.error(e.message);
|
|
200
181
|
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
182
|
+
}
|
|
203
183
|
|
|
184
|
+
// Compare .json file content with in memory database
|
|
204
185
|
const isDatabaseDifferent = !_.isEqual(obj, app.db.getState());
|
|
205
|
-
|
|
206
186
|
if (isDatabaseDifferent) {
|
|
207
187
|
console.log(chalk.gray(` ${source} has changed, reloading...`));
|
|
208
188
|
server && server.destroy(() => start());
|
|
@@ -210,14 +190,14 @@ module.exports = function (argv) {
|
|
|
210
190
|
}
|
|
211
191
|
}
|
|
212
192
|
}
|
|
213
|
-
});
|
|
193
|
+
});
|
|
214
194
|
|
|
195
|
+
// Watch routes
|
|
215
196
|
if (argv.routes) {
|
|
216
197
|
const watchedDir = path.dirname(argv.routes);
|
|
217
198
|
fs.watch(watchedDir, (event, file) => {
|
|
218
199
|
if (file) {
|
|
219
200
|
const watchedFile = path.resolve(watchedDir, file);
|
|
220
|
-
|
|
221
201
|
if (watchedFile === path.resolve(argv.routes)) {
|
|
222
202
|
console.log(chalk.gray(` ${argv.routes} has changed, reloading...`));
|
|
223
203
|
server && server.destroy(() => start());
|
package/lib/cli/utils/is.js
CHANGED
package/lib/cli/utils/load.js
CHANGED
|
@@ -1,23 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
-
|
|
5
4
|
const path = require('path');
|
|
6
|
-
|
|
7
5
|
const http = require('http');
|
|
8
|
-
|
|
9
6
|
const https = require('https');
|
|
10
|
-
|
|
11
7
|
const low = require('lowdb');
|
|
12
|
-
|
|
13
8
|
const FileAsync = require('lowdb/adapters/FileAsync');
|
|
14
|
-
|
|
15
9
|
const Memory = require('lowdb/adapters/Memory');
|
|
16
|
-
|
|
17
10
|
const is = require('./is');
|
|
18
|
-
|
|
19
11
|
const chalk = require('chalk');
|
|
20
|
-
|
|
21
12
|
const example = {
|
|
22
13
|
posts: [{
|
|
23
14
|
id: 1,
|
|
@@ -33,7 +24,6 @@ const example = {
|
|
|
33
24
|
name: 'typicode'
|
|
34
25
|
}
|
|
35
26
|
};
|
|
36
|
-
|
|
37
27
|
module.exports = function (source) {
|
|
38
28
|
return new Promise((resolve, reject) => {
|
|
39
29
|
if (is.FILE(source)) {
|
|
@@ -43,12 +33,11 @@ module.exports = function (source) {
|
|
|
43
33
|
console.log();
|
|
44
34
|
fs.writeFileSync(source, JSON.stringify(example, null, 2));
|
|
45
35
|
}
|
|
46
|
-
|
|
47
36
|
resolve(low(new FileAsync(source)));
|
|
48
37
|
} else if (is.URL(source)) {
|
|
49
38
|
// Normalize the source into a URL object.
|
|
50
|
-
const sourceUrl = new URL(source);
|
|
51
|
-
|
|
39
|
+
const sourceUrl = new URL(source);
|
|
40
|
+
// Pick the client based on the protocol scheme
|
|
52
41
|
const client = sourceUrl.protocol === 'https:' ? https : http;
|
|
53
42
|
client.get(sourceUrl, res => {
|
|
54
43
|
let dbData = '';
|
|
@@ -65,14 +54,12 @@ module.exports = function (source) {
|
|
|
65
54
|
// Clear cache
|
|
66
55
|
const filename = path.resolve(source);
|
|
67
56
|
delete require.cache[filename];
|
|
68
|
-
|
|
69
57
|
const dataFn = require(filename);
|
|
70
|
-
|
|
71
58
|
if (typeof dataFn !== 'function') {
|
|
72
59
|
throw new Error('The database is a JavaScript file but the export is not a function.');
|
|
73
|
-
}
|
|
74
|
-
|
|
60
|
+
}
|
|
75
61
|
|
|
62
|
+
// Run dataFn to generate data
|
|
76
63
|
const data = dataFn();
|
|
77
64
|
resolve(low(new Memory()).setState(data));
|
|
78
65
|
} else {
|
package/lib/server/defaults.js
CHANGED
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
-
|
|
5
4
|
const path = require('path');
|
|
6
|
-
|
|
7
5
|
const express = require('express');
|
|
8
|
-
|
|
9
6
|
const logger = require('morgan');
|
|
10
|
-
|
|
11
7
|
const cors = require('cors');
|
|
12
|
-
|
|
13
8
|
const compression = require('compression');
|
|
14
|
-
|
|
15
9
|
const errorhandler = require('errorhandler');
|
|
16
|
-
|
|
17
10
|
const bodyParser = require('./body-parser');
|
|
18
|
-
|
|
19
11
|
module.exports = function (opts) {
|
|
20
12
|
const userDir = path.join(process.cwd(), 'public');
|
|
21
13
|
const defaultDir = path.join(__dirname, '../../public');
|
|
@@ -24,43 +16,45 @@ module.exports = function (opts) {
|
|
|
24
16
|
logger: true,
|
|
25
17
|
static: staticDir
|
|
26
18
|
}, opts);
|
|
27
|
-
const arr = [];
|
|
19
|
+
const arr = [];
|
|
28
20
|
|
|
21
|
+
// Compress all requests
|
|
29
22
|
if (!opts.noGzip) {
|
|
30
23
|
arr.push(compression());
|
|
31
|
-
}
|
|
32
|
-
|
|
24
|
+
}
|
|
33
25
|
|
|
26
|
+
// Enable CORS for all the requests, including static files
|
|
34
27
|
if (!opts.noCors) {
|
|
35
28
|
arr.push(cors({
|
|
36
29
|
origin: true,
|
|
37
30
|
credentials: true
|
|
38
31
|
}));
|
|
39
32
|
}
|
|
40
|
-
|
|
41
33
|
if (process.env.NODE_ENV === 'development') {
|
|
42
34
|
// only use in development
|
|
43
35
|
arr.push(errorhandler());
|
|
44
|
-
}
|
|
45
|
-
|
|
36
|
+
}
|
|
46
37
|
|
|
47
|
-
|
|
38
|
+
// Serve static files
|
|
39
|
+
arr.push(express.static(opts.static));
|
|
48
40
|
|
|
41
|
+
// Logger
|
|
49
42
|
if (opts.logger) {
|
|
50
43
|
arr.push(logger('dev', {
|
|
51
44
|
skip: req => process.env.NODE_ENV === 'test' || req.path === '/favicon.ico'
|
|
52
45
|
}));
|
|
53
|
-
}
|
|
54
|
-
// https://support.microsoft.com/en-us/kb/234067
|
|
55
|
-
|
|
46
|
+
}
|
|
56
47
|
|
|
48
|
+
// No cache for IE
|
|
49
|
+
// https://support.microsoft.com/en-us/kb/234067
|
|
57
50
|
arr.push((req, res, next) => {
|
|
58
51
|
res.header('Cache-Control', 'no-cache');
|
|
59
52
|
res.header('Pragma', 'no-cache');
|
|
60
53
|
res.header('Expires', '-1');
|
|
61
54
|
next();
|
|
62
|
-
});
|
|
55
|
+
});
|
|
63
56
|
|
|
57
|
+
// Read-only
|
|
64
58
|
if (opts.readOnly) {
|
|
65
59
|
arr.push((req, res, next) => {
|
|
66
60
|
if (req.method === 'GET') {
|
|
@@ -69,12 +63,11 @@ module.exports = function (opts) {
|
|
|
69
63
|
res.sendStatus(403); // Forbidden
|
|
70
64
|
}
|
|
71
65
|
});
|
|
72
|
-
}
|
|
73
|
-
|
|
66
|
+
}
|
|
74
67
|
|
|
68
|
+
// Add middlewares
|
|
75
69
|
if (opts.bodyParser) {
|
|
76
70
|
arr.push(bodyParser);
|
|
77
71
|
}
|
|
78
|
-
|
|
79
72
|
return arr;
|
|
80
73
|
};
|
package/lib/server/index.js
CHANGED
package/lib/server/mixins.js
CHANGED
|
@@ -3,33 +3,29 @@
|
|
|
3
3
|
const {
|
|
4
4
|
nanoid
|
|
5
5
|
} = require('nanoid');
|
|
6
|
-
|
|
7
6
|
const pluralize = require('pluralize');
|
|
8
|
-
|
|
9
7
|
module.exports = {
|
|
10
8
|
getRemovable,
|
|
11
9
|
createId,
|
|
12
10
|
deepQuery
|
|
13
|
-
};
|
|
14
|
-
// Example: a comment that references a post that doesn't exist
|
|
11
|
+
};
|
|
15
12
|
|
|
13
|
+
// Returns document ids that have unsatisfied relations
|
|
14
|
+
// Example: a comment that references a post that doesn't exist
|
|
16
15
|
function getRemovable(db, opts) {
|
|
17
16
|
const _ = this;
|
|
18
|
-
|
|
19
17
|
const removable = [];
|
|
20
|
-
|
|
21
18
|
_.each(db, (coll, collName) => {
|
|
22
19
|
_.each(coll, doc => {
|
|
23
20
|
_.each(doc, (value, key) => {
|
|
24
21
|
if (new RegExp(`${opts.foreignKeySuffix}$`).test(key)) {
|
|
25
22
|
// Remove foreign key suffix and pluralize it
|
|
26
23
|
// Example postId -> posts
|
|
27
|
-
const refName = pluralize.plural(key.replace(new RegExp(`${opts.foreignKeySuffix}$`), ''));
|
|
28
|
-
|
|
24
|
+
const refName = pluralize.plural(key.replace(new RegExp(`${opts.foreignKeySuffix}$`), ''));
|
|
25
|
+
// Test if table exists
|
|
29
26
|
if (db[refName]) {
|
|
30
27
|
// Test if references is defined in table
|
|
31
28
|
const ref = _.getById(db[refName], value);
|
|
32
|
-
|
|
33
29
|
if (_.isUndefined(ref)) {
|
|
34
30
|
removable.push({
|
|
35
31
|
name: collName,
|
|
@@ -41,30 +37,25 @@ function getRemovable(db, opts) {
|
|
|
41
37
|
});
|
|
42
38
|
});
|
|
43
39
|
});
|
|
44
|
-
|
|
45
40
|
return removable;
|
|
46
|
-
}
|
|
47
|
-
// Used to override lodash-id's createId with utils.createId
|
|
48
|
-
|
|
41
|
+
}
|
|
49
42
|
|
|
43
|
+
// Return incremented id or uuid
|
|
44
|
+
// Used to override lodash-id's createId with utils.createId
|
|
50
45
|
function createId(coll) {
|
|
51
46
|
const _ = this;
|
|
52
|
-
|
|
53
47
|
const idProperty = _.__id();
|
|
54
|
-
|
|
55
48
|
if (_.isEmpty(coll)) {
|
|
56
49
|
return 1;
|
|
57
50
|
} else {
|
|
58
|
-
let id = _(coll).maxBy(idProperty)[idProperty];
|
|
59
|
-
|
|
51
|
+
let id = _(coll).maxBy(idProperty)[idProperty];
|
|
60
52
|
|
|
53
|
+
// Increment integer id or generate string id
|
|
61
54
|
return _.isFinite(id) ? ++id : nanoid(7);
|
|
62
55
|
}
|
|
63
56
|
}
|
|
64
|
-
|
|
65
57
|
function deepQuery(value, q) {
|
|
66
58
|
const _ = this;
|
|
67
|
-
|
|
68
59
|
if (value && q) {
|
|
69
60
|
if (_.isArray(value)) {
|
|
70
61
|
for (let i = 0; i < value.length; i++) {
|
package/lib/server/rewriter.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const pause = require('connect-pause');
|
|
4
|
-
|
|
5
4
|
module.exports = function delay(req, res, next) {
|
|
6
5
|
// NOTE: for some reason unknown to me, if the default is 0, the tests seems to add 2 seconds
|
|
7
6
|
// NOTE: to each test, a default value of 1 does not seem to be effected by that issue
|
|
8
7
|
const _delay = !isNaN(parseFloat(req.query._delay)) ? parseFloat(req.query._delay) : 1;
|
|
9
|
-
|
|
10
8
|
delete req.query._delay;
|
|
11
9
|
pause(_delay)(req, res, next);
|
|
12
10
|
};
|
|
@@ -1,81 +1,75 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const express = require('express');
|
|
4
|
-
|
|
5
4
|
const methodOverride = require('method-override');
|
|
6
|
-
|
|
7
5
|
const _ = require('lodash');
|
|
8
|
-
|
|
9
6
|
const lodashId = require('lodash-id');
|
|
10
|
-
|
|
11
7
|
const low = require('lowdb');
|
|
12
|
-
|
|
13
8
|
const Memory = require('lowdb/adapters/Memory');
|
|
14
|
-
|
|
15
9
|
const FileSync = require('lowdb/adapters/FileSync');
|
|
16
|
-
|
|
17
10
|
const bodyParser = require('../body-parser');
|
|
18
|
-
|
|
19
11
|
const validateData = require('./validate-data');
|
|
20
|
-
|
|
21
12
|
const plural = require('./plural');
|
|
22
|
-
|
|
23
13
|
const nested = require('./nested');
|
|
24
|
-
|
|
25
14
|
const singular = require('./singular');
|
|
26
|
-
|
|
27
15
|
const mixins = require('../mixins');
|
|
28
|
-
|
|
29
16
|
module.exports = (db, opts) => {
|
|
30
17
|
opts = Object.assign({
|
|
31
18
|
foreignKeySuffix: 'Id',
|
|
32
19
|
_isFake: false
|
|
33
20
|
}, opts);
|
|
34
|
-
|
|
35
21
|
if (typeof db === 'string') {
|
|
36
22
|
db = low(new FileSync(db));
|
|
37
23
|
} else if (!_.has(db, '__chain__') || !_.has(db, '__wrapped__')) {
|
|
38
24
|
db = low(new Memory()).setState(db);
|
|
39
|
-
}
|
|
40
|
-
|
|
25
|
+
}
|
|
41
26
|
|
|
42
|
-
|
|
27
|
+
// Create router
|
|
28
|
+
const router = express.Router();
|
|
43
29
|
|
|
30
|
+
// Add middlewares
|
|
44
31
|
router.use(methodOverride());
|
|
45
32
|
router.use(bodyParser);
|
|
46
|
-
validateData(db.getState());
|
|
33
|
+
validateData(db.getState());
|
|
47
34
|
|
|
48
|
-
|
|
35
|
+
// Add lodash-id methods to db
|
|
36
|
+
db._.mixin(lodashId);
|
|
49
37
|
|
|
38
|
+
// Add specific mixins
|
|
39
|
+
db._.mixin(mixins);
|
|
50
40
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
router.db = db; // Expose render
|
|
41
|
+
// Expose database
|
|
42
|
+
router.db = db;
|
|
55
43
|
|
|
44
|
+
// Expose render
|
|
56
45
|
router.render = (req, res) => {
|
|
57
46
|
res.jsonp(res.locals.data);
|
|
58
|
-
};
|
|
59
|
-
|
|
47
|
+
};
|
|
60
48
|
|
|
49
|
+
// GET /db
|
|
61
50
|
router.get('/db', (req, res) => {
|
|
62
51
|
res.jsonp(db.getState());
|
|
63
|
-
});
|
|
52
|
+
});
|
|
64
53
|
|
|
65
|
-
|
|
54
|
+
// Handle /:parent/:parentId/:resource
|
|
55
|
+
router.use(nested(opts));
|
|
66
56
|
|
|
57
|
+
// Create routes
|
|
67
58
|
db.forEach((value, key) => {
|
|
59
|
+
if (key === '$schema') {
|
|
60
|
+
// ignore $schema
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
68
63
|
if (_.isPlainObject(value)) {
|
|
69
64
|
router.use(`/${key}`, singular(db, key, opts));
|
|
70
65
|
return;
|
|
71
66
|
}
|
|
72
|
-
|
|
73
67
|
if (_.isArray(value)) {
|
|
74
68
|
router.use(`/${key}`, plural(db, key, opts));
|
|
75
69
|
return;
|
|
76
70
|
}
|
|
77
|
-
|
|
78
|
-
|
|
71
|
+
const sourceMessage = '';
|
|
72
|
+
// if (!_.isObject(source)) {
|
|
79
73
|
// sourceMessage = `in ${source}`
|
|
80
74
|
// }
|
|
81
75
|
|
|
@@ -87,7 +81,6 @@ module.exports = (db, opts) => {
|
|
|
87
81
|
res.status(404);
|
|
88
82
|
res.locals.data = {};
|
|
89
83
|
}
|
|
90
|
-
|
|
91
84
|
router.render(req, res);
|
|
92
85
|
});
|
|
93
86
|
router.use((err, req, res, next) => {
|
|
@@ -1,29 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const express = require('express');
|
|
4
|
-
|
|
5
4
|
const pluralize = require('pluralize');
|
|
6
|
-
|
|
7
5
|
const delay = require('./delay');
|
|
8
|
-
|
|
9
6
|
module.exports = opts => {
|
|
10
7
|
const router = express.Router();
|
|
11
|
-
router.use(delay);
|
|
8
|
+
router.use(delay);
|
|
12
9
|
|
|
10
|
+
// Rewrite URL (/:resource/:id/:nested -> /:nested) and request query
|
|
13
11
|
function get(req, res, next) {
|
|
14
12
|
const prop = pluralize.singular(req.params.resource);
|
|
15
13
|
req.query[`${prop}${opts.foreignKeySuffix}`] = req.params.id;
|
|
16
14
|
req.url = `/${req.params.nested}`;
|
|
17
15
|
next();
|
|
18
|
-
}
|
|
19
|
-
|
|
16
|
+
}
|
|
20
17
|
|
|
18
|
+
// Rewrite URL (/:resource/:id/:nested -> /:nested) and request body
|
|
21
19
|
function post(req, res, next) {
|
|
22
20
|
const prop = pluralize.singular(req.params.resource);
|
|
23
21
|
req.body[`${prop}${opts.foreignKeySuffix}`] = req.params.id;
|
|
24
22
|
req.url = `/${req.params.nested}`;
|
|
25
23
|
next();
|
|
26
24
|
}
|
|
27
|
-
|
|
28
25
|
return router.get('/:resource/:id/:nested', get).post('/:resource/:id/:nested', post);
|
|
29
26
|
};
|
|
@@ -1,30 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
function ownKeys(
|
|
4
|
-
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
function
|
|
8
|
-
|
|
3
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
4
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
5
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
6
|
+
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
|
|
7
|
+
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
9
8
|
const express = require('express');
|
|
10
|
-
|
|
11
9
|
const _ = require('lodash');
|
|
12
|
-
|
|
13
10
|
const pluralize = require('pluralize');
|
|
14
|
-
|
|
15
11
|
const write = require('./write');
|
|
16
|
-
|
|
17
12
|
const getFullURL = require('./get-full-url');
|
|
18
|
-
|
|
19
13
|
const utils = require('../utils');
|
|
20
|
-
|
|
21
14
|
const delay = require('./delay');
|
|
22
|
-
|
|
23
15
|
module.exports = (db, name, opts) => {
|
|
24
16
|
// Create router
|
|
25
17
|
const router = express.Router();
|
|
26
|
-
router.use(delay);
|
|
18
|
+
router.use(delay);
|
|
27
19
|
|
|
20
|
+
// Embed function used in GET /name and GET /name/id
|
|
28
21
|
function embed(resource, e) {
|
|
29
22
|
e && [].concat(e).forEach(externalResource => {
|
|
30
23
|
if (db.get(externalResource).value) {
|
|
@@ -34,31 +27,31 @@ module.exports = (db, name, opts) => {
|
|
|
34
27
|
resource[externalResource] = db.get(externalResource).filter(query).value();
|
|
35
28
|
}
|
|
36
29
|
});
|
|
37
|
-
}
|
|
38
|
-
|
|
30
|
+
}
|
|
39
31
|
|
|
32
|
+
// Expand function used in GET /name and GET /name/id
|
|
40
33
|
function expand(resource, e) {
|
|
41
34
|
e && [].concat(e).forEach(innerResource => {
|
|
42
35
|
const plural = pluralize(innerResource);
|
|
43
|
-
|
|
44
36
|
if (db.get(plural).value()) {
|
|
45
37
|
const prop = `${innerResource}${opts.foreignKeySuffix}`;
|
|
46
38
|
resource[innerResource] = db.get(plural).getById(resource[prop]).value();
|
|
47
39
|
}
|
|
48
40
|
});
|
|
49
|
-
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// GET /name
|
|
50
44
|
// GET /name?q=
|
|
51
45
|
// GET /name?attr=&attr=
|
|
52
46
|
// GET /name?_end=&
|
|
53
47
|
// GET /name?_start=&_end=&
|
|
54
48
|
// GET /name?_embed=&_expand=
|
|
55
|
-
|
|
56
|
-
|
|
57
49
|
function list(req, res, next) {
|
|
58
50
|
// Resource chain
|
|
59
|
-
let chain = db.get(name);
|
|
60
|
-
// parameters
|
|
51
|
+
let chain = db.get(name);
|
|
61
52
|
|
|
53
|
+
// Remove q, _start, _end, ... from req.query to avoid filtering using those
|
|
54
|
+
// parameters
|
|
62
55
|
let q = req.query.q;
|
|
63
56
|
let _start = req.query._start;
|
|
64
57
|
let _end = req.query._end;
|
|
@@ -75,39 +68,33 @@ module.exports = (db, name, opts) => {
|
|
|
75
68
|
delete req.query._order;
|
|
76
69
|
delete req.query._limit;
|
|
77
70
|
delete req.query._embed;
|
|
78
|
-
delete req.query._expand;
|
|
79
|
-
// in the database
|
|
71
|
+
delete req.query._expand;
|
|
80
72
|
|
|
73
|
+
// Automatically delete query parameters that can't be found
|
|
74
|
+
// in the database
|
|
81
75
|
Object.keys(req.query).forEach(query => {
|
|
82
76
|
const arr = db.get(name).value();
|
|
83
|
-
|
|
84
77
|
for (const i in arr) {
|
|
85
78
|
if (_.has(arr[i], query) || query === 'callback' || query === '_' || /_lte$/.test(query) || /_gte$/.test(query) || /_ne$/.test(query) || /_like$/.test(query)) return;
|
|
86
79
|
}
|
|
87
|
-
|
|
88
80
|
delete req.query[query];
|
|
89
81
|
});
|
|
90
|
-
|
|
91
82
|
if (q) {
|
|
92
83
|
// Full-text search
|
|
93
84
|
if (Array.isArray(q)) {
|
|
94
85
|
q = q[0];
|
|
95
86
|
}
|
|
96
|
-
|
|
97
87
|
q = q.toLowerCase();
|
|
98
88
|
chain = chain.filter(obj => {
|
|
99
89
|
for (const key in obj) {
|
|
100
90
|
const value = obj[key];
|
|
101
|
-
|
|
102
91
|
if (db._.deepQuery(value, q)) {
|
|
103
92
|
return true;
|
|
104
93
|
}
|
|
105
94
|
}
|
|
106
|
-
|
|
107
95
|
return false;
|
|
108
96
|
});
|
|
109
97
|
}
|
|
110
|
-
|
|
111
98
|
Object.keys(req.query).forEach(key => {
|
|
112
99
|
// Don't take into account JSONP query parameters
|
|
113
100
|
// jQuery adds a '_' query parameter too
|
|
@@ -122,13 +109,12 @@ module.exports = (db, name, opts) => {
|
|
|
122
109
|
return arr.map(function (value) {
|
|
123
110
|
// get item value based on path
|
|
124
111
|
// i.e post.title -> 'foo'
|
|
125
|
-
const elementValue = _.get(element, path);
|
|
126
|
-
|
|
112
|
+
const elementValue = _.get(element, path);
|
|
127
113
|
|
|
114
|
+
// Prevent toString() failing on undefined or null values
|
|
128
115
|
if (elementValue === undefined || elementValue === null) {
|
|
129
116
|
return undefined;
|
|
130
117
|
}
|
|
131
|
-
|
|
132
118
|
if (isRange) {
|
|
133
119
|
const isLowerThan = /_gte$/.test(key);
|
|
134
120
|
return isLowerThan ? value <= elementValue : value >= elementValue;
|
|
@@ -142,22 +128,20 @@ module.exports = (db, name, opts) => {
|
|
|
142
128
|
}).reduce((a, b) => isDifferent ? a && b : a || b);
|
|
143
129
|
});
|
|
144
130
|
}
|
|
145
|
-
});
|
|
131
|
+
});
|
|
146
132
|
|
|
133
|
+
// Sort
|
|
147
134
|
if (_sort) {
|
|
148
135
|
const _sortSet = _sort.split(',');
|
|
149
|
-
|
|
150
136
|
const _orderSet = (_order || '').split(',').map(s => s.toLowerCase());
|
|
151
|
-
|
|
152
137
|
chain = chain.orderBy(_sortSet, _orderSet);
|
|
153
|
-
}
|
|
154
|
-
|
|
138
|
+
}
|
|
155
139
|
|
|
140
|
+
// Slice result
|
|
156
141
|
if (_end || _limit || _page) {
|
|
157
142
|
res.setHeader('X-Total-Count', chain.size());
|
|
158
143
|
res.setHeader('Access-Control-Expose-Headers', `X-Total-Count${_page ? ', Link' : ''}`);
|
|
159
144
|
}
|
|
160
|
-
|
|
161
145
|
if (_page) {
|
|
162
146
|
_page = parseInt(_page, 10);
|
|
163
147
|
_page = _page >= 1 ? _page : 1;
|
|
@@ -165,23 +149,18 @@ module.exports = (db, name, opts) => {
|
|
|
165
149
|
const page = utils.getPage(chain.value(), _page, _limit);
|
|
166
150
|
const links = {};
|
|
167
151
|
const fullURL = getFullURL(req);
|
|
168
|
-
|
|
169
152
|
if (page.first) {
|
|
170
153
|
links.first = fullURL.replace(`page=${page.current}`, `page=${page.first}`);
|
|
171
154
|
}
|
|
172
|
-
|
|
173
155
|
if (page.prev) {
|
|
174
156
|
links.prev = fullURL.replace(`page=${page.current}`, `page=${page.prev}`);
|
|
175
157
|
}
|
|
176
|
-
|
|
177
158
|
if (page.next) {
|
|
178
159
|
links.next = fullURL.replace(`page=${page.current}`, `page=${page.next}`);
|
|
179
160
|
}
|
|
180
|
-
|
|
181
161
|
if (page.last) {
|
|
182
162
|
links.last = fullURL.replace(`page=${page.current}`, `page=${page.last}`);
|
|
183
163
|
}
|
|
184
|
-
|
|
185
164
|
res.links(links);
|
|
186
165
|
chain = _.chain(page.items);
|
|
187
166
|
} else if (_end) {
|
|
@@ -192,44 +171,42 @@ module.exports = (db, name, opts) => {
|
|
|
192
171
|
_start = parseInt(_start, 10) || 0;
|
|
193
172
|
_limit = parseInt(_limit, 10);
|
|
194
173
|
chain = chain.slice(_start, _start + _limit);
|
|
195
|
-
}
|
|
196
|
-
|
|
174
|
+
}
|
|
197
175
|
|
|
176
|
+
// embed and expand
|
|
198
177
|
chain = chain.cloneDeep().forEach(function (element) {
|
|
199
178
|
embed(element, _embed);
|
|
200
179
|
expand(element, _expand);
|
|
201
180
|
});
|
|
202
181
|
res.locals.data = chain.value();
|
|
203
182
|
next();
|
|
204
|
-
}
|
|
205
|
-
// GET /name/:id?_embed=&_expand
|
|
206
|
-
|
|
183
|
+
}
|
|
207
184
|
|
|
185
|
+
// GET /name/:id
|
|
186
|
+
// GET /name/:id?_embed=&_expand
|
|
208
187
|
function show(req, res, next) {
|
|
209
188
|
const _embed = req.query._embed;
|
|
210
189
|
const _expand = req.query._expand;
|
|
211
190
|
const resource = db.get(name).getById(req.params.id).value();
|
|
212
|
-
|
|
213
191
|
if (resource) {
|
|
214
192
|
// Clone resource to avoid making changes to the underlying object
|
|
215
|
-
const clone = _.cloneDeep(resource);
|
|
216
|
-
// /posts/1?_embed=comments
|
|
193
|
+
const clone = _.cloneDeep(resource);
|
|
217
194
|
|
|
195
|
+
// Embed other resources based on resource id
|
|
196
|
+
// /posts/1?_embed=comments
|
|
197
|
+
embed(clone, _embed);
|
|
218
198
|
|
|
219
|
-
|
|
199
|
+
// Expand inner resources based on id
|
|
220
200
|
// /posts/1?_expand=user
|
|
221
|
-
|
|
222
201
|
expand(clone, _expand);
|
|
223
202
|
res.locals.data = clone;
|
|
224
203
|
}
|
|
225
|
-
|
|
226
204
|
next();
|
|
227
|
-
}
|
|
228
|
-
|
|
205
|
+
}
|
|
229
206
|
|
|
207
|
+
// POST /name
|
|
230
208
|
function create(req, res, next) {
|
|
231
209
|
let resource;
|
|
232
|
-
|
|
233
210
|
if (opts._isFake) {
|
|
234
211
|
const id = db.get(name).createId().value();
|
|
235
212
|
resource = _objectSpread(_objectSpread({}, req.body), {}, {
|
|
@@ -238,23 +215,20 @@ module.exports = (db, name, opts) => {
|
|
|
238
215
|
} else {
|
|
239
216
|
resource = db.get(name).insert(req.body).value();
|
|
240
217
|
}
|
|
241
|
-
|
|
242
218
|
res.setHeader('Access-Control-Expose-Headers', 'Location');
|
|
243
219
|
res.location(`${getFullURL(req)}/${resource.id}`);
|
|
244
220
|
res.status(201);
|
|
245
221
|
res.locals.data = resource;
|
|
246
222
|
next();
|
|
247
|
-
}
|
|
248
|
-
// PATCH /name/:id
|
|
249
|
-
|
|
223
|
+
}
|
|
250
224
|
|
|
225
|
+
// PUT /name/:id
|
|
226
|
+
// PATCH /name/:id
|
|
251
227
|
function update(req, res, next) {
|
|
252
228
|
const id = req.params.id;
|
|
253
229
|
let resource;
|
|
254
|
-
|
|
255
230
|
if (opts._isFake) {
|
|
256
231
|
resource = db.get(name).getById(id).value();
|
|
257
|
-
|
|
258
232
|
if (req.method === 'PATCH') {
|
|
259
233
|
resource = _objectSpread(_objectSpread({}, resource), req.body);
|
|
260
234
|
} else {
|
|
@@ -267,37 +241,31 @@ module.exports = (db, name, opts) => {
|
|
|
267
241
|
chain = req.method === 'PATCH' ? chain.updateById(id, req.body) : chain.replaceById(id, req.body);
|
|
268
242
|
resource = chain.value();
|
|
269
243
|
}
|
|
270
|
-
|
|
271
244
|
if (resource) {
|
|
272
245
|
res.locals.data = resource;
|
|
273
246
|
}
|
|
274
|
-
|
|
275
247
|
next();
|
|
276
|
-
}
|
|
277
|
-
|
|
248
|
+
}
|
|
278
249
|
|
|
250
|
+
// DELETE /name/:id
|
|
279
251
|
function destroy(req, res, next) {
|
|
280
252
|
let resource;
|
|
281
|
-
|
|
282
253
|
if (opts._isFake) {
|
|
283
254
|
resource = db.get(name).value();
|
|
284
255
|
} else {
|
|
285
|
-
resource = db.get(name).removeById(req.params.id).value();
|
|
256
|
+
resource = db.get(name).removeById(req.params.id).value();
|
|
286
257
|
|
|
258
|
+
// Remove dependents documents
|
|
287
259
|
const removable = db._.getRemovable(db.getState(), opts);
|
|
288
|
-
|
|
289
260
|
removable.forEach(item => {
|
|
290
261
|
db.get(item.name).removeById(item.id).value();
|
|
291
262
|
});
|
|
292
263
|
}
|
|
293
|
-
|
|
294
264
|
if (resource) {
|
|
295
265
|
res.locals.data = {};
|
|
296
266
|
}
|
|
297
|
-
|
|
298
267
|
next();
|
|
299
268
|
}
|
|
300
|
-
|
|
301
269
|
const w = write(db);
|
|
302
270
|
router.route('/').get(list).post(create, w);
|
|
303
271
|
router.route('/:id').get(show).put(update, w).patch(update, w).delete(destroy, w);
|
|
@@ -1,28 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
function ownKeys(
|
|
4
|
-
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
function
|
|
8
|
-
|
|
3
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
4
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
5
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
6
|
+
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
|
|
7
|
+
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
9
8
|
const express = require('express');
|
|
10
|
-
|
|
11
9
|
const write = require('./write');
|
|
12
|
-
|
|
13
10
|
const getFullURL = require('./get-full-url');
|
|
14
|
-
|
|
15
11
|
const delay = require('./delay');
|
|
16
|
-
|
|
17
12
|
module.exports = (db, name, opts) => {
|
|
18
13
|
const router = express.Router();
|
|
19
14
|
router.use(delay);
|
|
20
|
-
|
|
21
15
|
function show(req, res, next) {
|
|
22
16
|
res.locals.data = db.get(name).value();
|
|
23
17
|
next();
|
|
24
18
|
}
|
|
25
|
-
|
|
26
19
|
function create(req, res, next) {
|
|
27
20
|
if (opts._isFake) {
|
|
28
21
|
res.locals.data = req.body;
|
|
@@ -30,13 +23,11 @@ module.exports = (db, name, opts) => {
|
|
|
30
23
|
db.set(name, req.body).value();
|
|
31
24
|
res.locals.data = db.get(name).value();
|
|
32
25
|
}
|
|
33
|
-
|
|
34
26
|
res.setHeader('Access-Control-Expose-Headers', 'Location');
|
|
35
27
|
res.location(`${getFullURL(req)}`);
|
|
36
28
|
res.status(201);
|
|
37
29
|
next();
|
|
38
30
|
}
|
|
39
|
-
|
|
40
31
|
function update(req, res, next) {
|
|
41
32
|
if (opts._isFake) {
|
|
42
33
|
if (req.method === 'PUT') {
|
|
@@ -51,13 +42,10 @@ module.exports = (db, name, opts) => {
|
|
|
51
42
|
} else {
|
|
52
43
|
db.get(name).assign(req.body).value();
|
|
53
44
|
}
|
|
54
|
-
|
|
55
45
|
res.locals.data = db.get(name).value();
|
|
56
46
|
}
|
|
57
|
-
|
|
58
47
|
next();
|
|
59
48
|
}
|
|
60
|
-
|
|
61
49
|
const w = write(db);
|
|
62
50
|
router.route('/').get(show).post(create, w).put(update, w).patch(update, w);
|
|
63
51
|
return router;
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
|
-
|
|
5
4
|
function validateKey(key) {
|
|
6
5
|
if (key.indexOf('/') !== -1) {
|
|
7
6
|
const msg = [`Oops, found / character in database property '${key}'.`, '', "/ aren't supported, if you want to tweak default routes, see", 'https://github.com/typicode/json-server/#add-custom-routes'].join('\n');
|
|
8
7
|
throw new Error(msg);
|
|
9
8
|
}
|
|
10
9
|
}
|
|
11
|
-
|
|
12
10
|
module.exports = obj => {
|
|
13
11
|
if (_.isPlainObject(obj)) {
|
|
14
12
|
Object.keys(obj).forEach(validateKey);
|
|
15
13
|
} else {
|
|
16
|
-
throw new Error(`Data must be an object. Found ${Array.isArray(obj) ? 'array' : typeof obj}
|
|
14
|
+
throw new Error(`Data must be an object. Found ${Array.isArray(obj) ? 'array' : typeof obj}.
|
|
17
15
|
'See https://github.com/typicode/json-server for example.`);
|
|
18
16
|
}
|
|
19
17
|
};
|
package/lib/server/utils.js
CHANGED
|
@@ -3,30 +3,24 @@
|
|
|
3
3
|
module.exports = {
|
|
4
4
|
getPage
|
|
5
5
|
};
|
|
6
|
-
|
|
7
6
|
function getPage(array, page, perPage) {
|
|
8
7
|
const obj = {};
|
|
9
8
|
const start = (page - 1) * perPage;
|
|
10
9
|
const end = page * perPage;
|
|
11
10
|
obj.items = array.slice(start, end);
|
|
12
|
-
|
|
13
11
|
if (obj.items.length === 0) {
|
|
14
12
|
return obj;
|
|
15
13
|
}
|
|
16
|
-
|
|
17
14
|
if (page > 1) {
|
|
18
15
|
obj.prev = page - 1;
|
|
19
16
|
}
|
|
20
|
-
|
|
21
17
|
if (end < array.length) {
|
|
22
18
|
obj.next = page + 1;
|
|
23
19
|
}
|
|
24
|
-
|
|
25
20
|
if (obj.items.length !== array.length) {
|
|
26
21
|
obj.current = page;
|
|
27
22
|
obj.first = 1;
|
|
28
23
|
obj.last = Math.ceil(array.length / perPage);
|
|
29
24
|
}
|
|
30
|
-
|
|
31
25
|
return obj;
|
|
32
26
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "json-server",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.4",
|
|
4
4
|
"description": "Get a full fake REST API with zero coding in less than 30 seconds",
|
|
5
5
|
"main": "./lib/server/index.js",
|
|
6
6
|
"bin": "./lib/cli/bin.js",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
],
|
|
11
11
|
"scripts": {
|
|
12
12
|
"prepare": "husky install",
|
|
13
|
-
"test": "npm run build && cross-env NODE_ENV=test jest
|
|
13
|
+
"test": "npm run build && cross-env NODE_ENV=test jest",
|
|
14
14
|
"start": "babel-node -- src/cli/bin db.json -r routes.json",
|
|
15
15
|
"lint": "eslint . --ignore-path .gitignore",
|
|
16
16
|
"fix": "npm run lint -- --fix",
|
|
@@ -38,7 +38,6 @@
|
|
|
38
38
|
"please-upgrade-node": "^3.2.0",
|
|
39
39
|
"pluralize": "^8.0.0",
|
|
40
40
|
"server-destroy": "^1.0.1",
|
|
41
|
-
"standard": "^17.0.0",
|
|
42
41
|
"yargs": "^17.0.1"
|
|
43
42
|
},
|
|
44
43
|
"devDependencies": {
|
|
@@ -64,6 +63,7 @@
|
|
|
64
63
|
"prettier": "^2.3.2",
|
|
65
64
|
"rimraf": "^3.0.2",
|
|
66
65
|
"server-ready": "^0.3.1",
|
|
66
|
+
"standard": "^17.1.0",
|
|
67
67
|
"supertest": "^6.0.1",
|
|
68
68
|
"temp-write": "^4.0.0"
|
|
69
69
|
},
|