@yrest/cli 0.11.2 → 0.13.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/README.md +100 -1
- package/dist/cli/index.js +245 -40
- package/dist/cli/index.mjs +245 -40
- package/dist/index.d.mts +41 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +217 -4
- package/dist/index.mjs +216 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -153,6 +153,49 @@ var init_parseRelations = __esm({
|
|
|
153
153
|
}
|
|
154
154
|
});
|
|
155
155
|
|
|
156
|
+
// src/storage/parseSseRoutes.ts
|
|
157
|
+
function parseSseRoutes(raw) {
|
|
158
|
+
if (!Array.isArray(raw)) return [];
|
|
159
|
+
return raw.map(normaliseSseRoute).filter((r) => r !== null);
|
|
160
|
+
}
|
|
161
|
+
function normaliseSseRoute(raw) {
|
|
162
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
163
|
+
const v = raw;
|
|
164
|
+
const method = typeof v["_method"] === "string" ? v["_method"].toUpperCase() : null;
|
|
165
|
+
const path = v["_path"];
|
|
166
|
+
const sse = v["_sse"];
|
|
167
|
+
if (method !== "SSE") return null;
|
|
168
|
+
if (typeof path !== "string" || !sse || typeof sse !== "object" || Array.isArray(sse)) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
const s = sse;
|
|
172
|
+
const rawEvents = s["_events"];
|
|
173
|
+
if (!Array.isArray(rawEvents)) return null;
|
|
174
|
+
const events = rawEvents.map(normaliseEvent).filter((e) => e !== null);
|
|
175
|
+
if (!events.length) return null;
|
|
176
|
+
return {
|
|
177
|
+
path,
|
|
178
|
+
interval: typeof s["_interval"] === "number" ? s["_interval"] : 1e3,
|
|
179
|
+
loop: s["_loop"] !== false,
|
|
180
|
+
repeat: typeof s["_repeat"] === "number" ? s["_repeat"] : void 0,
|
|
181
|
+
events
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function normaliseEvent(raw) {
|
|
185
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
186
|
+
const v = raw;
|
|
187
|
+
if (!("_data" in v)) return null;
|
|
188
|
+
const event = { data: v["_data"] };
|
|
189
|
+
if (typeof v["_event"] === "string") event.event = v["_event"];
|
|
190
|
+
return event;
|
|
191
|
+
}
|
|
192
|
+
var init_parseSseRoutes = __esm({
|
|
193
|
+
"src/storage/parseSseRoutes.ts"() {
|
|
194
|
+
"use strict";
|
|
195
|
+
init_cjs_shims();
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
156
199
|
// src/storage/parseSchema.ts
|
|
157
200
|
function parseSchema(raw) {
|
|
158
201
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
@@ -241,6 +284,8 @@ function normaliseRoute(raw) {
|
|
|
241
284
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
242
285
|
const v = raw;
|
|
243
286
|
const method = v["_method"];
|
|
287
|
+
if (typeof method === "string" && method.toUpperCase() === "SSE") return null;
|
|
288
|
+
if ("_sse" in v) return null;
|
|
244
289
|
const path = v["_path"];
|
|
245
290
|
if (typeof method !== "string" || typeof path !== "string") return null;
|
|
246
291
|
const route = { method, path };
|
|
@@ -310,6 +355,58 @@ var init_parseData = __esm({
|
|
|
310
355
|
}
|
|
311
356
|
});
|
|
312
357
|
|
|
358
|
+
// src/storage/resolveDirectives.ts
|
|
359
|
+
function resolveDirectives(data) {
|
|
360
|
+
const registry = /* @__PURE__ */ new Map();
|
|
361
|
+
let modified = false;
|
|
362
|
+
for (const [collection, items] of Object.entries(data)) {
|
|
363
|
+
for (const item of items) {
|
|
364
|
+
for (const [field, value] of Object.entries(item)) {
|
|
365
|
+
if (typeof value !== "string") continue;
|
|
366
|
+
const m = UUID_GEN_RE.exec(value);
|
|
367
|
+
if (!m) continue;
|
|
368
|
+
const uuid = (0, import_node_crypto2.randomUUID)();
|
|
369
|
+
item[field] = uuid;
|
|
370
|
+
modified = true;
|
|
371
|
+
const alias = m[1];
|
|
372
|
+
if (alias) {
|
|
373
|
+
if (!registry.has(collection)) registry.set(collection, /* @__PURE__ */ new Map());
|
|
374
|
+
registry.get(collection).set(alias, uuid);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
for (const items of Object.values(data)) {
|
|
380
|
+
for (const item of items) {
|
|
381
|
+
for (const [field, value] of Object.entries(item)) {
|
|
382
|
+
if (typeof value !== "string") continue;
|
|
383
|
+
const m = FK_RE.exec(value);
|
|
384
|
+
if (!m) continue;
|
|
385
|
+
const col = m[1];
|
|
386
|
+
const alias = m[2];
|
|
387
|
+
const uuid = registry.get(col)?.get(alias);
|
|
388
|
+
if (uuid !== void 0) {
|
|
389
|
+
item[field] = uuid;
|
|
390
|
+
modified = true;
|
|
391
|
+
} else {
|
|
392
|
+
console.warn(`yrest: cannot resolve __fk.${col}:${alias} \u2014 alias not registered`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return modified;
|
|
398
|
+
}
|
|
399
|
+
var import_node_crypto2, UUID_GEN_RE, FK_RE;
|
|
400
|
+
var init_resolveDirectives = __esm({
|
|
401
|
+
"src/storage/resolveDirectives.ts"() {
|
|
402
|
+
"use strict";
|
|
403
|
+
init_cjs_shims();
|
|
404
|
+
import_node_crypto2 = require("crypto");
|
|
405
|
+
UUID_GEN_RE = /^__uuid_gen(?::([a-zA-Z0-9_-]+))?$/;
|
|
406
|
+
FK_RE = /^__fk\.([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)$/;
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
313
410
|
// src/storage/yrestStorage.ts
|
|
314
411
|
var yrestStorage_exports = {};
|
|
315
412
|
__export(yrestStorage_exports, {
|
|
@@ -374,15 +471,17 @@ function createYrestStorage(filePath) {
|
|
|
374
471
|
const raw = (0, import_yaml3.parse)((0, import_node_fs3.readFileSync)(absPath, "utf8")) ?? {};
|
|
375
472
|
const relations = parseRelations(raw["_rel"]);
|
|
376
473
|
const routes = parseRoutes(raw["_routes"]);
|
|
474
|
+
const sseRoutes = parseSseRoutes(raw["_routes"]);
|
|
377
475
|
const schema = parseSchema(raw["_schema"]);
|
|
378
476
|
const dataBlock = hasDataBlock(raw);
|
|
379
477
|
const data = parseData(raw);
|
|
478
|
+
const directivesModified = resolveDirectives(data);
|
|
380
479
|
let snapshot = {
|
|
381
480
|
data: deepCopyData(data),
|
|
382
481
|
relations: { ...relations },
|
|
383
482
|
savedAt: /* @__PURE__ */ new Date()
|
|
384
483
|
};
|
|
385
|
-
|
|
484
|
+
const storage = {
|
|
386
485
|
getData() {
|
|
387
486
|
return data;
|
|
388
487
|
},
|
|
@@ -395,6 +494,9 @@ function createYrestStorage(filePath) {
|
|
|
395
494
|
getRoutes() {
|
|
396
495
|
return routes;
|
|
397
496
|
},
|
|
497
|
+
getSseRoutes() {
|
|
498
|
+
return sseRoutes;
|
|
499
|
+
},
|
|
398
500
|
getCollection(name) {
|
|
399
501
|
return data[name];
|
|
400
502
|
},
|
|
@@ -410,7 +512,7 @@ function createYrestStorage(filePath) {
|
|
|
410
512
|
} else {
|
|
411
513
|
Object.assign(payload, data);
|
|
412
514
|
}
|
|
413
|
-
const tmp = (0, import_node_path2.resolve)((0, import_node_path2.dirname)(absPath), `.yrest-${(0,
|
|
515
|
+
const tmp = (0, import_node_path2.resolve)((0, import_node_path2.dirname)(absPath), `.yrest-${(0, import_node_crypto3.randomUUID)()}.tmp`);
|
|
414
516
|
(0, import_node_fs3.writeFileSync)(tmp, (0, import_yaml3.stringify)(payload), "utf8");
|
|
415
517
|
(0, import_node_fs3.renameSync)(tmp, absPath);
|
|
416
518
|
},
|
|
@@ -422,6 +524,7 @@ function createYrestStorage(filePath) {
|
|
|
422
524
|
Object.assign(data, freshData);
|
|
423
525
|
for (const key of Object.keys(relations)) delete relations[key];
|
|
424
526
|
Object.assign(relations, freshRelations);
|
|
527
|
+
if (resolveDirectives(data)) storage.persist();
|
|
425
528
|
},
|
|
426
529
|
getSnapshot() {
|
|
427
530
|
return snapshot;
|
|
@@ -442,20 +545,24 @@ function createYrestStorage(filePath) {
|
|
|
442
545
|
this.persist();
|
|
443
546
|
}
|
|
444
547
|
};
|
|
548
|
+
if (directivesModified) storage.persist();
|
|
549
|
+
return storage;
|
|
445
550
|
}
|
|
446
|
-
var import_node_fs3, import_node_path2,
|
|
551
|
+
var import_node_fs3, import_node_path2, import_node_crypto3, import_yaml3;
|
|
447
552
|
var init_yrestStorage = __esm({
|
|
448
553
|
"src/storage/yrestStorage.ts"() {
|
|
449
554
|
"use strict";
|
|
450
555
|
init_cjs_shims();
|
|
451
556
|
import_node_fs3 = require("fs");
|
|
452
557
|
import_node_path2 = require("path");
|
|
453
|
-
|
|
558
|
+
import_node_crypto3 = require("crypto");
|
|
454
559
|
import_yaml3 = require("yaml");
|
|
455
560
|
init_parseRelations();
|
|
456
561
|
init_parseRoutes();
|
|
562
|
+
init_parseSseRoutes();
|
|
457
563
|
init_parseSchema();
|
|
458
564
|
init_parseData();
|
|
565
|
+
init_resolveDirectives();
|
|
459
566
|
init_deepCopy();
|
|
460
567
|
}
|
|
461
568
|
});
|
|
@@ -519,6 +626,7 @@ function dedent(str) {
|
|
|
519
626
|
init_cjs_shims();
|
|
520
627
|
var import_node_path3 = require("path");
|
|
521
628
|
init_parseRelations();
|
|
629
|
+
init_parseSseRoutes();
|
|
522
630
|
init_parseSchema();
|
|
523
631
|
|
|
524
632
|
// src/utils/handlers.ts
|
|
@@ -630,6 +738,8 @@ var METHOD_COLOR = {
|
|
|
630
738
|
PUT: "#d29922",
|
|
631
739
|
PATCH: "#a371f7",
|
|
632
740
|
DELETE: "#f85149",
|
|
741
|
+
SSE: "#0ea5e9",
|
|
742
|
+
WS: "#8b5cf6",
|
|
633
743
|
fn: "#f0883e"
|
|
634
744
|
};
|
|
635
745
|
function escapeHtml(str) {
|
|
@@ -821,6 +931,30 @@ function handlersAccordion(handlers, routes, base) {
|
|
|
821
931
|
</tbody></table>
|
|
822
932
|
</details>`;
|
|
823
933
|
}
|
|
934
|
+
function sseRoutesAccordion(routes, base) {
|
|
935
|
+
if (!routes.length) return "";
|
|
936
|
+
const rows = routes.map((r) => {
|
|
937
|
+
const tags = [
|
|
938
|
+
`<span style="color:#0ea5e9;font-size:11px">interval\xB7${r.interval}ms</span>`,
|
|
939
|
+
`<span style="color:var(--text-muted);font-size:11px">${r.events.length} event${r.events.length !== 1 ? "s" : ""}</span>`
|
|
940
|
+
];
|
|
941
|
+
if (!r.loop) tags.push(`<span style="color:var(--text-muted);font-size:11px">no\xB7loop</span>`);
|
|
942
|
+
if (r.repeat !== void 0)
|
|
943
|
+
tags.push(`<span style="color:var(--text-muted);font-size:11px">repeat\xB7${r.repeat}\xD7</span>`);
|
|
944
|
+
const desc = `SSE stream ${tags.join(" ")}`;
|
|
945
|
+
return endpointRow("SSE", `${base}${r.path}`, desc);
|
|
946
|
+
});
|
|
947
|
+
return `
|
|
948
|
+
<details class="resource-card nested-card">
|
|
949
|
+
<summary>
|
|
950
|
+
<span class="resource-name">SSE streams</span>
|
|
951
|
+
<span class="route-count">${routes.length} stream${routes.length !== 1 ? "s" : ""}</span>
|
|
952
|
+
</summary>
|
|
953
|
+
<table><tbody>
|
|
954
|
+
${rows.join("")}
|
|
955
|
+
</tbody></table>
|
|
956
|
+
</details>`;
|
|
957
|
+
}
|
|
824
958
|
function examplesBlock(collections, relations, base, host, options, firstCustomRoute) {
|
|
825
959
|
const examples = [];
|
|
826
960
|
const firstCol = collections[0];
|
|
@@ -1493,6 +1627,7 @@ function generateAboutHtml(storage, options, handlers = /* @__PURE__ */ new Map(
|
|
|
1493
1627
|
const collections = Object.keys(storage.getData());
|
|
1494
1628
|
const relations = storage.getRelations();
|
|
1495
1629
|
const customRoutes = storage.getRoutes();
|
|
1630
|
+
const sseRoutes = storage.getSseRoutes();
|
|
1496
1631
|
const base = options.base;
|
|
1497
1632
|
const host = `http://${options.host}:${options.port}`;
|
|
1498
1633
|
const swaggerUrl = `https://petstore.swagger.io/?url=${encodeURIComponent(`${host}/_openapi.json`)}`;
|
|
@@ -1676,6 +1811,7 @@ function generateAboutHtml(storage, options, handlers = /* @__PURE__ */ new Map(
|
|
|
1676
1811
|
${accordions}
|
|
1677
1812
|
${nestedRoutesAccordion(relations, base)}
|
|
1678
1813
|
${options.snapshot ? snapshotAccordion() : ""}
|
|
1814
|
+
${sseRoutesAccordion(sseRoutes, base)}
|
|
1679
1815
|
${customRoutesAccordion(customRoutes, base, handlers)}
|
|
1680
1816
|
${handlersAccordion(handlers, customRoutes, base)}
|
|
1681
1817
|
</div>
|
|
@@ -2828,6 +2964,80 @@ var SnapshotRouteCommand = class {
|
|
|
2828
2964
|
}
|
|
2829
2965
|
};
|
|
2830
2966
|
|
|
2967
|
+
// src/router/routes/sse.routes.ts
|
|
2968
|
+
init_cjs_shims();
|
|
2969
|
+
var SSERouteCommand = class {
|
|
2970
|
+
constructor(storage, base) {
|
|
2971
|
+
this.storage = storage;
|
|
2972
|
+
this.base = base;
|
|
2973
|
+
}
|
|
2974
|
+
storage;
|
|
2975
|
+
base;
|
|
2976
|
+
register(server) {
|
|
2977
|
+
for (const route of this.storage.getSseRoutes()) {
|
|
2978
|
+
const url = `${this.base}${route.path}`;
|
|
2979
|
+
server.route({
|
|
2980
|
+
method: "GET",
|
|
2981
|
+
url,
|
|
2982
|
+
handler: async (req, reply) => {
|
|
2983
|
+
reply.hijack();
|
|
2984
|
+
const raw = reply.raw;
|
|
2985
|
+
raw.setHeader("Content-Type", "text/event-stream");
|
|
2986
|
+
raw.setHeader("Cache-Control", "no-cache");
|
|
2987
|
+
raw.setHeader("Connection", "keep-alive");
|
|
2988
|
+
raw.setHeader("Access-Control-Allow-Origin", "*");
|
|
2989
|
+
raw.flushHeaders();
|
|
2990
|
+
let alive = true;
|
|
2991
|
+
req.raw.on("close", () => {
|
|
2992
|
+
alive = false;
|
|
2993
|
+
});
|
|
2994
|
+
const keepAlive = setInterval(() => {
|
|
2995
|
+
if (alive) raw.write(": ping\n\n");
|
|
2996
|
+
}, 15e3);
|
|
2997
|
+
let frameIndex = 0;
|
|
2998
|
+
let cycleCount = 0;
|
|
2999
|
+
function sendNext() {
|
|
3000
|
+
if (!alive) {
|
|
3001
|
+
clearInterval(keepAlive);
|
|
3002
|
+
return;
|
|
3003
|
+
}
|
|
3004
|
+
const ev = route.events[frameIndex];
|
|
3005
|
+
const ctx = {
|
|
3006
|
+
params: req.params,
|
|
3007
|
+
query: req.query,
|
|
3008
|
+
body: null,
|
|
3009
|
+
headers: req.headers
|
|
3010
|
+
};
|
|
3011
|
+
const data = hasTemplates(ev.data) ? interpolate(ev.data, ctx) : ev.data;
|
|
3012
|
+
let chunk = "";
|
|
3013
|
+
if (ev.event) chunk += `event: ${ev.event}
|
|
3014
|
+
`;
|
|
3015
|
+
chunk += `data: ${JSON.stringify(data)}
|
|
3016
|
+
|
|
3017
|
+
`;
|
|
3018
|
+
raw.write(chunk);
|
|
3019
|
+
frameIndex++;
|
|
3020
|
+
if (frameIndex >= route.events.length) {
|
|
3021
|
+
frameIndex = 0;
|
|
3022
|
+
cycleCount++;
|
|
3023
|
+
const done = !route.loop || route.repeat !== void 0 && cycleCount >= route.repeat;
|
|
3024
|
+
if (done) {
|
|
3025
|
+
clearInterval(keepAlive);
|
|
3026
|
+
raw.end();
|
|
3027
|
+
return;
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
if (alive) {
|
|
3031
|
+
setTimeout(sendNext, route.interval);
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
sendNext();
|
|
3035
|
+
}
|
|
3036
|
+
});
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
};
|
|
3040
|
+
|
|
2831
3041
|
// src/router/resource.router.ts
|
|
2832
3042
|
function buildResourceRouteCommands(storage, options) {
|
|
2833
3043
|
const commands = [];
|
|
@@ -2869,6 +3079,7 @@ async function createServer(storage, options, handlers = /* @__PURE__ */ new Map
|
|
|
2869
3079
|
new AboutRouteCommand(storage, options, handlers),
|
|
2870
3080
|
new OpenApiRouteCommand(storage, options),
|
|
2871
3081
|
...options.snapshot ? [new SnapshotRouteCommand(storage)] : [],
|
|
3082
|
+
new SSERouteCommand(storage, options.base),
|
|
2872
3083
|
new CustomRouteCommand(storage, options.base, handlers),
|
|
2873
3084
|
...buildResourceRouteCommands(storage, options)
|
|
2874
3085
|
];
|
|
@@ -2950,6 +3161,7 @@ function createInMemoryStorage(data) {
|
|
|
2950
3161
|
const RESERVED2 = /* @__PURE__ */ new Set(["_rel", "_routes", "_schema"]);
|
|
2951
3162
|
const relations = parseRelations(raw["_rel"]);
|
|
2952
3163
|
const routes = Array.isArray(raw["_routes"]) ? raw["_routes"] : [];
|
|
3164
|
+
const sseRoutes = parseSseRoutes(raw["_routes"]);
|
|
2953
3165
|
const schema = parseSchema(raw["_schema"]);
|
|
2954
3166
|
const collections = Object.fromEntries(
|
|
2955
3167
|
Object.entries(raw).filter(([k]) => !RESERVED2.has(k))
|
|
@@ -2964,6 +3176,7 @@ function createInMemoryStorage(data) {
|
|
|
2964
3176
|
getRelations: () => relations,
|
|
2965
3177
|
getSchema: () => schema,
|
|
2966
3178
|
getRoutes: () => routes,
|
|
3179
|
+
getSseRoutes: () => sseRoutes,
|
|
2967
3180
|
getCollection: (name) => collections[name],
|
|
2968
3181
|
setCollection: (name, items) => {
|
|
2969
3182
|
collections[name] = items;
|