json-server 1.0.0-beta.1 → 1.0.0-beta.10
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/LICENSE +21 -44
- package/README.md +140 -88
- package/lib/app.js +81 -51
- package/lib/bin.js +52 -49
- package/lib/matches-where.js +87 -0
- package/lib/paginate.js +24 -0
- package/lib/parse-where.js +56 -0
- package/lib/service.js +18 -161
- package/lib/where-operators.js +15 -0
- package/package.json +58 -44
- package/schema.json +10 -0
- package/views/index.html +73 -75
- package/lib/app.d.ts +0 -8
- package/lib/bin.d.ts +0 -2
- package/lib/observer.d.ts +0 -11
- package/lib/service.d.ts +0 -38
package/lib/bin.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync } from
|
|
3
|
-
import { extname } from
|
|
4
|
-
import { parseArgs } from
|
|
5
|
-
import chalk from
|
|
6
|
-
import { watch } from
|
|
7
|
-
import JSON5 from
|
|
8
|
-
import { Low } from
|
|
9
|
-
import { DataFile, JSONFile } from
|
|
10
|
-
import { fileURLToPath } from
|
|
11
|
-
import { createApp } from
|
|
12
|
-
import { Observer } from
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { extname } from "node:path";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { watch } from "chokidar";
|
|
7
|
+
import JSON5 from "json5";
|
|
8
|
+
import { Low } from "lowdb";
|
|
9
|
+
import { DataFile, JSONFile } from "lowdb/node";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { createApp } from "./app.js";
|
|
12
|
+
import { Observer } from "./observer.js";
|
|
13
13
|
function help() {
|
|
14
14
|
console.log(`Usage: json-server [options] <file>
|
|
15
15
|
|
|
@@ -27,44 +27,44 @@ function args() {
|
|
|
27
27
|
const { values, positionals } = parseArgs({
|
|
28
28
|
options: {
|
|
29
29
|
port: {
|
|
30
|
-
type:
|
|
31
|
-
short:
|
|
32
|
-
default: process.env[
|
|
30
|
+
type: "string",
|
|
31
|
+
short: "p",
|
|
32
|
+
default: process.env["PORT"] ?? "3000",
|
|
33
33
|
},
|
|
34
34
|
host: {
|
|
35
|
-
type:
|
|
36
|
-
short:
|
|
37
|
-
default: process.env[
|
|
35
|
+
type: "string",
|
|
36
|
+
short: "h",
|
|
37
|
+
default: process.env["HOST"] ?? "localhost",
|
|
38
38
|
},
|
|
39
39
|
static: {
|
|
40
|
-
type:
|
|
41
|
-
short:
|
|
40
|
+
type: "string",
|
|
41
|
+
short: "s",
|
|
42
42
|
multiple: true,
|
|
43
43
|
default: [],
|
|
44
44
|
},
|
|
45
45
|
help: {
|
|
46
|
-
type:
|
|
46
|
+
type: "boolean",
|
|
47
47
|
},
|
|
48
48
|
version: {
|
|
49
|
-
type:
|
|
49
|
+
type: "boolean",
|
|
50
50
|
},
|
|
51
51
|
// Deprecated
|
|
52
52
|
watch: {
|
|
53
|
-
type:
|
|
54
|
-
short:
|
|
53
|
+
type: "boolean",
|
|
54
|
+
short: "w",
|
|
55
55
|
},
|
|
56
56
|
},
|
|
57
57
|
allowPositionals: true,
|
|
58
58
|
});
|
|
59
59
|
// --version
|
|
60
60
|
if (values.version) {
|
|
61
|
-
const pkg = JSON.parse(readFileSync(fileURLToPath(new URL(
|
|
61
|
+
const pkg = JSON.parse(readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf-8"));
|
|
62
62
|
console.log(pkg.version);
|
|
63
63
|
process.exit();
|
|
64
64
|
}
|
|
65
65
|
// Handle --watch
|
|
66
66
|
if (values.watch) {
|
|
67
|
-
console.log(chalk.yellow(
|
|
67
|
+
console.log(chalk.yellow("--watch/-w can be omitted, JSON Server 1+ watches for file changes by default"));
|
|
68
68
|
}
|
|
69
69
|
if (values.help || positionals.length === 0) {
|
|
70
70
|
help();
|
|
@@ -72,15 +72,15 @@ function args() {
|
|
|
72
72
|
}
|
|
73
73
|
// App args and options
|
|
74
74
|
return {
|
|
75
|
-
file: positionals[0] ??
|
|
75
|
+
file: positionals[0] ?? "",
|
|
76
76
|
port: parseInt(values.port),
|
|
77
77
|
host: values.host,
|
|
78
78
|
static: values.static,
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
81
|
catch (e) {
|
|
82
|
-
if (e.code ===
|
|
83
|
-
console.log(chalk.red(e.message.split(
|
|
82
|
+
if (e.code === "ERR_PARSE_ARGS_UNKNOWN_OPTION") {
|
|
83
|
+
console.log(chalk.red(e.message.split(".")[0]));
|
|
84
84
|
help();
|
|
85
85
|
process.exit(1);
|
|
86
86
|
}
|
|
@@ -95,12 +95,12 @@ if (!existsSync(file)) {
|
|
|
95
95
|
process.exit(1);
|
|
96
96
|
}
|
|
97
97
|
// Handle empty string JSON file
|
|
98
|
-
if (readFileSync(file,
|
|
99
|
-
writeFileSync(file,
|
|
98
|
+
if (readFileSync(file, "utf-8").trim() === "") {
|
|
99
|
+
writeFileSync(file, "{}");
|
|
100
100
|
}
|
|
101
101
|
// Set up database
|
|
102
102
|
let adapter;
|
|
103
|
-
if (extname(file) ===
|
|
103
|
+
if (extname(file) === ".json5") {
|
|
104
104
|
adapter = new DataFile(file, {
|
|
105
105
|
parse: JSON5.parse,
|
|
106
106
|
stringify: JSON5.stringify,
|
|
@@ -115,41 +115,42 @@ await db.read();
|
|
|
115
115
|
// Create app
|
|
116
116
|
const app = createApp(db, { logger: false, static: staticArr });
|
|
117
117
|
function logRoutes(data) {
|
|
118
|
-
console.log(chalk.bold(
|
|
118
|
+
console.log(chalk.bold("Endpoints:"));
|
|
119
119
|
if (Object.keys(data).length === 0) {
|
|
120
120
|
console.log(chalk.gray(`No endpoints found, try adding some data to ${file}`));
|
|
121
121
|
return;
|
|
122
122
|
}
|
|
123
123
|
console.log(Object.keys(data)
|
|
124
124
|
.map((key) => `${chalk.gray(`http://${host}:${port}/`)}${chalk.blue(key)}`)
|
|
125
|
-
.join(
|
|
125
|
+
.join("\n"));
|
|
126
126
|
}
|
|
127
|
-
const kaomojis = [
|
|
127
|
+
const kaomojis = ["♡⸜(˶˃ ᵕ ˂˶)⸝♡", "♡( ◡‿◡ )", "( ˶ˆ ᗜ ˆ˵ )", "(˶ᵔ ᵕ ᵔ˶)"];
|
|
128
128
|
function randomItem(items) {
|
|
129
129
|
const index = Math.floor(Math.random() * items.length);
|
|
130
|
-
return items.at(index) ??
|
|
130
|
+
return items.at(index) ?? "";
|
|
131
131
|
}
|
|
132
132
|
app.listen(port, () => {
|
|
133
133
|
console.log([
|
|
134
134
|
chalk.bold(`JSON Server started on PORT :${port}`),
|
|
135
|
-
chalk.gray(
|
|
135
|
+
chalk.gray("Press CTRL-C to stop"),
|
|
136
136
|
chalk.gray(`Watching ${file}...`),
|
|
137
|
-
|
|
137
|
+
"",
|
|
138
138
|
chalk.magenta(randomItem(kaomojis)),
|
|
139
|
-
|
|
140
|
-
chalk.bold(
|
|
139
|
+
"",
|
|
140
|
+
chalk.bold("Index:"),
|
|
141
141
|
chalk.gray(`http://localhost:${port}/`),
|
|
142
|
-
|
|
143
|
-
chalk.bold(
|
|
144
|
-
chalk.gray(
|
|
145
|
-
|
|
146
|
-
].join(
|
|
142
|
+
"",
|
|
143
|
+
chalk.bold("Static files:"),
|
|
144
|
+
chalk.gray("Serving ./public directory if it exists"),
|
|
145
|
+
"",
|
|
146
|
+
].join("\n"));
|
|
147
147
|
logRoutes(db.data);
|
|
148
148
|
});
|
|
149
149
|
// Watch file for changes
|
|
150
|
-
if (process.env[
|
|
150
|
+
if (process.env["NODE_ENV"] !== "production") {
|
|
151
151
|
let writing = false; // true if the file is being written to by the app
|
|
152
|
-
let
|
|
152
|
+
let hadReadError = false;
|
|
153
|
+
let prevEndpoints = "";
|
|
153
154
|
observer.onWriteStart = () => {
|
|
154
155
|
writing = true;
|
|
155
156
|
};
|
|
@@ -164,17 +165,19 @@ if (process.env['NODE_ENV'] !== 'production') {
|
|
|
164
165
|
return;
|
|
165
166
|
}
|
|
166
167
|
const nextEndpoints = JSON.stringify(Object.keys(data).sort());
|
|
167
|
-
if (prevEndpoints !== nextEndpoints) {
|
|
168
|
+
if (hadReadError || prevEndpoints !== nextEndpoints) {
|
|
168
169
|
console.log();
|
|
169
170
|
logRoutes(data);
|
|
170
171
|
}
|
|
172
|
+
hadReadError = false;
|
|
171
173
|
};
|
|
172
|
-
watch(file).on(
|
|
174
|
+
watch(file).on("change", () => {
|
|
173
175
|
// Do no reload if the file is being written to by the app
|
|
174
176
|
if (!writing) {
|
|
175
177
|
db.read().catch((e) => {
|
|
176
178
|
if (e instanceof SyntaxError) {
|
|
177
|
-
|
|
179
|
+
hadReadError = true;
|
|
180
|
+
return console.log(chalk.red(["", `Error parsing ${file}`, e.message].join("\n")));
|
|
178
181
|
}
|
|
179
182
|
console.log(e);
|
|
180
183
|
});
|
|
@@ -0,0 +1,87 @@
|
|
|
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
|
+
if (knownOps.includes('in')) {
|
|
52
|
+
const inValues = Array.isArray(op.in) ? op.in : [op.in];
|
|
53
|
+
if (!inValues.some((v) => field === v))
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
if (knownOps.includes('contains')) {
|
|
57
|
+
if (typeof field !== 'string')
|
|
58
|
+
return false;
|
|
59
|
+
if (!field.toLowerCase().includes(String(op.contains).toLowerCase()))
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
if (knownOps.includes('startsWith')) {
|
|
63
|
+
if (typeof field !== 'string')
|
|
64
|
+
return false;
|
|
65
|
+
if (!field.toLowerCase().startsWith(String(op.startsWith).toLowerCase()))
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
if (knownOps.includes('endsWith')) {
|
|
69
|
+
if (typeof field !== 'string')
|
|
70
|
+
return false;
|
|
71
|
+
if (!field.toLowerCase().endsWith(String(op.endsWith).toLowerCase()))
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (isJSONObject(field)) {
|
|
77
|
+
if (!matchesWhere(field, value))
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (field === undefined)
|
|
83
|
+
return false;
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
}
|
package/lib/paginate.js
ADDED
|
@@ -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,56 @@
|
|
|
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
|
+
if (op === 'in') {
|
|
27
|
+
setProperty(root, fullPath, value.split(',').map((part) => coerceValue(part.trim())));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
setProperty(root, fullPath, coerceValue(value));
|
|
31
|
+
}
|
|
32
|
+
function coerceValue(value) {
|
|
33
|
+
if (value === 'true')
|
|
34
|
+
return true;
|
|
35
|
+
if (value === 'false')
|
|
36
|
+
return false;
|
|
37
|
+
if (value === 'null')
|
|
38
|
+
return null;
|
|
39
|
+
if (value.trim() === '')
|
|
40
|
+
return value;
|
|
41
|
+
const num = Number(value);
|
|
42
|
+
if (Number.isFinite(num))
|
|
43
|
+
return num;
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
export function parseWhere(query) {
|
|
47
|
+
const out = {};
|
|
48
|
+
const params = new URLSearchParams(query);
|
|
49
|
+
for (const [rawKey, rawValue] of params.entries()) {
|
|
50
|
+
const { path, op } = splitKey(rawKey);
|
|
51
|
+
if (op === null)
|
|
52
|
+
continue;
|
|
53
|
+
setPathOp(out, path, op, rawValue);
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
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';
|
|
3
|
+
import { Low } from 'lowdb';
|
|
4
4
|
import sortOn from 'sort-on';
|
|
5
|
+
import { matchesWhere } from "./matches-where.js";
|
|
6
|
+
import { paginate } from "./paginate.js";
|
|
5
7
|
export function isItem(obj) {
|
|
6
|
-
return typeof obj === 'object' && obj !== null;
|
|
8
|
+
return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
|
|
7
9
|
}
|
|
8
10
|
export function isData(obj) {
|
|
9
11
|
if (typeof obj !== 'object' || obj === null) {
|
|
10
12
|
return false;
|
|
11
13
|
}
|
|
12
14
|
const data = obj;
|
|
13
|
-
return Object.values(data).every((value) => Array.isArray(value)
|
|
14
|
-
}
|
|
15
|
-
var Condition;
|
|
16
|
-
(function (Condition) {
|
|
17
|
-
Condition["lt"] = "lt";
|
|
18
|
-
Condition["lte"] = "lte";
|
|
19
|
-
Condition["gt"] = "gt";
|
|
20
|
-
Condition["gte"] = "gte";
|
|
21
|
-
Condition["ne"] = "ne";
|
|
22
|
-
Condition["default"] = "";
|
|
23
|
-
})(Condition || (Condition = {}));
|
|
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,157 +110,24 @@ export class Service {
|
|
|
120
110
|
}
|
|
121
111
|
return;
|
|
122
112
|
}
|
|
123
|
-
find(name,
|
|
124
|
-
|
|
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(
|
|
130
|
-
|
|
131
|
-
items = items.map((item) => embed(this.#db, name, item, related));
|
|
132
|
-
}
|
|
133
|
-
});
|
|
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[field] = [op, value];
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
if ([
|
|
153
|
-
'_embed',
|
|
154
|
-
'_sort',
|
|
155
|
-
'_start',
|
|
156
|
-
'_end',
|
|
157
|
-
'_limit',
|
|
158
|
-
'_page',
|
|
159
|
-
'_per_page',
|
|
160
|
-
].includes(key)) {
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
conds[key] = [Condition.default, value];
|
|
164
|
-
}
|
|
165
|
-
// Loop through conditions and filter items
|
|
166
|
-
const res = items.filter((item) => {
|
|
167
|
-
for (const [key, [op, paramValue]] of Object.entries(conds)) {
|
|
168
|
-
if (paramValue && !Array.isArray(paramValue)) {
|
|
169
|
-
// https://github.com/sindresorhus/dot-prop/issues/95
|
|
170
|
-
const itemValue = getProperty(item, key);
|
|
171
|
-
switch (op) {
|
|
172
|
-
// item_gt=value
|
|
173
|
-
case Condition.gt: {
|
|
174
|
-
if (!(typeof itemValue === 'number' &&
|
|
175
|
-
itemValue > parseInt(paramValue))) {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
break;
|
|
179
|
-
}
|
|
180
|
-
// item_gte=value
|
|
181
|
-
case Condition.gte: {
|
|
182
|
-
if (!(typeof itemValue === 'number' &&
|
|
183
|
-
itemValue >= parseInt(paramValue))) {
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
|
-
// item_lt=value
|
|
189
|
-
case Condition.lt: {
|
|
190
|
-
if (!(typeof itemValue === 'number' &&
|
|
191
|
-
itemValue < parseInt(paramValue))) {
|
|
192
|
-
return false;
|
|
193
|
-
}
|
|
194
|
-
break;
|
|
195
|
-
}
|
|
196
|
-
// item_lte=value
|
|
197
|
-
case Condition.lte: {
|
|
198
|
-
if (!(typeof itemValue === 'number' &&
|
|
199
|
-
itemValue <= parseInt(paramValue))) {
|
|
200
|
-
return false;
|
|
201
|
-
}
|
|
202
|
-
break;
|
|
203
|
-
}
|
|
204
|
-
// item_ne=value
|
|
205
|
-
case Condition.ne: {
|
|
206
|
-
switch (typeof itemValue) {
|
|
207
|
-
case 'number':
|
|
208
|
-
return itemValue !== parseInt(paramValue);
|
|
209
|
-
case 'string':
|
|
210
|
-
return itemValue !== paramValue;
|
|
211
|
-
case 'boolean':
|
|
212
|
-
return itemValue !== (paramValue === 'true');
|
|
213
|
-
}
|
|
214
|
-
break;
|
|
215
|
-
}
|
|
216
|
-
// item=value
|
|
217
|
-
case Condition.default: {
|
|
218
|
-
switch (typeof itemValue) {
|
|
219
|
-
case 'number':
|
|
220
|
-
return itemValue === parseInt(paramValue);
|
|
221
|
-
case 'string':
|
|
222
|
-
return itemValue === paramValue;
|
|
223
|
-
case 'boolean':
|
|
224
|
-
return itemValue === (paramValue === 'true');
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
return true;
|
|
120
|
+
ensureArray(opts.embed).forEach((related) => {
|
|
121
|
+
results = results.map((item) => embed(this.#db, name, item, related));
|
|
231
122
|
});
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
// Slice
|
|
236
|
-
const start = query._start;
|
|
237
|
-
const end = query._end;
|
|
238
|
-
const limit = query._limit;
|
|
239
|
-
if (start !== undefined) {
|
|
240
|
-
if (end !== undefined) {
|
|
241
|
-
return sorted.slice(start, end);
|
|
242
|
-
}
|
|
243
|
-
return sorted.slice(start, start + (limit || 0));
|
|
244
|
-
}
|
|
245
|
-
if (limit !== undefined) {
|
|
246
|
-
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(','));
|
|
247
126
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const perPage = query._per_page || 10;
|
|
251
|
-
if (page) {
|
|
252
|
-
const items = sorted.length;
|
|
253
|
-
const pages = Math.ceil(items / perPage);
|
|
254
|
-
// Ensure page is within the valid range
|
|
255
|
-
page = Math.max(1, Math.min(page, pages));
|
|
256
|
-
const first = 1;
|
|
257
|
-
const prev = page > 1 ? page - 1 : null;
|
|
258
|
-
const next = page < pages ? page + 1 : null;
|
|
259
|
-
const last = pages;
|
|
260
|
-
const start = (page - 1) * perPage;
|
|
261
|
-
const end = start + perPage;
|
|
262
|
-
const data = sorted.slice(start, end);
|
|
263
|
-
return {
|
|
264
|
-
first,
|
|
265
|
-
prev,
|
|
266
|
-
next,
|
|
267
|
-
last,
|
|
268
|
-
pages,
|
|
269
|
-
items,
|
|
270
|
-
data,
|
|
271
|
-
};
|
|
127
|
+
if (opts.page !== undefined) {
|
|
128
|
+
return paginate(results, opts.page, opts.perPage ?? 10);
|
|
272
129
|
}
|
|
273
|
-
return
|
|
130
|
+
return results;
|
|
274
131
|
}
|
|
275
132
|
async create(name, data = {}) {
|
|
276
133
|
const items = this.#get(name);
|
|
@@ -285,7 +142,7 @@ export class Service {
|
|
|
285
142
|
const item = this.#get(name);
|
|
286
143
|
if (item === undefined || Array.isArray(item))
|
|
287
144
|
return;
|
|
288
|
-
const nextItem = (this.#db.data[name] = isPatch ? { item, ...body } : body);
|
|
145
|
+
const nextItem = (this.#db.data[name] = isPatch ? { ...item, ...body } : body);
|
|
289
146
|
await this.#db.write();
|
|
290
147
|
return nextItem;
|
|
291
148
|
}
|
|
@@ -322,7 +179,7 @@ export class Service {
|
|
|
322
179
|
if (item === undefined)
|
|
323
180
|
return;
|
|
324
181
|
const index = items.indexOf(item);
|
|
325
|
-
items.splice(index, 1)
|
|
182
|
+
items.splice(index, 1);
|
|
326
183
|
nullifyForeignKey(this.#db, name, id);
|
|
327
184
|
const dependents = ensureArray(dependent);
|
|
328
185
|
deleteDependents(this.#db, name, dependents);
|