json-server 1.0.0-beta.10 → 1.0.0-beta.13

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.
@@ -0,0 +1,31 @@
1
+ import { randomId } from "../random-id.js";
2
+ export const DEFAULT_SCHEMA_PATH = './node_modules/json-server/schema.json';
3
+ export class NormalizedAdapter {
4
+ #adapter;
5
+ constructor(adapter) {
6
+ this.#adapter = adapter;
7
+ }
8
+ async read() {
9
+ const data = await this.#adapter.read();
10
+ if (data === null) {
11
+ return null;
12
+ }
13
+ delete data['$schema'];
14
+ for (const value of Object.values(data)) {
15
+ if (Array.isArray(value)) {
16
+ for (const item of value) {
17
+ if (typeof item['id'] === 'number') {
18
+ item['id'] = item['id'].toString();
19
+ }
20
+ if (item['id'] === undefined) {
21
+ item['id'] = randomId();
22
+ }
23
+ }
24
+ }
25
+ }
26
+ return data;
27
+ }
28
+ async write(data) {
29
+ await this.#adapter.write({ ...data, $schema: DEFAULT_SCHEMA_PATH });
30
+ }
31
+ }
package/lib/app.js CHANGED
@@ -52,18 +52,22 @@ function parseListParams(req) {
52
52
  function withBody(action) {
53
53
  return async (req, res, next) => {
54
54
  const { name = '' } = req.params;
55
- if (isItem(req.body)) {
56
- res.locals['data'] = await action(name, req.body);
55
+ if (!isItem(req.body)) {
56
+ res.status(400).json({ error: 'Body must be a JSON object' });
57
+ return;
57
58
  }
59
+ res.locals['data'] = await action(name, req.body);
58
60
  next?.();
59
61
  };
60
62
  }
61
63
  function withIdAndBody(action) {
62
64
  return async (req, res, next) => {
63
65
  const { name = '', id = '' } = req.params;
64
- if (isItem(req.body)) {
65
- res.locals['data'] = await action(name, id, req.body);
66
+ if (!isItem(req.body)) {
67
+ res.status(400).json({ error: 'Body must be a JSON object' });
68
+ return;
66
69
  }
70
+ res.locals['data'] = await action(name, id, req.body);
67
71
  next?.();
68
72
  };
69
73
  }
package/lib/bin.js CHANGED
@@ -8,8 +8,9 @@ import JSON5 from "json5";
8
8
  import { Low } from "lowdb";
9
9
  import { DataFile, JSONFile } from "lowdb/node";
10
10
  import { fileURLToPath } from "node:url";
11
+ import { NormalizedAdapter } from "./adapters/normalized-adapter.js";
12
+ import { Observer } from "./adapters/observer.js";
11
13
  import { createApp } from "./app.js";
12
- import { Observer } from "./observer.js";
13
14
  function help() {
14
15
  console.log(`Usage: json-server [options] <file>
15
16
 
@@ -109,7 +110,7 @@ if (extname(file) === ".json5") {
109
110
  else {
110
111
  adapter = new JSONFile(file);
111
112
  }
112
- const observer = new Observer(adapter);
113
+ const observer = new Observer(new NormalizedAdapter(adapter));
113
114
  const db = new Low(observer, {});
114
115
  await db.read();
115
116
  // Create app
@@ -0,0 +1,4 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ export function randomId() {
3
+ return randomBytes(2).toString('hex');
4
+ }
package/lib/service.js CHANGED
@@ -1,19 +1,12 @@
1
- import { randomBytes } from 'node:crypto';
2
1
  import inflection from 'inflection';
3
2
  import { Low } from 'lowdb';
4
3
  import sortOn from 'sort-on';
5
4
  import { matchesWhere } from "./matches-where.js";
6
5
  import { paginate } from "./paginate.js";
6
+ import { randomId } from "./random-id.js";
7
7
  export function isItem(obj) {
8
8
  return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
9
9
  }
10
- export function isData(obj) {
11
- if (typeof obj !== 'object' || obj === null) {
12
- return false;
13
- }
14
- const data = obj;
15
- return Object.values(data).every((value) => Array.isArray(value) ? value.every(isItem) : isItem(value));
16
- }
17
10
  function ensureArray(arg = []) {
18
11
  return Array.isArray(arg) ? arg : [arg];
19
12
  }
@@ -65,31 +58,9 @@ function deleteDependents(db, name, dependents) {
65
58
  }
66
59
  });
67
60
  }
68
- function randomId() {
69
- return randomBytes(2).toString('hex');
70
- }
71
- function fixItemsIds(items) {
72
- items.forEach((item) => {
73
- if (typeof item['id'] === 'number') {
74
- item['id'] = item['id'].toString();
75
- }
76
- if (item['id'] === undefined) {
77
- item['id'] = randomId();
78
- }
79
- });
80
- }
81
- // Ensure all items have an id
82
- function fixAllItemsIds(data) {
83
- Object.values(data).forEach((value) => {
84
- if (Array.isArray(value)) {
85
- fixItemsIds(value);
86
- }
87
- });
88
- }
89
61
  export class Service {
90
62
  #db;
91
63
  constructor(db) {
92
- fixAllItemsIds(db.data);
93
64
  this.#db = db;
94
65
  }
95
66
  #get(name) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-server",
3
- "version": "1.0.0-beta.10",
3
+ "version": "1.0.0-beta.13",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "JSON",
package/views/index.html CHANGED
@@ -1,95 +1,147 @@
1
1
  <!doctype html>
2
- <html>
2
+ <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>JSON Server</title>
6
7
  <style>
7
- html {
8
- font-size: 16px;
9
- line-height: 1.5;
10
- background-color: #fff;
11
- color: #000;
8
+ :root {
9
+ color-scheme: light dark;
10
+ --fg: light-dark(#111, #e8e8e8);
11
+ --bg: light-dark(#fff, #111214);
12
+ --muted: light-dark(#999, #9aa0a6);
13
+ --line: light-dark(#e5e5e5, #2c2f34);
14
+ --accent: light-dark(#6366f1, #8b90ff);
15
+ --chip-bg: light-dark(#111, #f5f5f5);
16
+ --chip-fg: light-dark(#fff, #111214);
12
17
  }
13
-
14
18
  body {
15
- margin: 0 auto;
16
- max-width: 720px;
17
- padding: 0 16px;
18
- font-family: sans-serif;
19
+ margin: 40px auto;
20
+ max-width: 400px;
21
+ padding: 0 24px;
22
+ font-family:
23
+ ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
24
+ background-color: var(--bg);
25
+ color: var(--fg);
26
+ line-height: 1.5;
27
+ font-size: 14px;
28
+ transition:
29
+ background-color 0.2s ease,
30
+ color 0.2s ease,
31
+ border-color 0.2s ease;
19
32
  }
20
-
21
33
  a {
22
- color: #db2777;
23
- text-decoration: none;
34
+ color: inherit;
24
35
  }
25
-
26
36
  header {
27
- margin-bottom: 32px;
28
- padding: 16px 0;
37
+ margin-bottom: 40px;
38
+ color: var(--muted);
29
39
  }
30
-
31
- nav {
40
+ .topbar {
32
41
  display: flex;
33
42
  justify-content: space-between;
43
+ align-items: baseline;
44
+ margin-bottom: 12px;
45
+ padding-top: 12px;
46
+ border-top: 1px solid var(--line);
34
47
  }
35
-
36
- nav div a {
37
- margin-left: 16px;
48
+ .topbar a {
49
+ text-decoration: none;
50
+ transition: color 0.1s ease;
38
51
  }
39
-
40
- ul {
41
- margin: 0;
42
- padding: 0;
43
- list-style: none;
52
+ .topbar a:first-child {
53
+ color: var(--fg);
44
54
  }
45
-
46
- li {
55
+ .topbar a:hover {
56
+ color: var(--accent);
57
+ }
58
+ .section-title {
47
59
  margin-bottom: 8px;
60
+ color: var(--muted);
48
61
  }
49
-
50
- /* Dark mode styles */
51
- @media (prefers-color-scheme: dark) {
52
- html {
53
- background-color: #1e293b;
54
- color: #fff;
55
- }
56
-
57
- a {
58
- }
62
+ .list {
63
+ display: flex;
64
+ flex-direction: column;
65
+ gap: 4px;
66
+ }
67
+ .list a {
68
+ display: flex;
69
+ justify-content: space-between;
70
+ align-items: center;
71
+ padding: 8px 0;
72
+ text-decoration: none;
73
+ transition: color 0.1s ease;
74
+ }
75
+ .list a:hover {
76
+ color: var(--accent);
77
+ }
78
+ .meta {
79
+ color: var(--muted);
80
+ font-size: 13px;
81
+ }
82
+ .empty {
83
+ color: var(--muted);
84
+ }
85
+ footer {
86
+ margin-top: 48px;
87
+ padding-top: 12px;
88
+ border-top: 1px solid var(--line);
89
+ color: var(--muted);
90
+ font-size: 13px;
91
+ }
92
+ .heart {
93
+ position: fixed;
94
+ bottom: 20px;
95
+ right: 24px;
96
+ display: inline-flex;
97
+ align-items: center;
98
+ justify-content: center;
99
+ width: 32px;
100
+ height: 32px;
101
+ border-radius: 50%;
102
+ color: var(--chip-fg);
103
+ background-color: var(--chip-bg);
104
+ text-decoration: none;
105
+ transition: transform 0.15s ease;
106
+ }
107
+ .heart:hover {
108
+ transform: scale(1.1);
59
109
  }
60
110
  </style>
61
111
  </head>
62
112
 
63
113
  <body>
114
+ <% const resources = Object.entries(it.data ?? {}); %>
115
+
64
116
  <header>
65
- <nav>
66
- <strong>JSON Server</strong>
67
- <div>
68
- <a href="https://github.com/typicode/json-server">Docs</a>
69
- <a href="https://github.com/sponsors/typicode">♡ Sponsor</a>
70
- </div>
71
- </nav>
117
+ <div class="topbar">
118
+ <a href="https://github.com/typicode/json-server" target="_blank">json-server</a>
119
+ <a href="https://github.com/typicode/json-server" target="_blank">README</a>
120
+ </div>
121
+ <div class="intro">Available REST resources from db.json.</div>
72
122
  </header>
73
- <main class="my-12">
74
- <p
75
- class="bg-gradient-to-r from-purple-500 via-pink-500 to-red-500 text-transparent bg-clip-text"
76
- >
77
- ✧*。٩(ˊᗜˋ*)و✧*。
78
- </p>
79
- <% if (Object.keys(it.data).length===0) { %>
80
- <p>No resources found in JSON file</p>
81
- <% } %> <% Object.entries(it.data).forEach(function([name]) { %>
82
- <ul>
83
- <li>
84
- <a href="<%= name %>">/<%= name %></a>
85
- <span>
86
- <% if (Array.isArray(it.data[name])) { %> - <%= it.data[name].length %> <%=
87
- it.data[name].length> 1 ? 'items' : 'item' %>
88
- </span>
89
- <% } %>
90
- </li>
91
- </ul>
92
- <% }) %>
93
- </main>
123
+
124
+ <div class="section-title">Resources</div>
125
+
126
+ <div class="list">
127
+ <% if (resources.length === 0) { %>
128
+ <span class="empty">No resources in db.json.</span>
129
+ <% } else { %> <% resources.forEach(function([name, value]) { const isCollection =
130
+ Array.isArray(value); %>
131
+ <a href="/<%= name %>">
132
+ <span>/<%= name %></span>
133
+ <span class="meta">
134
+ <% if (isCollection) { %> <%= value.length %> items <% } else { %> object <% } %>
135
+ </span>
136
+ </a>
137
+ <% }) %> <% } %>
138
+ </div>
139
+
140
+ <footer>
141
+ <span>To replace this page, create public/index.html.</span>
142
+ </footer>
143
+
144
+ <a href="https://github.com/sponsors/typicode" target="_blank" class="heart">❤</a>
145
+
94
146
  </body>
95
147
  </html>
File without changes