@yrest/cli 0.5.3 → 0.7.0
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 -0
- package/README.md +255 -0
- package/dist/cli/index.js +133 -17
- package/dist/cli/index.mjs +133 -17
- package/dist/index.d.mts +205 -30
- package/dist/index.d.ts +205 -30
- package/dist/index.js +363 -33
- package/dist/index.mjs +378 -41
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// node_modules/tsup/assets/esm_shims.js
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
var init_esm_shims = __esm({
|
|
15
|
+
"node_modules/tsup/assets/esm_shims.js"() {
|
|
16
|
+
"use strict";
|
|
17
|
+
}
|
|
18
|
+
});
|
|
6
19
|
|
|
7
20
|
// src/utils/deepCopy.ts
|
|
8
21
|
function deepCopyData(source) {
|
|
@@ -10,11 +23,25 @@ function deepCopyData(source) {
|
|
|
10
23
|
Object.entries(source).map(([k, v]) => [k, v.map((item) => ({ ...item }))])
|
|
11
24
|
);
|
|
12
25
|
}
|
|
26
|
+
var init_deepCopy = __esm({
|
|
27
|
+
"src/utils/deepCopy.ts"() {
|
|
28
|
+
"use strict";
|
|
29
|
+
init_esm_shims();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
13
32
|
|
|
14
|
-
// src/storage/
|
|
15
|
-
|
|
33
|
+
// src/storage/yrestStorage.ts
|
|
34
|
+
var yrestStorage_exports = {};
|
|
35
|
+
__export(yrestStorage_exports, {
|
|
36
|
+
createYrestStorage: () => createYrestStorage
|
|
37
|
+
});
|
|
38
|
+
import { readFileSync, writeFileSync, renameSync } from "fs";
|
|
39
|
+
import { resolve, dirname } from "path";
|
|
40
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
41
|
+
import { parse as parse2, stringify } from "yaml";
|
|
42
|
+
function createYrestStorage(filePath) {
|
|
16
43
|
const absPath = resolve(filePath);
|
|
17
|
-
const raw =
|
|
44
|
+
const raw = parse2(readFileSync(absPath, "utf8")) ?? {};
|
|
18
45
|
const relations = raw["_rel"] ?? {};
|
|
19
46
|
const routes = Array.isArray(raw["_routes"]) ? raw["_routes"] : [];
|
|
20
47
|
const data = Object.fromEntries(
|
|
@@ -46,12 +73,12 @@ function createYamlStorage(filePath) {
|
|
|
46
73
|
if (Object.keys(relations).length > 0) payload._rel = relations;
|
|
47
74
|
if (routes.length > 0) payload._routes = routes;
|
|
48
75
|
Object.assign(payload, data);
|
|
49
|
-
const tmp = resolve(dirname(absPath), `.yrest-${
|
|
76
|
+
const tmp = resolve(dirname(absPath), `.yrest-${randomUUID2()}.tmp`);
|
|
50
77
|
writeFileSync(tmp, stringify(payload), "utf8");
|
|
51
78
|
renameSync(tmp, absPath);
|
|
52
79
|
},
|
|
53
80
|
reload() {
|
|
54
|
-
const fresh =
|
|
81
|
+
const fresh = parse2(readFileSync(absPath, "utf8")) ?? {};
|
|
55
82
|
const freshRelations = fresh["_rel"] ?? {};
|
|
56
83
|
const freshData = Object.fromEntries(
|
|
57
84
|
Object.entries(fresh).filter(([key]) => key !== "_rel" && key !== "_routes")
|
|
@@ -81,31 +108,126 @@ function createYamlStorage(filePath) {
|
|
|
81
108
|
}
|
|
82
109
|
};
|
|
83
110
|
}
|
|
111
|
+
var init_yrestStorage = __esm({
|
|
112
|
+
"src/storage/yrestStorage.ts"() {
|
|
113
|
+
"use strict";
|
|
114
|
+
init_esm_shims();
|
|
115
|
+
init_deepCopy();
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// src/index.ts
|
|
120
|
+
init_esm_shims();
|
|
121
|
+
|
|
122
|
+
// src/api/yrest.ts
|
|
123
|
+
init_esm_shims();
|
|
124
|
+
import { parse } from "yaml";
|
|
125
|
+
function yrest(strings, ...values) {
|
|
126
|
+
const raw = strings.reduce(
|
|
127
|
+
(acc, str, i) => acc + str + (values[i] !== void 0 ? stringifyInterpolation(values[i]) : ""),
|
|
128
|
+
""
|
|
129
|
+
);
|
|
130
|
+
const dedented = dedent(raw);
|
|
131
|
+
let parsed;
|
|
132
|
+
try {
|
|
133
|
+
parsed = parse(dedented);
|
|
134
|
+
} catch (e) {
|
|
135
|
+
throw new Error(`[yrest] Invalid YAML: ${e instanceof Error ? e.message : String(e)}`, {
|
|
136
|
+
cause: e
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
140
|
+
throw new Error("[yrest] Template must resolve to a YAML object with collection keys.");
|
|
141
|
+
}
|
|
142
|
+
return parsed;
|
|
143
|
+
}
|
|
144
|
+
function stringifyInterpolation(value) {
|
|
145
|
+
if (value === null || value === void 0) return String(value);
|
|
146
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
147
|
+
return String(value);
|
|
148
|
+
}
|
|
149
|
+
const type = Array.isArray(value) ? "array" : typeof value;
|
|
150
|
+
throw new Error(
|
|
151
|
+
`[yrest] Cannot interpolate a value of type "${type}". Only string, number, and boolean are supported.`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
function dedent(str) {
|
|
155
|
+
const lines = str.split("\n");
|
|
156
|
+
const minIndent = lines.filter((line) => line.trim().length > 0).reduce((min, line) => {
|
|
157
|
+
const match = line.match(/^(\s*)/);
|
|
158
|
+
return Math.min(min, match ? match[1].length : 0);
|
|
159
|
+
}, Infinity);
|
|
160
|
+
const indent = minIndent === Infinity ? 0 : minIndent;
|
|
161
|
+
return lines.map((line) => line.slice(indent)).join("\n").trim();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/api/yrestServer.ts
|
|
165
|
+
init_esm_shims();
|
|
166
|
+
import { resolve as resolve2 } from "path";
|
|
167
|
+
|
|
168
|
+
// src/utils/handlers.ts
|
|
169
|
+
init_esm_shims();
|
|
170
|
+
import { existsSync } from "fs";
|
|
171
|
+
async function loadHandlers(filePath) {
|
|
172
|
+
if (!existsSync(filePath)) return /* @__PURE__ */ new Map();
|
|
173
|
+
try {
|
|
174
|
+
const mod = await import(filePath);
|
|
175
|
+
const map = /* @__PURE__ */ new Map();
|
|
176
|
+
for (const [name, value] of Object.entries(mod)) {
|
|
177
|
+
if (typeof value === "function") map.set(name, value);
|
|
178
|
+
}
|
|
179
|
+
return map;
|
|
180
|
+
} catch (err) {
|
|
181
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
182
|
+
console.error(` \x1B[31m[handlers] failed to load ${filePath} \u2014 ${msg}\x1B[0m`);
|
|
183
|
+
return /* @__PURE__ */ new Map();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/api/yrestServer.ts
|
|
188
|
+
init_deepCopy();
|
|
189
|
+
|
|
190
|
+
// src/server/index.ts
|
|
191
|
+
init_esm_shims();
|
|
84
192
|
|
|
85
193
|
// src/server/createServer.ts
|
|
194
|
+
init_esm_shims();
|
|
86
195
|
import Fastify from "fastify";
|
|
87
196
|
import cors from "@fastify/cors";
|
|
88
197
|
|
|
198
|
+
// src/router/resource.router.ts
|
|
199
|
+
init_esm_shims();
|
|
200
|
+
|
|
201
|
+
// src/router/routes/index.ts
|
|
202
|
+
init_esm_shims();
|
|
203
|
+
|
|
204
|
+
// src/router/routes/about.routes.ts
|
|
205
|
+
init_esm_shims();
|
|
206
|
+
|
|
207
|
+
// src/router/templates/about.template.ts
|
|
208
|
+
init_esm_shims();
|
|
209
|
+
|
|
89
210
|
// src/utils/interpolate.ts
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
211
|
+
init_esm_shims();
|
|
212
|
+
import { randomUUID } from "crypto";
|
|
213
|
+
function getPath(obj, path2) {
|
|
214
|
+
return path2.split(".").reduce((acc, key) => {
|
|
93
215
|
if (acc != null && typeof acc === "object") return acc[key];
|
|
94
216
|
return void 0;
|
|
95
217
|
}, obj);
|
|
96
218
|
}
|
|
97
|
-
function resolveVar(
|
|
98
|
-
if (
|
|
99
|
-
if (
|
|
100
|
-
if (
|
|
101
|
-
if (
|
|
102
|
-
if (
|
|
103
|
-
if (
|
|
104
|
-
const val = ctx.query[
|
|
219
|
+
function resolveVar(path2, ctx) {
|
|
220
|
+
if (path2 === "now") return (/* @__PURE__ */ new Date()).toISOString();
|
|
221
|
+
if (path2 === "uuid") return randomUUID();
|
|
222
|
+
if (path2 === "body") return ctx.body;
|
|
223
|
+
if (path2.startsWith("body.")) return getPath(ctx.body, path2.slice(5));
|
|
224
|
+
if (path2.startsWith("params.")) return ctx.params[path2.slice(7)] ?? "";
|
|
225
|
+
if (path2.startsWith("query.")) {
|
|
226
|
+
const val = ctx.query[path2.slice(6)];
|
|
105
227
|
return Array.isArray(val) ? val[0] : val ?? "";
|
|
106
228
|
}
|
|
107
|
-
if (
|
|
108
|
-
const val = ctx.headers[
|
|
229
|
+
if (path2.startsWith("headers.")) {
|
|
230
|
+
const val = ctx.headers[path2.slice(8)];
|
|
109
231
|
return Array.isArray(val) ? val[0] : val ?? "";
|
|
110
232
|
}
|
|
111
233
|
return "";
|
|
@@ -113,8 +235,8 @@ function resolveVar(path, ctx) {
|
|
|
113
235
|
function interpolateString(str, ctx) {
|
|
114
236
|
const exact = str.match(/^\{\{([^}]+)\}\}$/);
|
|
115
237
|
if (exact) return resolveVar(exact[1].trim(), ctx);
|
|
116
|
-
return str.replace(/\{\{([^}]+)\}\}/g, (_,
|
|
117
|
-
const val = resolveVar(
|
|
238
|
+
return str.replace(/\{\{([^}]+)\}\}/g, (_, path2) => {
|
|
239
|
+
const val = resolveVar(path2.trim(), ctx);
|
|
118
240
|
return val == null ? "" : String(val);
|
|
119
241
|
});
|
|
120
242
|
}
|
|
@@ -148,11 +270,11 @@ function methodBadge(method) {
|
|
|
148
270
|
const color = METHOD_COLOR[method] ?? "#7d8590";
|
|
149
271
|
return badge(method, color, `${color}18`);
|
|
150
272
|
}
|
|
151
|
-
function endpointRow(method,
|
|
273
|
+
function endpointRow(method, path2, desc) {
|
|
152
274
|
return `
|
|
153
275
|
<tr>
|
|
154
276
|
<td class="method-cell">${methodBadge(method)}</td>
|
|
155
|
-
<td class="path-cell"><code>${
|
|
277
|
+
<td class="path-cell"><code>${path2}</code></td>
|
|
156
278
|
<td class="desc-cell">${desc}</td>
|
|
157
279
|
</tr>`;
|
|
158
280
|
}
|
|
@@ -332,16 +454,33 @@ function generateAboutHtml(storage, options, handlers = /* @__PURE__ */ new Map(
|
|
|
332
454
|
<table><tbody>
|
|
333
455
|
${customRoutes.map((r) => {
|
|
334
456
|
const fullPath = `${base}${r.path}`;
|
|
457
|
+
const tags = [];
|
|
458
|
+
if (r.delay && r.delay > 0) {
|
|
459
|
+
tags.push(`<span style="color:#fb923c;font-size:11px">delay\xB7${r.delay}ms</span>`);
|
|
460
|
+
}
|
|
461
|
+
if (r.scenarios?.length) {
|
|
462
|
+
const hasOr = r.scenarios.some((s) => Array.isArray(s.when));
|
|
463
|
+
tags.push(
|
|
464
|
+
`<span style="color:#a371f7;font-size:11px">scenarios\xB7${r.scenarios.length}${hasOr ? " (OR)" : ""}</span>`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
if (r.otherwise) {
|
|
468
|
+
tags.push(`<span style="color:var(--text-muted);font-size:11px">otherwise</span>`);
|
|
469
|
+
}
|
|
335
470
|
let desc;
|
|
336
471
|
if (r.handler) {
|
|
337
472
|
const found = handlers.has(r.handler);
|
|
338
473
|
desc = found ? `Handler \u2014 <code>${r.handler}()</code>` : `Handler \u2014 <code>${r.handler}()</code> <span style="color:#f85149">(not loaded)</span>`;
|
|
474
|
+
} else if (r.scenarios?.length) {
|
|
475
|
+
const hasTemplateInScenarios = r.scenarios.some((s) => s.response.body != null && hasTemplates(s.response.body)) || r.otherwise?.body != null && hasTemplates(r.otherwise.body);
|
|
476
|
+
desc = hasTemplateInScenarios ? `Scenarios \u2014 <code>{{\u2026}}</code>` : `Scenarios`;
|
|
339
477
|
} else if (r.response?.body != null && hasTemplates(r.response.body)) {
|
|
340
|
-
desc = `Dynamic
|
|
478
|
+
desc = `Dynamic \u2014 <code>{{\u2026}}</code>`;
|
|
341
479
|
} else {
|
|
342
480
|
const status = r.response?.status ?? 200;
|
|
343
|
-
desc = `Static \u2014 <code>${status}</code>${r.response?.headers ? ` +
|
|
481
|
+
desc = `Static \u2014 <code>${status}</code>${r.response?.headers ? ` + headers` : ""}`;
|
|
344
482
|
}
|
|
483
|
+
if (tags.length) desc += ` ${tags.join(" ")}`;
|
|
345
484
|
return endpointRow(r.method?.toUpperCase() ?? "GET", fullPath, desc);
|
|
346
485
|
}).join("")}
|
|
347
486
|
</tbody></table>
|
|
@@ -493,7 +632,7 @@ function generateAboutHtml(storage, options, handlers = /* @__PURE__ */ new Map(
|
|
|
493
632
|
|
|
494
633
|
<div class="banner">
|
|
495
634
|
<div class="banner-inner">
|
|
496
|
-
<h1><span class="y">y</span><span class="rest">
|
|
635
|
+
<h1><span class="y">y</span><span class="rest">Rest</span></h1>
|
|
497
636
|
<p>Zero-config REST API mock server</p>
|
|
498
637
|
<div class="banner-meta">
|
|
499
638
|
<span>URL <strong>${host}</strong></span>
|
|
@@ -545,7 +684,7 @@ function generateAboutHtml(storage, options, handlers = /* @__PURE__ */ new Map(
|
|
|
545
684
|
${collections.length ? `<h2>Examples</h2><div class="card">${examplesBlock(collections, relations, base, host, options, customRoutes[0])}</div>` : ""}
|
|
546
685
|
|
|
547
686
|
<footer>
|
|
548
|
-
Powered by <a href="https://github.com/aggiovato/
|
|
687
|
+
Powered by <a href="https://github.com/aggiovato/yRest" target="_blank">@yrest/cli</a> \xB7 <a href="/_about">/_about</a>
|
|
549
688
|
</footer>
|
|
550
689
|
|
|
551
690
|
</div>
|
|
@@ -571,7 +710,11 @@ var AboutRouteCommand = class {
|
|
|
571
710
|
}
|
|
572
711
|
};
|
|
573
712
|
|
|
713
|
+
// src/router/routes/collection.routes.ts
|
|
714
|
+
init_esm_shims();
|
|
715
|
+
|
|
574
716
|
// src/utils/params.ts
|
|
717
|
+
init_esm_shims();
|
|
575
718
|
function nextId(items) {
|
|
576
719
|
const ids = items.map((i) => i["id"]).filter((id) => typeof id === "number");
|
|
577
720
|
return ids.length > 0 ? Math.max(...ids) + 1 : 1;
|
|
@@ -582,6 +725,7 @@ function firstParam(value) {
|
|
|
582
725
|
}
|
|
583
726
|
|
|
584
727
|
// src/services/query.service.ts
|
|
728
|
+
init_esm_shims();
|
|
585
729
|
var OPERATORS = ["_gte", "_lte", "_ne", "_like", "_start", "_regex"];
|
|
586
730
|
function applyOperator(itemValue, op, filterValue) {
|
|
587
731
|
const strItem = String(itemValue);
|
|
@@ -657,6 +801,7 @@ function paginate(items, page, limit) {
|
|
|
657
801
|
}
|
|
658
802
|
|
|
659
803
|
// src/services/resource.service.ts
|
|
804
|
+
init_esm_shims();
|
|
660
805
|
function findById(items, id) {
|
|
661
806
|
return items.find((i) => String(i["id"]) === id);
|
|
662
807
|
}
|
|
@@ -704,6 +849,7 @@ function deleteItem(storage, resource, id) {
|
|
|
704
849
|
}
|
|
705
850
|
|
|
706
851
|
// src/services/expand.service.ts
|
|
852
|
+
init_esm_shims();
|
|
707
853
|
function expandItems(input, query, resource, storage) {
|
|
708
854
|
const isArray = Array.isArray(input);
|
|
709
855
|
const items = isArray ? input : [input];
|
|
@@ -851,6 +997,66 @@ var CollectionRouteCommand = class {
|
|
|
851
997
|
};
|
|
852
998
|
|
|
853
999
|
// src/router/routes/custom.routes.ts
|
|
1000
|
+
init_esm_shims();
|
|
1001
|
+
|
|
1002
|
+
// src/utils/conditions.ts
|
|
1003
|
+
init_esm_shims();
|
|
1004
|
+
function resolveRequestPath(dotPath, req) {
|
|
1005
|
+
const [root, ...rest] = dotPath.split(".");
|
|
1006
|
+
let value;
|
|
1007
|
+
switch (root) {
|
|
1008
|
+
case "body":
|
|
1009
|
+
value = req.body;
|
|
1010
|
+
break;
|
|
1011
|
+
case "params":
|
|
1012
|
+
value = req.params;
|
|
1013
|
+
break;
|
|
1014
|
+
case "query":
|
|
1015
|
+
value = req.query;
|
|
1016
|
+
break;
|
|
1017
|
+
case "headers":
|
|
1018
|
+
value = req.headers;
|
|
1019
|
+
break;
|
|
1020
|
+
default:
|
|
1021
|
+
return void 0;
|
|
1022
|
+
}
|
|
1023
|
+
for (const key of rest) {
|
|
1024
|
+
if (value != null && typeof value === "object") {
|
|
1025
|
+
value = value[key];
|
|
1026
|
+
} else {
|
|
1027
|
+
return void 0;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return value;
|
|
1031
|
+
}
|
|
1032
|
+
function matchConditionGroup(group, req) {
|
|
1033
|
+
return Object.entries(group).every(([key, expected]) => {
|
|
1034
|
+
const op = OPERATORS.find((o) => key.endsWith(o));
|
|
1035
|
+
if (op) {
|
|
1036
|
+
const path2 = key.slice(0, -op.length);
|
|
1037
|
+
const value2 = resolveRequestPath(path2, req);
|
|
1038
|
+
if (value2 === void 0) return false;
|
|
1039
|
+
return applyOperator(value2, op, String(expected));
|
|
1040
|
+
}
|
|
1041
|
+
const value = resolveRequestPath(key, req);
|
|
1042
|
+
return String(value) === String(expected);
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
function matchWhen(when, req) {
|
|
1046
|
+
if (Array.isArray(when)) {
|
|
1047
|
+
return when.some((group) => matchConditionGroup(group, req));
|
|
1048
|
+
}
|
|
1049
|
+
return matchConditionGroup(when, req);
|
|
1050
|
+
}
|
|
1051
|
+
function findMatchingScenario(scenarios, req) {
|
|
1052
|
+
return scenarios.find((s) => matchWhen(s.when, req));
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// src/router/routes/custom.routes.ts
|
|
1056
|
+
function resolveBody(body, ctx) {
|
|
1057
|
+
if (body != null && hasTemplates(body)) return interpolate(body, ctx);
|
|
1058
|
+
return body ?? null;
|
|
1059
|
+
}
|
|
854
1060
|
var CustomRouteCommand = class {
|
|
855
1061
|
constructor(storage, base, handlers = /* @__PURE__ */ new Map()) {
|
|
856
1062
|
this.storage = storage;
|
|
@@ -863,9 +1069,9 @@ var CustomRouteCommand = class {
|
|
|
863
1069
|
register(server) {
|
|
864
1070
|
for (const route of this.storage.getRoutes()) {
|
|
865
1071
|
const method = route.method?.toUpperCase();
|
|
866
|
-
const
|
|
867
|
-
if (!method || !
|
|
868
|
-
const url = `${this.base}${
|
|
1072
|
+
const path2 = route.path;
|
|
1073
|
+
if (!method || !path2) continue;
|
|
1074
|
+
const url = `${this.base}${path2}`;
|
|
869
1075
|
const status = route.response?.status ?? 200;
|
|
870
1076
|
const rawBody = route.response?.body ?? null;
|
|
871
1077
|
const headers = route.response?.headers ?? {};
|
|
@@ -875,6 +1081,9 @@ var CustomRouteCommand = class {
|
|
|
875
1081
|
method,
|
|
876
1082
|
url,
|
|
877
1083
|
handler: async (req, reply) => {
|
|
1084
|
+
if (route.delay && route.delay > 0) {
|
|
1085
|
+
await new Promise((resolve3) => setTimeout(resolve3, route.delay));
|
|
1086
|
+
}
|
|
878
1087
|
for (const [key, value] of Object.entries(headers)) {
|
|
879
1088
|
reply.header(key, value);
|
|
880
1089
|
}
|
|
@@ -884,13 +1093,13 @@ var CustomRouteCommand = class {
|
|
|
884
1093
|
return reply.status(501).send({ error: `Handler "${handlerName}" is not defined in the handlers file` });
|
|
885
1094
|
}
|
|
886
1095
|
try {
|
|
887
|
-
const
|
|
1096
|
+
const ctx2 = {
|
|
888
1097
|
params: req.params,
|
|
889
1098
|
query: req.query,
|
|
890
1099
|
body: req.body,
|
|
891
1100
|
headers: req.headers
|
|
892
1101
|
};
|
|
893
|
-
const result = await fn(
|
|
1102
|
+
const result = await fn(ctx2);
|
|
894
1103
|
const resStatus = result.status ?? 200;
|
|
895
1104
|
for (const [k, v] of Object.entries(result.headers ?? {})) {
|
|
896
1105
|
reply.header(k, v);
|
|
@@ -902,12 +1111,24 @@ var CustomRouteCommand = class {
|
|
|
902
1111
|
return reply.status(500).send({ error: `Handler "${handlerName}" threw an error: ${msg}` });
|
|
903
1112
|
}
|
|
904
1113
|
}
|
|
905
|
-
const
|
|
1114
|
+
const ctx = {
|
|
906
1115
|
params: req.params,
|
|
907
1116
|
query: req.query,
|
|
908
1117
|
body: req.body,
|
|
909
1118
|
headers: req.headers
|
|
910
|
-
}
|
|
1119
|
+
};
|
|
1120
|
+
if (route.scenarios?.length) {
|
|
1121
|
+
const matched = findMatchingScenario(route.scenarios, ctx);
|
|
1122
|
+
const active = matched?.response ?? route.otherwise;
|
|
1123
|
+
if (active) {
|
|
1124
|
+
const aStatus = active.status ?? 200;
|
|
1125
|
+
const aBody = resolveBody(active.body, ctx);
|
|
1126
|
+
for (const [k, v] of Object.entries(active.headers ?? {})) reply.header(k, v);
|
|
1127
|
+
if (!active.body && aStatus === 204) return reply.status(aStatus).send();
|
|
1128
|
+
return reply.status(aStatus).send(aBody);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
const body = dynamic ? interpolate(rawBody, ctx) : rawBody;
|
|
911
1132
|
if (body === null && status === 204) return reply.status(status).send();
|
|
912
1133
|
return reply.status(status).send(body);
|
|
913
1134
|
}
|
|
@@ -917,6 +1138,7 @@ var CustomRouteCommand = class {
|
|
|
917
1138
|
};
|
|
918
1139
|
|
|
919
1140
|
// src/router/routes/item.routes.ts
|
|
1141
|
+
init_esm_shims();
|
|
920
1142
|
var ItemRouteCommand = class {
|
|
921
1143
|
constructor(storage, resource, base) {
|
|
922
1144
|
this.storage = storage;
|
|
@@ -966,6 +1188,7 @@ var ItemRouteCommand = class {
|
|
|
966
1188
|
};
|
|
967
1189
|
|
|
968
1190
|
// src/router/routes/nested.routes.ts
|
|
1191
|
+
init_esm_shims();
|
|
969
1192
|
var NestedRouteCommand = class {
|
|
970
1193
|
constructor(storage, relations, base) {
|
|
971
1194
|
this.storage = storage;
|
|
@@ -1005,6 +1228,7 @@ var NestedRouteCommand = class {
|
|
|
1005
1228
|
};
|
|
1006
1229
|
|
|
1007
1230
|
// src/router/routes/snapshot.routes.ts
|
|
1231
|
+
init_esm_shims();
|
|
1008
1232
|
var SnapshotRouteCommand = class {
|
|
1009
1233
|
constructor(storage) {
|
|
1010
1234
|
this.storage = storage;
|
|
@@ -1094,9 +1318,119 @@ async function createServer(storage, options, handlers = /* @__PURE__ */ new Map
|
|
|
1094
1318
|
return server;
|
|
1095
1319
|
}
|
|
1096
1320
|
|
|
1321
|
+
// src/server/yrestServer.ts
|
|
1322
|
+
init_esm_shims();
|
|
1323
|
+
function createYrestServerFromStorage(storage, options, handlers = /* @__PURE__ */ new Map()) {
|
|
1324
|
+
let _port = 0;
|
|
1325
|
+
let _started = false;
|
|
1326
|
+
let _fastify = null;
|
|
1327
|
+
return {
|
|
1328
|
+
async start() {
|
|
1329
|
+
if (_started) return;
|
|
1330
|
+
_fastify = await createServer(storage, options, handlers);
|
|
1331
|
+
await _fastify.listen({ port: options.port, host: options.host });
|
|
1332
|
+
const address = _fastify.addresses()[0];
|
|
1333
|
+
_port = typeof address === "object" && "port" in address ? address.port : options.port;
|
|
1334
|
+
_started = true;
|
|
1335
|
+
},
|
|
1336
|
+
async stop() {
|
|
1337
|
+
if (!_started || !_fastify) return;
|
|
1338
|
+
await _fastify.close();
|
|
1339
|
+
_started = false;
|
|
1340
|
+
},
|
|
1341
|
+
get port() {
|
|
1342
|
+
return _port;
|
|
1343
|
+
},
|
|
1344
|
+
get url() {
|
|
1345
|
+
return `http://${options.host}:${_port}${options.base}`;
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// src/api/yrestServer.ts
|
|
1351
|
+
function createYrestServer(options) {
|
|
1352
|
+
const resolvedOptions = buildOptions(options);
|
|
1353
|
+
let _inner = null;
|
|
1354
|
+
return {
|
|
1355
|
+
async start() {
|
|
1356
|
+
const storage = "data" in options && options.data !== void 0 ? createInMemoryStorage(options.data) : (await Promise.resolve().then(() => (init_yrestStorage(), yrestStorage_exports))).createYrestStorage(resolvedOptions.file);
|
|
1357
|
+
const handlers = resolvedOptions.handlers ? await loadHandlers(resolve2(resolvedOptions.handlers)) : /* @__PURE__ */ new Map();
|
|
1358
|
+
_inner = createYrestServerFromStorage(storage, resolvedOptions, handlers);
|
|
1359
|
+
await _inner.start();
|
|
1360
|
+
},
|
|
1361
|
+
async stop() {
|
|
1362
|
+
await _inner?.stop();
|
|
1363
|
+
},
|
|
1364
|
+
get port() {
|
|
1365
|
+
return _inner?.port ?? 0;
|
|
1366
|
+
},
|
|
1367
|
+
get url() {
|
|
1368
|
+
return _inner?.url ?? "";
|
|
1369
|
+
}
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
function buildOptions(opts) {
|
|
1373
|
+
const pageable = opts.pageable;
|
|
1374
|
+
return {
|
|
1375
|
+
file: "file" in opts && opts.file ? opts.file : "",
|
|
1376
|
+
port: opts.port ?? 3070,
|
|
1377
|
+
host: opts.host ?? "localhost",
|
|
1378
|
+
base: opts.base ? opts.base.startsWith("/") ? opts.base : `/${opts.base}` : "",
|
|
1379
|
+
watch: false,
|
|
1380
|
+
snapshot: opts.snapshot ?? false,
|
|
1381
|
+
readonly: opts.readonly ?? false,
|
|
1382
|
+
delay: opts.delay ?? 0,
|
|
1383
|
+
handlers: opts.handlers,
|
|
1384
|
+
pageable: typeof pageable === "number" ? { enabled: true, limit: pageable } : pageable ? { enabled: true, limit: 10 } : { enabled: false, limit: 10 }
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
function createInMemoryStorage(data) {
|
|
1388
|
+
const raw = data;
|
|
1389
|
+
const relations = raw["_rel"] ?? {};
|
|
1390
|
+
const routes = Array.isArray(raw["_routes"]) ? raw["_routes"] : [];
|
|
1391
|
+
const collections = Object.fromEntries(
|
|
1392
|
+
Object.entries(raw).filter(([k]) => k !== "_rel" && k !== "_routes")
|
|
1393
|
+
);
|
|
1394
|
+
let snapshot = {
|
|
1395
|
+
data: deepCopyData(collections),
|
|
1396
|
+
relations: { ...relations },
|
|
1397
|
+
savedAt: /* @__PURE__ */ new Date()
|
|
1398
|
+
};
|
|
1399
|
+
return {
|
|
1400
|
+
getData: () => collections,
|
|
1401
|
+
getRelations: () => relations,
|
|
1402
|
+
getRoutes: () => routes,
|
|
1403
|
+
getCollection: (name) => collections[name],
|
|
1404
|
+
setCollection: (name, items) => {
|
|
1405
|
+
collections[name] = items;
|
|
1406
|
+
},
|
|
1407
|
+
persist: () => {
|
|
1408
|
+
},
|
|
1409
|
+
reload: () => {
|
|
1410
|
+
},
|
|
1411
|
+
getSnapshot: () => snapshot,
|
|
1412
|
+
saveSnapshot: () => {
|
|
1413
|
+
snapshot = {
|
|
1414
|
+
data: deepCopyData(collections),
|
|
1415
|
+
relations: { ...relations },
|
|
1416
|
+
savedAt: /* @__PURE__ */ new Date()
|
|
1417
|
+
};
|
|
1418
|
+
},
|
|
1419
|
+
resetToSnapshot: () => {
|
|
1420
|
+
const snap = deepCopyData(snapshot.data);
|
|
1421
|
+
for (const key of Object.keys(collections)) delete collections[key];
|
|
1422
|
+
Object.assign(collections, snap);
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
// src/index.ts
|
|
1428
|
+
init_yrestStorage();
|
|
1429
|
+
|
|
1097
1430
|
// src/config/loadOptions.ts
|
|
1431
|
+
init_esm_shims();
|
|
1098
1432
|
import { z } from "zod";
|
|
1099
|
-
var
|
|
1433
|
+
var yrestOptionsSchema = z.object({
|
|
1100
1434
|
/** Path to the YAML database file. Must be a non-empty string. */
|
|
1101
1435
|
file: z.string().min(1),
|
|
1102
1436
|
/** TCP port the server listens on. Accepts string input and coerces to number. */
|
|
@@ -1135,6 +1469,9 @@ var serverOptionsSchema = z.object({
|
|
|
1135
1469
|
});
|
|
1136
1470
|
export {
|
|
1137
1471
|
createServer,
|
|
1138
|
-
|
|
1139
|
-
|
|
1472
|
+
createYrestServer,
|
|
1473
|
+
createYrestServerFromStorage,
|
|
1474
|
+
createYrestStorage,
|
|
1475
|
+
yrest,
|
|
1476
|
+
yrestOptionsSchema
|
|
1140
1477
|
};
|
package/package.json
CHANGED