@yrest/cli 0.5.3 → 0.6.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
  }
@@ -571,7 +693,11 @@ var AboutRouteCommand = class {
571
693
  }
572
694
  };
573
695
 
696
+ // src/router/routes/collection.routes.ts
697
+ init_esm_shims();
698
+
574
699
  // src/utils/params.ts
700
+ init_esm_shims();
575
701
  function nextId(items) {
576
702
  const ids = items.map((i) => i["id"]).filter((id) => typeof id === "number");
577
703
  return ids.length > 0 ? Math.max(...ids) + 1 : 1;
@@ -582,6 +708,7 @@ function firstParam(value) {
582
708
  }
583
709
 
584
710
  // src/services/query.service.ts
711
+ init_esm_shims();
585
712
  var OPERATORS = ["_gte", "_lte", "_ne", "_like", "_start", "_regex"];
586
713
  function applyOperator(itemValue, op, filterValue) {
587
714
  const strItem = String(itemValue);
@@ -657,6 +784,7 @@ function paginate(items, page, limit) {
657
784
  }
658
785
 
659
786
  // src/services/resource.service.ts
787
+ init_esm_shims();
660
788
  function findById(items, id) {
661
789
  return items.find((i) => String(i["id"]) === id);
662
790
  }
@@ -704,6 +832,7 @@ function deleteItem(storage, resource, id) {
704
832
  }
705
833
 
706
834
  // src/services/expand.service.ts
835
+ init_esm_shims();
707
836
  function expandItems(input, query, resource, storage) {
708
837
  const isArray = Array.isArray(input);
709
838
  const items = isArray ? input : [input];
@@ -851,6 +980,7 @@ var CollectionRouteCommand = class {
851
980
  };
852
981
 
853
982
  // src/router/routes/custom.routes.ts
983
+ init_esm_shims();
854
984
  var CustomRouteCommand = class {
855
985
  constructor(storage, base, handlers = /* @__PURE__ */ new Map()) {
856
986
  this.storage = storage;
@@ -863,9 +993,9 @@ var CustomRouteCommand = class {
863
993
  register(server) {
864
994
  for (const route of this.storage.getRoutes()) {
865
995
  const method = route.method?.toUpperCase();
866
- const path = route.path;
867
- if (!method || !path) continue;
868
- const url = `${this.base}${path}`;
996
+ const path2 = route.path;
997
+ if (!method || !path2) continue;
998
+ const url = `${this.base}${path2}`;
869
999
  const status = route.response?.status ?? 200;
870
1000
  const rawBody = route.response?.body ?? null;
871
1001
  const headers = route.response?.headers ?? {};
@@ -917,6 +1047,7 @@ var CustomRouteCommand = class {
917
1047
  };
918
1048
 
919
1049
  // src/router/routes/item.routes.ts
1050
+ init_esm_shims();
920
1051
  var ItemRouteCommand = class {
921
1052
  constructor(storage, resource, base) {
922
1053
  this.storage = storage;
@@ -966,6 +1097,7 @@ var ItemRouteCommand = class {
966
1097
  };
967
1098
 
968
1099
  // src/router/routes/nested.routes.ts
1100
+ init_esm_shims();
969
1101
  var NestedRouteCommand = class {
970
1102
  constructor(storage, relations, base) {
971
1103
  this.storage = storage;
@@ -1005,6 +1137,7 @@ var NestedRouteCommand = class {
1005
1137
  };
1006
1138
 
1007
1139
  // src/router/routes/snapshot.routes.ts
1140
+ init_esm_shims();
1008
1141
  var SnapshotRouteCommand = class {
1009
1142
  constructor(storage) {
1010
1143
  this.storage = storage;
@@ -1094,9 +1227,119 @@ async function createServer(storage, options, handlers = /* @__PURE__ */ new Map
1094
1227
  return server;
1095
1228
  }
1096
1229
 
1230
+ // src/server/yrestServer.ts
1231
+ init_esm_shims();
1232
+ function createYrestServerFromStorage(storage, options, handlers = /* @__PURE__ */ new Map()) {
1233
+ let _port = 0;
1234
+ let _started = false;
1235
+ let _fastify = null;
1236
+ return {
1237
+ async start() {
1238
+ if (_started) return;
1239
+ _fastify = await createServer(storage, options, handlers);
1240
+ await _fastify.listen({ port: options.port, host: options.host });
1241
+ const address = _fastify.addresses()[0];
1242
+ _port = typeof address === "object" && "port" in address ? address.port : options.port;
1243
+ _started = true;
1244
+ },
1245
+ async stop() {
1246
+ if (!_started || !_fastify) return;
1247
+ await _fastify.close();
1248
+ _started = false;
1249
+ },
1250
+ get port() {
1251
+ return _port;
1252
+ },
1253
+ get url() {
1254
+ return `http://${options.host}:${_port}${options.base}`;
1255
+ }
1256
+ };
1257
+ }
1258
+
1259
+ // src/api/yrestServer.ts
1260
+ function createYrestServer(options) {
1261
+ const resolvedOptions = buildOptions(options);
1262
+ let _inner = null;
1263
+ return {
1264
+ async start() {
1265
+ const storage = "data" in options && options.data !== void 0 ? createInMemoryStorage(options.data) : (await Promise.resolve().then(() => (init_yrestStorage(), yrestStorage_exports))).createYrestStorage(resolvedOptions.file);
1266
+ const handlers = resolvedOptions.handlers ? await loadHandlers(resolve2(resolvedOptions.handlers)) : /* @__PURE__ */ new Map();
1267
+ _inner = createYrestServerFromStorage(storage, resolvedOptions, handlers);
1268
+ await _inner.start();
1269
+ },
1270
+ async stop() {
1271
+ await _inner?.stop();
1272
+ },
1273
+ get port() {
1274
+ return _inner?.port ?? 0;
1275
+ },
1276
+ get url() {
1277
+ return _inner?.url ?? "";
1278
+ }
1279
+ };
1280
+ }
1281
+ function buildOptions(opts) {
1282
+ const pageable = opts.pageable;
1283
+ return {
1284
+ file: "file" in opts && opts.file ? opts.file : "",
1285
+ port: opts.port ?? 3070,
1286
+ host: opts.host ?? "localhost",
1287
+ base: opts.base ? opts.base.startsWith("/") ? opts.base : `/${opts.base}` : "",
1288
+ watch: false,
1289
+ snapshot: opts.snapshot ?? false,
1290
+ readonly: opts.readonly ?? false,
1291
+ delay: opts.delay ?? 0,
1292
+ handlers: opts.handlers,
1293
+ pageable: typeof pageable === "number" ? { enabled: true, limit: pageable } : pageable ? { enabled: true, limit: 10 } : { enabled: false, limit: 10 }
1294
+ };
1295
+ }
1296
+ function createInMemoryStorage(data) {
1297
+ const raw = data;
1298
+ const relations = raw["_rel"] ?? {};
1299
+ const routes = Array.isArray(raw["_routes"]) ? raw["_routes"] : [];
1300
+ const collections = Object.fromEntries(
1301
+ Object.entries(raw).filter(([k]) => k !== "_rel" && k !== "_routes")
1302
+ );
1303
+ let snapshot = {
1304
+ data: deepCopyData(collections),
1305
+ relations: { ...relations },
1306
+ savedAt: /* @__PURE__ */ new Date()
1307
+ };
1308
+ return {
1309
+ getData: () => collections,
1310
+ getRelations: () => relations,
1311
+ getRoutes: () => routes,
1312
+ getCollection: (name) => collections[name],
1313
+ setCollection: (name, items) => {
1314
+ collections[name] = items;
1315
+ },
1316
+ persist: () => {
1317
+ },
1318
+ reload: () => {
1319
+ },
1320
+ getSnapshot: () => snapshot,
1321
+ saveSnapshot: () => {
1322
+ snapshot = {
1323
+ data: deepCopyData(collections),
1324
+ relations: { ...relations },
1325
+ savedAt: /* @__PURE__ */ new Date()
1326
+ };
1327
+ },
1328
+ resetToSnapshot: () => {
1329
+ const snap = deepCopyData(snapshot.data);
1330
+ for (const key of Object.keys(collections)) delete collections[key];
1331
+ Object.assign(collections, snap);
1332
+ }
1333
+ };
1334
+ }
1335
+
1336
+ // src/index.ts
1337
+ init_yrestStorage();
1338
+
1097
1339
  // src/config/loadOptions.ts
1340
+ init_esm_shims();
1098
1341
  import { z } from "zod";
1099
- var serverOptionsSchema = z.object({
1342
+ var yrestOptionsSchema = z.object({
1100
1343
  /** Path to the YAML database file. Must be a non-empty string. */
1101
1344
  file: z.string().min(1),
1102
1345
  /** TCP port the server listens on. Accepts string input and coerces to number. */
@@ -1135,6 +1378,9 @@ var serverOptionsSchema = z.object({
1135
1378
  });
1136
1379
  export {
1137
1380
  createServer,
1138
- createYamlStorage,
1139
- serverOptionsSchema
1381
+ createYrestServer,
1382
+ createYrestServerFromStorage,
1383
+ createYrestStorage,
1384
+ yrest,
1385
+ yrestOptionsSchema
1140
1386
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yrest/cli",
3
- "version": "0.5.3",
3
+ "version": "0.6.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",