json-server 1.0.0-alpha.10 → 1.0.0-alpha.12

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 CHANGED
@@ -8,7 +8,7 @@
8
8
  ## Install
9
9
 
10
10
  ```shell
11
- npm install json-server@alpha
11
+ npm install json-server
12
12
  ```
13
13
 
14
14
  ## Usage
@@ -40,7 +40,7 @@ $ json-server db.json
40
40
  Get a REST API
41
41
 
42
42
  ```shell
43
- $ curl -H "Accept: application/json" -X GET http://localhost:3000/posts/1
43
+ $ curl http://localhost:3000/posts/1
44
44
  {
45
45
  "id": "1",
46
46
  "title": "a title"
@@ -49,15 +49,20 @@ $ curl -H "Accept: application/json" -X GET http://localhost:3000/posts/1
49
49
 
50
50
  Run `json-server --help` for a list of options
51
51
 
52
+ ## Sponsors ✨
53
+
52
54
  | Sponsors |
53
55
  | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
54
56
  | <a href="https://mockend.com/" target="_blank"><img src="https://jsonplaceholder.typicode.com/mockend.svg" height="70px"></a> |
55
57
  | <a href="https://www.storyblok.com/" target="_blank"><img src="https://github.com/typicode/json-server/assets/5502029/c6b10674-4ada-4616-91b8-59d30046b45a" height="40px"></a> |
58
+ | <a href="https://betterstack.com/" target="_blank"><img src="https://github.com/typicode/json-server/assets/5502029/44679f8f-9671-470d-b77e-26d90b90cbdc" height="40px"></a> |
56
59
 
57
60
  [Become a sponsor and have your company logo here](https://github.com/users/typicode/sponsorship)
58
61
 
59
62
  ## Routes
60
63
 
64
+ Based on the example `db.json`, you'll get the following routes:
65
+
61
66
  ```
62
67
  GET /posts
63
68
  GET /posts/:id
@@ -65,6 +70,8 @@ POST /posts
65
70
  PUT /posts/:id
66
71
  PATCH /posts/:id
67
72
  DELETE /posts/:id
73
+
74
+ # Same for comments
68
75
  ```
69
76
 
70
77
  ```
@@ -124,7 +131,7 @@ GET /posts?_sort=id,-views
124
131
  ```
125
132
  GET /posts?author.name=foo
126
133
  GET /posts?author.email=foo
127
- GET /posts?names[0]=foo
134
+ GET /posts?tags[0]=foo
128
135
  ```
129
136
 
130
137
  ### Embed
package/lib/bin.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
  import { extname, join } from 'node:path';
4
4
  import { parseArgs } from 'node:util';
5
+ import chalk from 'chalk';
5
6
  import { watch } from 'chokidar';
6
7
  import JSON5 from 'json5';
7
8
  import { Low } from 'lowdb';
@@ -54,7 +55,7 @@ const file = positionals[0] ?? '';
54
55
  const port = parseInt(values.port ?? process.env['PORT'] ?? '3000');
55
56
  const host = values.host ?? process.env['HOST'] ?? 'localhost';
56
57
  if (!existsSync(file)) {
57
- console.log(`File ${file} not found`);
58
+ console.log(chalk.red(`File ${file} not found`));
58
59
  process.exit(1);
59
60
  }
60
61
  // Set up database
@@ -73,37 +74,66 @@ const db = new Low(observer, {});
73
74
  await db.read();
74
75
  // Create app
75
76
  const app = createApp(db, { logger: false, static: values.static });
