json-server 1.0.0-beta.4 → 1.0.0-beta.6

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
@@ -3,10 +3,10 @@
3
3
  [![Node.js CI](https://github.com/typicode/json-server/actions/workflows/node.js.yml/badge.svg)](https://github.com/typicode/json-server/actions/workflows/node.js.yml)
4
4
 
5
5
  > [!IMPORTANT]
6
- > Viewing beta v1 documentation – usable but expect breaking changes. For stable version, see [here](https://github.com/typicode/json-server/tree/v0)
6
+ > Viewing beta v1 documentation – usable but expect breaking changes. For stable version, see [here](https://github.com/typicode/json-server/tree/v0.17.4)
7
7
 
8
8
  > [!NOTE]
9
- > Using React ⚛️ ? Check my new project [MistCSS](https://github.com/typicode/mistcss) to write type-safe styles (works with TailwindCSS)
9
+ > Using React ⚛️ and tired of CSS-in-JS? See [MistCSS](https://github.com/typicode/mistcss) 👀
10
10
 
11
11
  ## Install
12
12
 
@@ -20,6 +20,7 @@ Create a `db.json` or `db.json5` file
20
20
 
21
21
  ```json
22
22
  {
23
+ "$schema": "./node_modules/json-server/schema.json",
23
24
  "posts": [
24
25
  { "id": "1", "title": "a title", "views": 100 },
25
26
  { "id": "2", "title": "another title", "views": 200 }
@@ -86,6 +87,7 @@ Run `json-server --help` for a list of options
86
87
  | <a href="https://mockend.com/" target="_blank"><img src="https://jsonplaceholder.typicode.com/mockend.svg" height="100px"></a> |
87
88
  | <a href="https://zuplo.link/json-server-gh"><img src="https://github.com/user-attachments/assets/adfee31f-a8b6-4684-9a9b-af4f03ac5b75" height="100px"></a> |
88
89
  | <a href="https://www.mintlify.com/"><img src="https://github.com/user-attachments/assets/bcc8cc48-b2d9-4577-8939-1eb4196b7cc5" height="100px"></a> |
90
+ | <a href="http://git-tower.com/?utm_source=husky&utm_medium=referral"><img height="100px" alt="tower-dock-icon-light" src="https://github.com/user-attachments/assets/b6b4ab20-beff-4e5c-9845-bb9d60057196" /></a>
89
91
 
90
92
  ### Silver
91
93
 
@@ -108,108 +110,110 @@ Run `json-server --help` for a list of options
108
110
  >
109
111
  > For more information, FAQs, and the rationale behind this, visit [https://fair.io/](https://fair.io/).
110
112
 
113
+ ## Query capabilities overview
114
+
115
+ ```http
116
+ GET /posts?views:gt=100
117
+ GET /posts?_sort=-views
118
+ GET /posts?_page=1&_per_page=10
119
+ GET /posts?_embed=comments
120
+ GET /posts?_where={"or":[{"views":{"gt":100}},{"title":{"eq":"Hello"}}]}
121
+ ```
122
+
111
123
  ## Routes
112
124
 
113
- Based on the example `db.json`, you'll get the following routes:
125
+ For array resources (`posts`, `comments`):
114
126
 
115
- ```
127
+ ```text
116
128
  GET /posts
117
129
  GET /posts/:id
118
130
  POST /posts
119
131
  PUT /posts/:id
120
132
  PATCH /posts/:id
121
133
  DELETE /posts/:id
122
-
123
- # Same for comments
124
134
  ```
125
135
 
126
- ```
136
+ For object resources (`profile`):
137
+
138
+ ```text
127
139
  GET /profile
128
140
  PUT /profile
129
141
  PATCH /profile
130
142
  ```
131
143
 
132
- ## Params
144
+ ## Query params
133
145
 
134
146
  ### Conditions
135
147
 
136
- - ` ` → `==`
137
- - `lt` → `<`
138
- - `lte` → `<=`
139
- - `gt` → `>`
140
- - `gte` → `>=`
141
- - `ne` → `!=`
148
+ Use `field:operator=value`.
142
149
 
143
- ```
144
- GET /posts?views_gt=9000
145
- ```
150
+ Operators:
146
151
 
147
- ### Range
152
+ - no operator -> `eq` (equal)
153
+ - `lt` less than, `lte` less than or equal
154
+ - `gt` greater than, `gte` greater than or equal
155
+ - `eq` equal, `ne` not equal
148
156
 
149
- - `start`
150
- - `end`
151
- - `limit`
157
+ Examples:
152
158
 
153
- ```
154
- GET /posts?_start=10&_end=20
155
- GET /posts?_start=10&_limit=10
156
- ```
157
-
158
- ### Paginate
159
-
160
- - `page`
161
- - `per_page` (default = 10)
162
-
163
- ```
164
- GET /posts?_page=1&_per_page=25
159
+ ```http
160
+ GET /posts?views:gt=100
161
+ GET /posts?title:eq=Hello
162
+ GET /posts?author.name:eq=typicode
165
163
  ```
166
164
 
167
165
  ### Sort
168
166
 
169
- - `_sort=f1,f2`
170
-
167
+ ```http
168
+ GET /posts?_sort=title
169
+ GET /posts?_sort=-views
170
+ GET /posts?_sort=author.name,-views
171
171
  ```
172
- GET /posts?_sort=id,-views
173
- ```
174
-
175
- ### Nested and array fields
176
172
 
177
- - `x.y.z...`
178
- - `x.y.z[i]...`
173
+ ### Pagination
179
174
 
175
+ ```http
176
+ GET /posts?_page=1&_per_page=25
180
177
  ```
181
- GET /foo?a.b=bar
182
- GET /foo?x.y_lt=100
183
- GET /foo?arr[0]=bar
184
- ```
178
+
179
+ - `_per_page` default is `10`
180
+ - invalid page/per_page values are normalized
185
181
 
186
182
  ### Embed
187
183
 
188
- ```
184
+ ```http
189
185
  GET /posts?_embed=comments
190
186
  GET /comments?_embed=post
191
187
  ```
192
188
 
193
- ## Delete
189
+ ### Complex filter with `_where`
194
190
 
191
+ `_where` accepts a JSON object and overrides normal query params when valid.
192
+
193
+ ```http
194
+ GET /posts?_where={"or":[{"views":{"gt":100}},{"author":{"name":{"lt":"m"}}}]}
195
195
  ```
196
- DELETE /posts/1
196
+
197
+ ## Delete dependents
198
+
199
+ ```http
197
200
  DELETE /posts/1?_dependent=comments
198
201
  ```
199
202
 
200
- ## Serving static files
203
+ ## Static files
201
204
 
202
- If you create a `./public` directory, JSON Server will serve its content in addition to the REST API.
205
+ JSON Server serves `./public` automatically.
203
206
 
204
- You can also add custom directories using `-s/--static` option.
207
+ Add more static dirs:
205
208
 
206
209
  ```sh
207
210
  json-server -s ./static
208
211
  json-server -s ./static -s ./node_modules
209
212
  ```
210
213
 
211
- ## Notable differences with v0.17
214
+ ## Behavior notes
212
215
 
213
216
  - `id` is always a string and will be generated for you if missing
214
217
  - use `_per_page` with `_page` instead of `_limit`for pagination
218
+ - use `_embed` instead of `_expand`
215
219
  - use Chrome's `Network tab > throtling` to delay requests instead of `--delay` CLI option
package/lib/app.js CHANGED
@@ -6,6 +6,7 @@ import { Eta } from 'eta';
6
6
  import { Low } from 'lowdb';
7
7
  import { json } from 'milliparsec';
8
8
  import sirv from 'sirv';
9
+ import { parseWhere } from "./parse-where.js";
9
10
  import { isItem, Service } from "./service.js";
10
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
11
12
  const isProduction = process.env['NODE_ENV'] === 'production';
@@ -13,6 +14,59 @@ const eta = new Eta({
13
14
  views: join(__dirname, '../views'),
14
15
  cache: isProduction,
15
16
  });
17
+ const RESERVED_QUERY_KEYS = new Set(['_sort', '_page', '_per_page', '_embed', '_where']);
18
+ function parseListParams(req) {
19
+ const queryString = req.url.split('?')[1] ?? '';
20
+ const params = new URLSearchParams(queryString);
21
+ const filterParams = new URLSearchParams();
22
+ for (const [key, value] of params.entries()) {
23
+ if (!RESERVED_QUERY_KEYS.has(key)) {
24
+ filterParams.append(key, value);
25
+ }
26
+ }
27
+ let where = parseWhere(filterParams.toString());
28
+ const rawWhere = params.get('_where');
29
+ if (typeof rawWhere === 'string') {
30
+ try {
31
+ const parsed = JSON.parse(rawWhere);
32
+ if (typeof parsed === 'object' && parsed !== null) {
33
+ where = parsed;
34
+ }
35
+ }
36
+ catch {
37
+ // Ignore invalid JSON and fallback to parsed query params
38
+ }
39
+ }
40
+ const pageRaw = params.get('_page');
41
+ const perPageRaw = params.get('_per_page');
42
+ const page = pageRaw === null ? undefined : Number.parseInt(pageRaw, 10);
43
+ const perPage = perPageRaw === null ? undefined : Number.parseInt(perPageRaw, 10);
44
+ return {
45
+ where,
46
+ sort: params.get('_sort') ?? undefined,
47
+ page: Number.isNaN(page) ? undefined : page,
48
+ perPage: Number.isNaN(perPage) ? undefined : perPage,
49
+ embed: req.query['_embed'],
50
+ };
51
+ }
52
+ function withBody(action) {
53
+ return async (req, res, next) => {
54
+ const { name = '' } = req.params;
55
+ if (isItem(req.body)) {
56
+ res.locals['data'] = await action(name, req.body);
57
+ }
58
+ next?.();
59
+ };
60
+ }
61
+ function withIdAndBody(action) {
62
+ return async (req, res, next) => {
63
+ const { name = '', id = '' } = req.params;
64
+ if (isItem(req.body)) {
65
+ res.locals['data'] = await action(name, id, req.body);
66
+ }
67
+ next?.();
68
+ };
69
+ }
16
70
  export function createApp(db, options = {}) {
17
71
  // Create service
18
72
  const service = new Service(db);
@@ -38,18 +92,14 @@ export function createApp(db, options = {}) {
38
92
  app.get('/', (_req, res) => res.send(eta.render('index.html', { data: db.data })));
39
93
  app.get('/:name', (req, res, next) => {
40
94
  const { name = '' } = req.params;
41
- const query = {};
42
- Object.keys(req.query).forEach((key) => {
43
- let value = req.query[key];
44
- if (['_start', '_end', '_limit', '_page', '_per_page'].includes(key) &&
45
- typeof value === 'string') {
46
- value = parseInt(value);
47
- }
48
- if (!Number.isNaN(value)) {
49
- query[key] = value;
50
- }
95
+ const { where, sort, page, perPage, embed } = parseListParams(req);
96
+ res.locals['data'] = service.find(name, {
97
+ where,
98
+ sort,
99
+ page,
100
+ perPage,
101
+ embed,
51
102
  });
52
- res.locals['data'] = service.find(name, query);
53
103
  next?.();
54
104
  });
55
105
  app.get('/:name/:id', (req, res, next) => {
@@ -57,41 +107,11 @@ export function createApp(db, options = {}) {
57
107
  res.locals['data'] = service.findById(name, id, req.query);
58
108
  next?.();
59
109
  });
60
- app.post('/:name', async (req, res, next) => {
61
- const { name = '' } = req.params;
62
- if (isItem(req.body)) {
63
- res.locals['data'] = await service.create(name, req.body);
64
- }
65
- next?.();
66
- });
67
- app.put('/:name', async (req, res, next) => {
68
- const { name = '' } = req.params;
69
- if (isItem(req.body)) {
70
- res.locals['data'] = await service.update(name, req.body);
71
- }
72
- next?.();
73
- });
74
- app.put('/:name/:id', async (req, res, next) => {
75
- const { name = '', id = '' } = req.params;
76
- if (isItem(req.body)) {
77
- res.locals['data'] = await service.updateById(name, id, req.body);
78
- }
79
- next?.();
80
- });
81
- app.patch('/:name', async (req, res, next) => {
82
- const { name = '' } = req.params;
83
- if (isItem(req.body)) {
84
- res.locals['data'] = await service.patch(name, req.body);
85
- }
86
- next?.();
87
- });
88
- app.patch('/:name/:id', async (req, res, next) => {
89
- const { name = '', id = '' } = req.params;
90
- if (isItem(req.body)) {
91
- res.locals['data'] = await service.patchById(name, id, req.body);
92
- }
93
- next?.();
94
- });
110
+ app.post('/:name', withBody(service.create.bind(service)));
111
+ app.put('/:name', withBody(service.update.bind(service)));
112
+ app.put('/:name/:id', withIdAndBody(service.updateById.bind(service)));
113
+ app.patch('/:name', withBody(service.patch.bind(service)));
114
+ app.patch('/:name/:id', withIdAndBody(service.patchById.bind(service)));
95
115
  app.delete('/:name/:id', async (req, res, next) => {
96
116
  const { name = '', id = '' } = req.params;
97
117
  res.locals['data'] = await service.destroyById(name, id, req.query['_dependent']);
@@ -0,0 +1,64 @@
1
+ import { WHERE_OPERATORS } from "./where-operators.js";
2
+ function isJSONObject(value) {
3
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
4
+ }
5
+ function getKnownOperators(value) {
6
+ if (!isJSONObject(value))
7
+ return [];
8
+ const ops = [];
9
+ for (const op of WHERE_OPERATORS) {
10
+ if (op in value) {
11
+ ops.push(op);
12
+ }
13
+ }
14
+ return ops;
15
+ }
16
+ export function matchesWhere(obj, where) {
17
+ for (const [key, value] of Object.entries(where)) {
18
+ if (key === 'or') {
19
+ if (!Array.isArray(value) || value.length === 0)
20
+ return false;
21
+ let matched = false;
22
+ for (const subWhere of value) {
23
+ if (isJSONObject(subWhere) && matchesWhere(obj, subWhere)) {
24
+ matched = true;
25
+ break;
26
+ }
27
+ }
28
+ if (!matched)
29
+ return false;
30
+ continue;
31
+ }
32
+ const field = obj[key];
33
+ if (isJSONObject(value)) {
34
+ const knownOps = getKnownOperators(value);
35
+ if (knownOps.length > 0) {
36
+ if (field === undefined)
37
+ return false;
38
+ const op = value;
39
+ if (knownOps.includes('lt') && !(field < op.lt))
40
+ return false;
41
+ if (knownOps.includes('lte') && !(field <= op.lte))
42
+ return false;
43
+ if (knownOps.includes('gt') && !(field > op.gt))
44
+ return false;
45
+ if (knownOps.includes('gte') && !(field >= op.gte))
46
+ return false;
47
+ if (knownOps.includes('eq') && !(field === op.eq))
48
+ return false;
49
+ if (knownOps.includes('ne') && !(field !== op.ne))
50
+ return false;
51
+ continue;
52
+ }
53
+ if (isJSONObject(field)) {
54
+ if (!matchesWhere(field, value))
55
+ return false;
56
+ }
57
+ continue;
58
+ }
59
+ if (field === undefined)
60
+ return false;
61
+ return false;
62
+ }
63
+ return true;
64
+ }
@@ -0,0 +1,24 @@
1
+ export function paginate(items, page, perPage) {
2
+ const totalItems = items.length;
3
+ const safePerPage = Number.isFinite(perPage) && perPage > 0 ? Math.floor(perPage) : 1;
4
+ const pages = Math.max(1, Math.ceil(totalItems / safePerPage));
5
+ // Ensure page is within the valid range
6
+ const safePage = Number.isFinite(page) ? Math.floor(page) : 1;
7
+ const currentPage = Math.max(1, Math.min(safePage, pages));
8
+ const first = 1;
9
+ const prev = currentPage > 1 ? currentPage - 1 : null;
10
+ const next = currentPage < pages ? currentPage + 1 : null;
11
+ const last = pages;
12
+ const start = (currentPage - 1) * safePerPage;
13
+ const end = start + safePerPage;
14
+ const data = items.slice(start, end);
15
+ return {
16
+ first,
17
+ prev,
18
+ next,
19
+ last,
20
+ pages,
21
+ items: totalItems,
22
+ data,
23
+ };
24
+ }
@@ -0,0 +1,52 @@
1
+ import { setProperty } from 'dot-prop';
2
+ import { isWhereOperator } from "./where-operators.js";
3
+ function splitKey(key) {
4
+ const colonIdx = key.lastIndexOf(':');
5
+ if (colonIdx !== -1) {
6
+ const path = key.slice(0, colonIdx);
7
+ const op = key.slice(colonIdx + 1);
8
+ if (!op) {
9
+ return { path: key, op: 'eq' };
10
+ }
11
+ return isWhereOperator(op) ? { path, op } : { path, op: null };
12
+ }
13
+ // Compatibility with v0.17 operator style (e.g. _lt, _gt)
14
+ const underscoreMatch = key.match(/^(.*)_([a-z]+)$/);
15
+ if (underscoreMatch) {
16
+ const path = underscoreMatch[1];
17
+ const op = underscoreMatch[2];
18
+ if (path && isWhereOperator(op)) {
19
+ return { path, op };
20
+ }
21
+ }
22
+ return { path: key, op: 'eq' };
23
+ }
24
+ function setPathOp(root, path, op, value) {
25
+ const fullPath = `${path}.${op}`;
26
+ setProperty(root, fullPath, coerceValue(value));
27
+ }
28
+ function coerceValue(value) {
29
+ if (value === 'true')
30
+ return true;
31
+ if (value === 'false')
32
+ return false;
33
+ if (value === 'null')
34
+ return null;
35
+ if (value.trim() === '')
36
+ return value;
37
+ const num = Number(value);
38
+ if (Number.isFinite(num))
39
+ return num;
40
+ return value;
41
+ }
42
+ export function parseWhere(query) {
43
+ const out = {};
44
+ const params = new URLSearchParams(query);
45
+ for (const [rawKey, rawValue] of params.entries()) {
46
+ const { path, op } = splitKey(rawKey);
47
+ if (op === null)
48
+ continue;
49
+ setPathOp(out, path, op, rawValue);
50
+ }
51
+ return out;
52
+ }
package/lib/service.js CHANGED
@@ -1,28 +1,18 @@
1
1
  import { randomBytes } from 'node:crypto';
2
- import { getProperty } from 'dot-prop';
3
2
  import inflection from 'inflection';
4
3
  import { Low } from 'lowdb';
5
4
  import sortOn from 'sort-on';
5
+ import { matchesWhere } from "./matches-where.js";
6
+ import { paginate } from "./paginate.js";
6
7
  export function isItem(obj) {
7
- return typeof obj === 'object' && obj !== null;
8
+ return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
8
9
  }
9
10
  export function isData(obj) {
10
11
  if (typeof obj !== 'object' || obj === null) {
11
12
  return false;
12
13
  }
13
14
  const data = obj;
14
- return Object.values(data).every((value) => Array.isArray(value) && value.every(isItem));
15
- }
16
- const Condition = {
17
- lt: 'lt',
18
- lte: 'lte',
19
- gt: 'gt',
20
- gte: 'gte',
21
- ne: 'ne',
22
- default: '',
23
- };
24
- function isCondition(value) {
25
- return Object.values(Condition).includes(value);
15
+ return Object.values(data).every((value) => Array.isArray(value) ? value.every(isItem) : isItem(value));
26
16
  }
27
17
  function ensureArray(arg = []) {
28
18
  return Array.isArray(arg) ? arg : [arg];
@@ -120,148 +110,24 @@ export class Service {
120
110
  }
121
111
  return;
122
112
  }
123
- find(name, query = {}) {
124
- let items = this.#get(name);
113
+ find(name, opts) {
114
+ const items = this.#get(name);
125
115
  if (!Array.isArray(items)) {
126
116
  return items;
127
117
  }
118
+ let results = items;
128
119
  // Include
129
- ensureArray(query._embed).forEach((related) => {
130
- if (items !== undefined && Array.isArray(items)) {
131
- items = items.map((item) => embed(this.#db, name, item, related));
132
- }
120
+ ensureArray(opts.embed).forEach((related) => {
121
+ results = results.map((item) => embed(this.#db, name, item, related));
133
122
  });
134
- // Return list if no query params
135
- if (Object.keys(query).length === 0) {
136
- return items;
137
- }
138
- // Convert query params to conditions
139
- const conds = [];
140
- for (const [key, value] of Object.entries(query)) {
141
- if (value === undefined || typeof value !== 'string') {
142
- continue;
143
- }
144
- const re = /_(lt|lte|gt|gte|ne)$/;
145
- const reArr = re.exec(key);
146
- const op = reArr?.at(1);
147
- if (op && isCondition(op)) {
148
- const field = key.replace(re, '');
149
- conds.push([field, op, value]);
150
- continue;
151
- }
152
- if (['_embed', '_sort', '_start', '_end', '_limit', '_page', '_per_page'].includes(key)) {
153
- continue;
154
- }
155
- conds.push([key, Condition.default, value]);
156
- }
157
- // Loop through conditions and filter items
158
- let filtered = items;
159
- for (const [key, op, paramValue] of conds) {
160
- filtered = filtered.filter((item) => {
161
- if (paramValue && !Array.isArray(paramValue)) {
162
- // https://github.com/sindresorhus/dot-prop/issues/95
163
- const itemValue = getProperty(item, key);
164
- switch (op) {
165
- // item_gt=value
166
- case Condition.gt: {
167
- if (!(typeof itemValue === 'number' && itemValue > parseInt(paramValue))) {
168
- return false;
169
- }
170
- break;
171
- }
172
- // item_gte=value
173
- case Condition.gte: {
174
- if (!(typeof itemValue === 'number' && itemValue >= parseInt(paramValue))) {
175
- return false;
176
- }
177
- break;
178
- }
179
- // item_lt=value
180
- case Condition.lt: {
181
- if (!(typeof itemValue === 'number' && itemValue < parseInt(paramValue))) {
182
- return false;
183
- }
184
- break;
185
- }
186
- // item_lte=value
187
- case Condition.lte: {
188
- if (!(typeof itemValue === 'number' && itemValue <= parseInt(paramValue))) {
189
- return false;
190
- }
191
- break;
192
- }
193
- // item_ne=value
194
- case Condition.ne: {
195
- switch (typeof itemValue) {
196
- case 'number':
197
- return itemValue !== parseInt(paramValue);
198
- case 'string':
199
- return itemValue !== paramValue;
200
- case 'boolean':
201
- return itemValue !== (paramValue === 'true');
202
- }
203
- break;
204
- }
205
- // item=value
206
- case Condition.default: {
207
- switch (typeof itemValue) {
208
- case 'number':
209
- return itemValue === parseInt(paramValue);
210
- case 'string':
211
- return itemValue === paramValue;
212
- case 'boolean':
213
- return itemValue === (paramValue === 'true');
214
- case 'undefined':
215
- return false;
216
- }
217
- }
218
- }
219
- }
220
- return true;
221
- });
222
- }
223
- // Sort
224
- const sort = query._sort || '';
225
- const sorted = sortOn(filtered, sort.split(','));
226
- // Slice
227
- const start = query._start;
228
- const end = query._end;
229
- const limit = query._limit;
230
- if (start !== undefined) {
231
- if (end !== undefined) {
232
- return sorted.slice(start, end);
233
- }
234
- return sorted.slice(start, start + (limit || 0));
235
- }
236
- if (limit !== undefined) {
237
- return sorted.slice(0, limit);
123
+ results = results.filter((item) => matchesWhere(item, opts.where));
124
+ if (opts.sort) {
125
+ results = sortOn(results, opts.sort.split(','));
238
126
  }
239
- // Paginate
240
- let page = query._page;
241
- const perPage = query._per_page || 10;
242
- if (page) {
243
- const items = sorted.length;
244
- const pages = Math.ceil(items / perPage);
245
- // Ensure page is within the valid range
246
- page = Math.max(1, Math.min(page, pages));
247
- const first = 1;
248
- const prev = page > 1 ? page - 1 : null;
249
- const next = page < pages ? page + 1 : null;
250
- const last = pages;
251
- const start = (page - 1) * perPage;
252
- const end = start + perPage;
253
- const data = sorted.slice(start, end);
254
- return {
255
- first,
256
- prev,
257
- next,
258
- last,
259
- pages,
260
- items,
261
- data,
262
- };
127
+ if (opts.page !== undefined) {
128
+ return paginate(results, opts.page, opts.perPage ?? 10);
263
129
  }
264
- return sorted.slice(start, end);
130
+ return results;
265
131
  }
266
132
  async create(name, data = {}) {
267
133
  const items = this.#get(name);
@@ -0,0 +1,4 @@
1
+ export const WHERE_OPERATORS = ['lt', 'lte', 'gt', 'gte', 'eq', 'ne'];
2
+ export function isWhereOperator(value) {
3
+ return WHERE_OPERATORS.includes(value);
4
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-server",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0-beta.6",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "license": "SEE LICENSE IN ./LICENSE",
@@ -14,7 +14,8 @@
14
14
  },
15
15
  "files": [
16
16
  "lib",
17
- "views"
17
+ "views",
18
+ "schema.json"
18
19
  ],
19
20
  "type": "module",
20
21
  "dependencies": {
package/schema.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "additionalProperties": {
5
+ "oneOf": [
6
+ { "type": "array" },
7
+ { "type": "object" }
8
+ ]
9
+ }
10
+ }