@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/dist/index.mjs CHANGED
@@ -1,8 +1,21 @@
1
- // src/storage/yamlStorage.ts
2
- import { readFileSync, writeFileSync, renameSync } from "fs";
3
- import { resolve, dirname } from "path";
4
- import { randomUUID } from "crypto";
5
- import { parse, stringify } from "yaml";
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/yamlStorage.ts
15
- function createYamlStorage(filePath) {
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 = parse(readFileSync(absPath, "utf8")) ?? {};
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-${randomUUID()}.tmp`);
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 = parse(readFileSync(absPath, "utf8")) ?? {};
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
- import { randomUUID as randomUUID2 } from "crypto";
91
- function getPath(obj, path) {
92
- return path.split(".").reduce((acc, key) => {
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(path, ctx) {
98
- if (path === "now") return (/* @__PURE__ */ new Date()).toISOString();
99
- if (path === "uuid") return randomUUID2();
100
- if (path === "body") return ctx.body;
101
- if (path.startsWith("body.")) return getPath(ctx.body, path.slice(5));
102
- if (path.startsWith("params.")) return ctx.params[path.slice(7)] ?? "";
103
- if (path.startsWith("query.")) {
104
- const val = ctx.query[path.slice(6)];
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 (path.startsWith("headers.")) {
108
- const val = ctx.headers[path.slice(8)];
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, (_, path) => {
117
- const val = resolveVar(path.trim(), ctx);
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, path, desc) {
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>${path}</code></td>
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 body \u2014 <code>{{\u2026}}</code>`;
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 ? ` + custom headers` : ""}`;
481
+ desc = `Static \u2014 <code>${status}</code>${r.response?.headers ? ` + headers` : ""}`;
344
482
  }
483
+ if (tags.length) desc += `&ensp;${tags.join("&ensp;")}`;
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">rest</span></h1>
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/yaml-rest" target="_blank">@aggiovato/yrest</a> &nbsp;\xB7&nbsp; <a href="/_about">/_about</a>
687
+ Powered by <a href="https://github.com/aggiovato/yRest" target="_blank">@yrest/cli</a> &nbsp;\xB7&nbsp; <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 path = route.path;
867
- if (!method || !path) continue;
868
- const url = `${this.base}${path}`;
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 ctx = {
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(ctx);
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 body = dynamic ? interpolate(rawBody, {
1114
+ const ctx = {
906
1115
  params: req.params,
907
1116
  query: req.query,
908
1117
  body: req.body,
909
1118
  headers: req.headers
910
- }) : rawBody;
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 serverOptionsSchema = z.object({
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
- createYamlStorage,
1139
- serverOptionsSchema
1472
+ createYrestServer,
1473
+ createYrestServerFromStorage,
1474
+ createYrestStorage,
1475
+ yrest,
1476
+ yrestOptionsSchema
1140
1477
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yrest/cli",
3
- "version": "0.5.3",
3
+ "version": "0.7.0",
4
4
  "description": "YAML-powered json-server alternative. Zero-config REST API mock server with full CRUD, relations, filters and snapshots from a db.yml file.",
5
5
  "keywords": [
6
6
  "yrest",