76
- function routes(db) {
77
- return Object.keys(db.data).map((key) => `http://${host}:${port}/${key}`);
77
+ function logRoutes(data) {
78
+ console.log([
79
+ chalk.bold('Endpoints:'),
80
+ ...Object.keys(data).map((key) => `${chalk.gray(`http://${host}:${port}/`)}${chalk.blue(key)}`),
81
+ ].join('\n'));
78
82
  }
83
+ const kaomojis = ['♡⸜(˶˃ ᵕ ˂˶)⸝♡', '♡( ◡‿◡ )', '( ˶ˆ ᗜ ˆ˵ )', '(˶ᵔ ᵕ ᵔ˶)'];
84
+ function randomItem(items) {
85
+ const index = Math.floor(Math.random() * items.length);
86
+ return items.at(index) ?? '';
87
+ }
88
+ app.listen(port, () => {
89
+ console.log([
90
+ chalk.bold(`JSON Server started on PORT :${port}`),
91
+ chalk.gray('Press CTRL-C to stop'),
92
+ chalk.gray(`Watching ${file}...`),
93
+ '',
94
+ chalk.magenta(randomItem(kaomojis)),
95
+ '',
96
+ chalk.bold('Index:'),
97
+ chalk.gray(`http://localhost:${port}/`),
98
+ '',
99
+ chalk.bold('Static files:'),
100
+ chalk.gray('Serving ./public directory if it exists'),
101
+ '',
102
+ ].join('\n'));
103
+ logRoutes(db.data);
104
+ });
79
105
  // Watch file for changes
80
106
  if (process.env['NODE_ENV'] !== 'production') {
81
107
  let writing = false; // true if the file is being written to by the app
108
+ let prevEndpoints = '';
82
109
  observer.onWriteStart = () => {
83
110
  writing = true;
84
111
  };
85
112
  observer.onWriteEnd = () => {
86
113
  writing = false;
87
114
  };
88
- observer.onReadStart = () => console.log(`Reloading ${file}...`);
89
- observer.onReadEnd = () => console.log('Reloaded');
115
+ observer.onReadStart = () => {
116
+ prevEndpoints = JSON.stringify(Object.keys(db.data).sort());
117
+ };
118
+ observer.onReadEnd = (data) => {
119
+ if (data === null) {
120
+ return;
121
+ }
122
+ const nextEndpoints = JSON.stringify(Object.keys(data).sort());
123
+ if (prevEndpoints !== nextEndpoints) {
124
+ console.log();
125
+ logRoutes(data);
126
+ }
127
+ };
90
128
  watch(file).on('change', () => {
91
129
  // Do no reload if the file is being written to by the app
92
130
  if (!writing) {
93
- db.read()
94
- .then(() => routes(db))
95
- .catch((e) => {
131
+ db.read().catch((e) => {
96
132
  if (e instanceof SyntaxError) {
97
- return console.log(e.message);
133
+ return console.log(chalk.red(['', `Error parsing ${file}`, e.message].join('\n')));
98
134
  }
99
135
  console.log(e);
100
136
  });
101
137
  }
102
138
  });
103
139
  }
104
- app.listen(port, () => {
105
- console.log(`Started on :${port}`);
106
- console.log(`http://localhost:${port}/`);
107
- console.log(routes(db).join('\n'));
108
- console.log(`Watching ${file}...`);
109
- });
package/lib/observer.d.ts CHANGED
@@ -2,7 +2,7 @@ import { Adapter } from 'lowdb';
2
2
  export declare class Observer<T> {
3
3
  #private;
4
4
  onReadStart: () => void;
5
- onReadEnd: () => void;
5
+ onReadEnd: (data: T | null) => void;
6
6
  onWriteStart: () => void;
7
7
  onWriteEnd: () => void;
8
8
  constructor(adapter: Adapter<T>);
package/lib/observer.js CHANGED
@@ -19,7 +19,7 @@ export class Observer {
19
19
  async read() {
20
20
  this.onReadStart();
21
21
  const data = await this.#adapter.read();
22
- this.onReadEnd();
22
+ this.onReadEnd(data);
23
23
  return data;
24
24
  }
25
25
  async write(arg) {
package/lib/service.js CHANGED
@@ -72,9 +72,28 @@ function deleteDependents(db, name, dependents) {
72
72
  }
73
73
  });
74
74
  }
75
+ function randomId() {
76
+ return randomBytes(2).toString('hex');
77
+ }
78
+ function ensureItemsHaveIds(items) {
79
+ return items.map((item) => {
80
+ if (item['id'] === undefined) {
81
+ return { ...item, id: randomId() };
82
+ }
83
+ return item;
84
+ });
85
+ }
86
+ // Ensure all items have an id
87
+ function ensureAllItemsHaveIds(data) {
88
+ return Object.entries(data).reduce((acc, [key, value]) => ({
89
+ ...acc,
90
+ [key]: Array.isArray(value) ? ensureItemsHaveIds(value) : value,
91
+ }), {});
92
+ }
75
93
  export class Service {
76
94
  #db;
77
95
  constructor(db) {
96
+ db.data = ensureAllItemsHaveIds(db.data);
78
97
  this.#db = db;
79
98
  }
80
99
  #get(name) {
@@ -226,7 +245,7 @@ export class Service {
226
245
  const items = this.#get(name);
227
246
  if (items === undefined || !Array.isArray(items))
228
247
  return;
229
- const item = { id: randomBytes(2).toString('hex'), ...data };
248
+ const item = { id: randomId(), ...data };
230
249
  items.push(item);
231
250
  await this.#db.write();
232
251
  return item;
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
2
2
  import test from 'node:test';
3
3
  import { Low, Memory } from 'lowdb';
4
4
  import { Service } from './service.js';
5
- const defaultData = { posts: [] };
5
+ const defaultData = { posts: [], comments: [], object: {} };
6
6
  const adapter = new Memory();
7
7
  const db = new Low(adapter, defaultData);
8
8
  const service = new Service(db);
@@ -44,6 +44,16 @@ function reset() {
44
44
  object: obj,
45
45
  });
46
46
  }
47
+ await test('constructor', () => {
48
+ const defaultData = { posts: [{ id: '1' }, {}], object: {} };
49
+ const db = new Low(adapter, defaultData);
50
+ new Service(db);
51
+ if (Array.isArray(db.data['posts'])) {
52
+ const id = db.data['posts']?.at(1)?.['id'];
53
+ assert.ok(id instanceof String, 'id should be a string');
54
+ assert.ok(id.length > 0, 'id should not be empty');
55
+ }
56
+ });
47
57
  await test('findById', () => {
48
58
  reset();
49
59
  if (!Array.isArray(db.data?.[POSTS]))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-server",
3
- "version": "1.0.0-alpha.10",
3
+ "version": "1.0.0-alpha.12",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "types": "lib",
@@ -39,6 +39,7 @@
39
39
  "dependencies": {
40
40
  "@tinyhttp/app": "^2.2.1",
41
41
  "@tinyhttp/cors": "^2.0.0",
42
+ "chalk": "^5.3.0",
42
43
  "chokidar": "^3.5.3",
43
44
  "dot-prop": "^8.0.2",
44
45
  "eta": "^3.2.0",