@emeryld/rrroutes-openapi 2.2.27 → 2.3.0-alpha.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/docs/LeafDocsPage.d.ts +9 -1
- package/dist/docs/docs.d.ts +1 -0
- package/dist/index.cjs +463 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.mjs +463 -3
- package/dist/index.mjs.map +1 -1
- package/dist/public/assets/docs.css +1 -1
- package/dist/public/assets/docs.js +19 -12
- package/dist/web/components/Analytics.d.ts +68 -0
- package/dist/web/components/CopyablePre.d.ts +7 -0
- package/dist/web/components/FiltersBar.d.ts +25 -0
- package/dist/web/components/HelperEnumInput.d.ts +10 -0
- package/dist/web/components/HistoryView.d.ts +2 -1
- package/dist/web/components/LogsView.d.ts +1 -0
- package/dist/web/components/PlaygroundOverlay.d.ts +2 -2
- package/dist/web/components/RequestLogs.d.ts +10 -0
- package/dist/web/components/ui/Button.d.ts +8 -0
- package/dist/web/components/ui/Clickable.d.ts +7 -0
- package/dist/web/components/ui/Tag.d.ts +9 -0
- package/dist/web/components/ui/Text.d.ts +8 -0
- package/dist/web/components/ui/index.d.ts +4 -0
- package/dist/web/historyStore.d.ts +21 -17
- package/dist/web/logsStore.d.ts +51 -0
- package/dist/webhooks.d.ts +181 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -184,12 +184,14 @@ var DocsDocument = ({
|
|
|
184
184
|
assetBase,
|
|
185
185
|
docsBase,
|
|
186
186
|
historyJson,
|
|
187
|
+
logsJson,
|
|
187
188
|
baseUrlSuffix,
|
|
189
|
+
webhooks,
|
|
188
190
|
cspNonce
|
|
189
191
|
}) => {
|
|
190
192
|
const cssHref = `${assetBase}/docs.css`;
|
|
191
193
|
const jsSrc = `${assetBase}/docs.js`;
|
|
192
|
-
const configJson = serializeConfig({ docsBasePath: docsBase, baseUrlSuffix });
|
|
194
|
+
const configJson = serializeConfig({ docsBasePath: docsBase, baseUrlSuffix, webhooks });
|
|
193
195
|
return /* @__PURE__ */ jsxs("html", { lang: "en", children: [
|
|
194
196
|
/* @__PURE__ */ jsxs("head", { children: [
|
|
195
197
|
/* @__PURE__ */ jsx("meta", { charSet: "UTF-8" }),
|
|
@@ -226,6 +228,15 @@ var DocsDocument = ({
|
|
|
226
228
|
dangerouslySetInnerHTML: { __html: historyJson }
|
|
227
229
|
}
|
|
228
230
|
),
|
|
231
|
+
/* @__PURE__ */ jsx(
|
|
232
|
+
"script",
|
|
233
|
+
{
|
|
234
|
+
id: "logs-data",
|
|
235
|
+
type: "application/json",
|
|
236
|
+
nonce: cspNonce,
|
|
237
|
+
dangerouslySetInnerHTML: { __html: logsJson }
|
|
238
|
+
}
|
|
239
|
+
),
|
|
229
240
|
/* @__PURE__ */ jsx(
|
|
230
241
|
"script",
|
|
231
242
|
{
|
|
@@ -248,6 +259,9 @@ function serializePresets(presets) {
|
|
|
248
259
|
function serializeHistorySeeds(historySeeds) {
|
|
249
260
|
return JSON.stringify(Array.isArray(historySeeds) ? historySeeds : []).replace(/<\//g, "<\\/");
|
|
250
261
|
}
|
|
262
|
+
function serializeLogSeeds(logSeeds) {
|
|
263
|
+
return JSON.stringify(Array.isArray(logSeeds) ? logSeeds : []).replace(/<\//g, "<\\/");
|
|
264
|
+
}
|
|
251
265
|
function serializeConfig(config) {
|
|
252
266
|
return JSON.stringify(config).replace(/<\//g, "<\\/");
|
|
253
267
|
}
|
|
@@ -257,7 +271,9 @@ function createLeafDocsDocument(leaves, options = {}) {
|
|
|
257
271
|
const presetsJson = serializePresets(options.presets);
|
|
258
272
|
const docsBase = normalizeDocsBase(options.docsBasePath);
|
|
259
273
|
const historyJson = serializeHistorySeeds(options.historySeeds);
|
|
274
|
+
const logsJson = serializeLogSeeds(options.logSeeds);
|
|
260
275
|
const baseUrlSuffix = normalizeBaseUrlSuffix(options.baseUrlSuffix);
|
|
276
|
+
const webhooks = options.webhooks;
|
|
261
277
|
return /* @__PURE__ */ jsx(
|
|
262
278
|
DocsDocument,
|
|
263
279
|
{
|
|
@@ -266,7 +282,9 @@ function createLeafDocsDocument(leaves, options = {}) {
|
|
|
266
282
|
assetBase,
|
|
267
283
|
docsBase,
|
|
268
284
|
historyJson,
|
|
285
|
+
logsJson,
|
|
269
286
|
baseUrlSuffix,
|
|
287
|
+
webhooks,
|
|
270
288
|
cspNonce: options.cspNonce
|
|
271
289
|
}
|
|
272
290
|
);
|
|
@@ -282,6 +300,65 @@ function renderLeafDocsHTML2(leaves, options = {}) {
|
|
|
282
300
|
return renderLeafDocsHTML(leaves, options);
|
|
283
301
|
}
|
|
284
302
|
|
|
303
|
+
// src/webhooks.ts
|
|
304
|
+
import { z as z2 } from "zod";
|
|
305
|
+
var logTypeSchema = z2.enum(["debug", "info", "warn", "error", "system"]);
|
|
306
|
+
var historyFeedEntrySchema = z2.object({
|
|
307
|
+
id: z2.string(),
|
|
308
|
+
requestId: z2.string().optional(),
|
|
309
|
+
timestamp: z2.number(),
|
|
310
|
+
method: z2.string(),
|
|
311
|
+
path: z2.string(),
|
|
312
|
+
fullUrl: z2.string().optional(),
|
|
313
|
+
params: z2.record(z2.string(), z2.string()).optional(),
|
|
314
|
+
query: z2.record(z2.string(), z2.string()).optional(),
|
|
315
|
+
body: z2.string().optional(),
|
|
316
|
+
output: z2.string().optional(),
|
|
317
|
+
status: z2.number().optional(),
|
|
318
|
+
durationMs: z2.number(),
|
|
319
|
+
error: z2.string().optional()
|
|
320
|
+
});
|
|
321
|
+
var logFeedEntrySchema = z2.object({
|
|
322
|
+
id: z2.string(),
|
|
323
|
+
type: logTypeSchema,
|
|
324
|
+
message: z2.string(),
|
|
325
|
+
timestamp: z2.number(),
|
|
326
|
+
requestId: z2.string().optional(),
|
|
327
|
+
tags: z2.array(z2.string()).optional(),
|
|
328
|
+
metadata: z2.string().optional()
|
|
329
|
+
});
|
|
330
|
+
var historyFeedQuerySchema = z2.object({
|
|
331
|
+
cursor: z2.string().optional(),
|
|
332
|
+
limit: z2.number().int().positive().optional(),
|
|
333
|
+
methods: z2.array(z2.string()).optional(),
|
|
334
|
+
path: z2.string().optional(),
|
|
335
|
+
status: z2.string().optional(),
|
|
336
|
+
text: z2.string().optional(),
|
|
337
|
+
from: z2.number().optional(),
|
|
338
|
+
to: z2.number().optional(),
|
|
339
|
+
sortBy: z2.enum(["timestamp", "path", "duration"]).optional(),
|
|
340
|
+
sortDir: z2.enum(["asc", "desc"]).optional()
|
|
341
|
+
});
|
|
342
|
+
var logFeedQuerySchema = z2.object({
|
|
343
|
+
cursor: z2.string().optional(),
|
|
344
|
+
limit: z2.number().int().positive().optional(),
|
|
345
|
+
types: z2.array(logTypeSchema).optional(),
|
|
346
|
+
tags: z2.array(z2.string()).optional(),
|
|
347
|
+
requestId: z2.string().optional(),
|
|
348
|
+
text: z2.string().optional(),
|
|
349
|
+
from: z2.number().optional(),
|
|
350
|
+
to: z2.number().optional(),
|
|
351
|
+
sortDir: z2.enum(["asc", "desc"]).optional()
|
|
352
|
+
});
|
|
353
|
+
var webhookPageSchema = (itemSchema) => z2.object({
|
|
354
|
+
items: z2.array(itemSchema).default([]),
|
|
355
|
+
nextCursor: z2.string().optional(),
|
|
356
|
+
prevCursor: z2.string().optional(),
|
|
357
|
+
total: z2.number().optional()
|
|
358
|
+
});
|
|
359
|
+
var historyWebhookResponseSchema = webhookPageSchema(historyFeedEntrySchema);
|
|
360
|
+
var logWebhookResponseSchema = webhookPageSchema(logFeedEntrySchema);
|
|
361
|
+
|
|
285
362
|
// src/index.ts
|
|
286
363
|
var trimTrailingSlash = (value) => value.endsWith("/") && value.length > 1 ? value.slice(0, -1) : value;
|
|
287
364
|
function mountRRRoutesDocs({
|
|
@@ -296,10 +373,150 @@ function mountRRRoutesDocs({
|
|
|
296
373
|
const assetsMountPath = trimTrailingSlash(
|
|
297
374
|
options.assetBasePath ?? `${normalizedDocsPath}/assets`
|
|
298
375
|
);
|
|
376
|
+
const webhookBaseInput = options.logWebhook?.basePath ?? `${normalizedDocsPath}/webhooks`;
|
|
377
|
+
const webhookBasePath = trimTrailingSlash(
|
|
378
|
+
webhookBaseInput.startsWith("/") ? webhookBaseInput : `/${webhookBaseInput}`
|
|
379
|
+
);
|
|
380
|
+
const defaultHistoryLimit = 200;
|
|
381
|
+
const defaultLogLimit = 400;
|
|
382
|
+
const inMemoryHistory = normalizeHistorySeeds(options.historySeeds, defaultHistoryLimit);
|
|
383
|
+
const inMemoryLogs = normalizeLogSeeds(options.logSeeds, defaultLogLimit);
|
|
384
|
+
const webhookPaths = {
|
|
385
|
+
history: `${webhookBasePath}/history`,
|
|
386
|
+
logs: `${webhookBasePath}/logs`
|
|
387
|
+
};
|
|
388
|
+
const webhookSchemas = {
|
|
389
|
+
history: {
|
|
390
|
+
query: historyFeedQuerySchema,
|
|
391
|
+
response: historyWebhookResponseSchema,
|
|
392
|
+
entry: historyFeedEntrySchema
|
|
393
|
+
},
|
|
394
|
+
logs: {
|
|
395
|
+
query: logFeedQuerySchema,
|
|
396
|
+
response: logWebhookResponseSchema,
|
|
397
|
+
entry: logFeedEntrySchema
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
const webhookLeaves = {
|
|
401
|
+
history: {
|
|
402
|
+
method: "get",
|
|
403
|
+
path: webhookPaths.history,
|
|
404
|
+
cfg: {
|
|
405
|
+
summary: "RRRoutes docs history feed",
|
|
406
|
+
description: "Returns request history for the docs UI.",
|
|
407
|
+
querySchema: historyFeedQuerySchema,
|
|
408
|
+
outputSchema: historyWebhookResponseSchema,
|
|
409
|
+
tags: ["rrroutes", "docs"]
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
logs: {
|
|
413
|
+
method: "get",
|
|
414
|
+
path: webhookPaths.logs,
|
|
415
|
+
cfg: {
|
|
416
|
+
summary: "RRRoutes docs request logs",
|
|
417
|
+
description: "Returns request logs for the docs UI.",
|
|
418
|
+
querySchema: logFeedQuerySchema,
|
|
419
|
+
outputSchema: logWebhookResponseSchema,
|
|
420
|
+
tags: ["rrroutes", "docs"]
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
};
|
|
299
424
|
const publicDir = resolvePublicDir();
|
|
300
425
|
const assetsDir = path.join(publicDir, "assets");
|
|
301
426
|
const cspEnabled = options.csp !== false;
|
|
302
427
|
router.use(assetsMountPath, expressStatic(assetsDir, { immutable: true, maxAge: "365d" }));
|
|
428
|
+
const usingFakeHistory = !options.logWebhook?.history;
|
|
429
|
+
const usingFakeLogs = !options.logWebhook?.logs;
|
|
430
|
+
if (usingFakeHistory || usingFakeLogs) {
|
|
431
|
+
router.use((req, res, next) => {
|
|
432
|
+
if (req.path.startsWith(webhookBasePath) || req.path.startsWith(assetsMountPath) || req.path.startsWith(normalizedDocsPath)) {
|
|
433
|
+
return next();
|
|
434
|
+
}
|
|
435
|
+
const start = Date.now();
|
|
436
|
+
const requestIdHeader = req.headers["x-request-id"] || req.headers["x-requestid"] || req.headers["x-request_id"];
|
|
437
|
+
const requestId = Array.isArray(requestIdHeader) ? requestIdHeader[0] : requestIdHeader;
|
|
438
|
+
res.once("finish", () => {
|
|
439
|
+
const timestamp = Date.now();
|
|
440
|
+
const durationMs = Math.max(timestamp - start, 0);
|
|
441
|
+
const methodUpper = String(req.method || "GET").toUpperCase();
|
|
442
|
+
const pathOnly = req.path || req.originalUrl || "";
|
|
443
|
+
const status = res.statusCode;
|
|
444
|
+
const errorMsg = status >= 400 ? `${status}` : void 0;
|
|
445
|
+
if (usingFakeHistory) {
|
|
446
|
+
inMemoryHistory.unshift({
|
|
447
|
+
id: randomBytes(8).toString("hex"),
|
|
448
|
+
requestId: requestId ? String(requestId) : void 0,
|
|
449
|
+
timestamp,
|
|
450
|
+
method: methodUpper,
|
|
451
|
+
path: pathOnly,
|
|
452
|
+
fullUrl: req.originalUrl || pathOnly,
|
|
453
|
+
params: {},
|
|
454
|
+
query: coerceQueryRecord(req.query),
|
|
455
|
+
body: coercePayload(req.body),
|
|
456
|
+
output: "",
|
|
457
|
+
status,
|
|
458
|
+
durationMs,
|
|
459
|
+
error: errorMsg
|
|
460
|
+
});
|
|
461
|
+
if (inMemoryHistory.length > defaultHistoryLimit) inMemoryHistory.length = defaultHistoryLimit;
|
|
462
|
+
}
|
|
463
|
+
if (usingFakeLogs) {
|
|
464
|
+
const logType = status >= 500 ? "error" : status >= 400 ? "warn" : "info";
|
|
465
|
+
const metadata = JSON.stringify({
|
|
466
|
+
query: req.query,
|
|
467
|
+
durationMs
|
|
468
|
+
});
|
|
469
|
+
inMemoryLogs.unshift({
|
|
470
|
+
id: randomBytes(8).toString("hex"),
|
|
471
|
+
type: logType,
|
|
472
|
+
message: `${methodUpper} ${pathOnly} -> ${status}`,
|
|
473
|
+
timestamp,
|
|
474
|
+
requestId: requestId ? String(requestId) : void 0,
|
|
475
|
+
tags: [],
|
|
476
|
+
metadata
|
|
477
|
+
});
|
|
478
|
+
if (inMemoryLogs.length > defaultLogLimit) inMemoryLogs.length = defaultLogLimit;
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
next();
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
router.get(webhookPaths.history, async (req, res) => {
|
|
485
|
+
const handler = options.logWebhook?.history;
|
|
486
|
+
try {
|
|
487
|
+
const query = parseHistoryWebhookQuery(req);
|
|
488
|
+
if (!handler) {
|
|
489
|
+
const filtered2 = applyHistoryQuery(inMemoryHistory, query, defaultHistoryLimit);
|
|
490
|
+
res.json(filtered2);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
const result = await handler({ query, req, res });
|
|
494
|
+
const normalized = normalizeWebhookPage(result);
|
|
495
|
+
const filtered = applyHistoryQuery(normalized.items, query, defaultHistoryLimit);
|
|
496
|
+
res.json(filtered);
|
|
497
|
+
} catch (err) {
|
|
498
|
+
console.error("Failed to serve history webhook", err);
|
|
499
|
+
res.status(500).json({ error: "Failed to load history feed" });
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
router.get(webhookPaths.logs, async (req, res) => {
|
|
503
|
+
const handler = options.logWebhook?.logs;
|
|
504
|
+
try {
|
|
505
|
+
const query = parseLogWebhookQuery(req);
|
|
506
|
+
if (!handler) {
|
|
507
|
+
const filtered2 = applyLogsQuery(inMemoryLogs, query, defaultLogLimit);
|
|
508
|
+
res.json(filtered2);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
const result = await handler({ query, req, res });
|
|
512
|
+
const normalized = normalizeWebhookPage(result);
|
|
513
|
+
const filtered = applyLogsQuery(normalized.items, query, defaultLogLimit);
|
|
514
|
+
res.json(filtered);
|
|
515
|
+
} catch (err) {
|
|
516
|
+
console.error("Failed to serve log webhook", err);
|
|
517
|
+
res.status(500).json({ error: "Failed to load logs feed" });
|
|
518
|
+
}
|
|
519
|
+
});
|
|
303
520
|
const docsRoutePaths = [normalizedDocsPath, `${normalizedDocsPath}/`, `${normalizedDocsPath}/*id`];
|
|
304
521
|
router.get(docsRoutePaths, (req, res) => {
|
|
305
522
|
const preparedLeaves = Array.isArray(leaves) ? leaves.filter((leaf) => leaf.cfg.docsHidden !== true) : [];
|
|
@@ -318,7 +535,12 @@ function mountRRRoutesDocs({
|
|
|
318
535
|
docsBasePath: `${prefix}${normalizedDocsPath}`,
|
|
319
536
|
baseUrlSuffix: prefix,
|
|
320
537
|
historySeeds: options.historySeeds,
|
|
321
|
-
|
|
538
|
+
logSeeds: options.logSeeds,
|
|
539
|
+
presets: normalizePresets(finalPresets),
|
|
540
|
+
webhooks: {
|
|
541
|
+
history: `${prefix}${webhookPaths.history}`,
|
|
542
|
+
logs: `${prefix}${webhookPaths.logs}`
|
|
543
|
+
}
|
|
322
544
|
});
|
|
323
545
|
if (cspEnabled && nonce) {
|
|
324
546
|
res.setHeader(
|
|
@@ -336,7 +558,7 @@ function mountRRRoutesDocs({
|
|
|
336
558
|
}
|
|
337
559
|
res.send(html);
|
|
338
560
|
});
|
|
339
|
-
return { path: docsPath };
|
|
561
|
+
return { path: docsPath, webhooks: webhookPaths, webhookLeaves, webhookSchemas };
|
|
340
562
|
}
|
|
341
563
|
function resolvePublicDir() {
|
|
342
564
|
const moduleDir = typeof __dirname !== "undefined" ? __dirname : path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -362,6 +584,244 @@ function normalizePresets(presets) {
|
|
|
362
584
|
})) : []
|
|
363
585
|
}));
|
|
364
586
|
}
|
|
587
|
+
function parseHistoryWebhookQuery(req) {
|
|
588
|
+
const query = req.query || {};
|
|
589
|
+
const methods = parseStringList(query.methods);
|
|
590
|
+
const path2 = typeof query.path === "string" ? query.path : void 0;
|
|
591
|
+
const status = typeof query.status === "string" ? query.status : void 0;
|
|
592
|
+
const text = typeof query.text === "string" ? query.text : void 0;
|
|
593
|
+
const cursor = typeof query.cursor === "string" ? query.cursor : void 0;
|
|
594
|
+
const sortBy = isSortKey(query.sortBy) ? query.sortBy : void 0;
|
|
595
|
+
const sortDir = isSortDir(query.sortDir) ? query.sortDir : void 0;
|
|
596
|
+
const limit = parseLimit(query.limit);
|
|
597
|
+
const from = parseDateInput(query.from);
|
|
598
|
+
const to = parseDateInput(query.to);
|
|
599
|
+
return {
|
|
600
|
+
cursor,
|
|
601
|
+
methods,
|
|
602
|
+
path: path2,
|
|
603
|
+
status,
|
|
604
|
+
text,
|
|
605
|
+
limit,
|
|
606
|
+
from,
|
|
607
|
+
to,
|
|
608
|
+
sortBy,
|
|
609
|
+
sortDir
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function parseLogWebhookQuery(req) {
|
|
613
|
+
const query = req.query || {};
|
|
614
|
+
const types = parseStringList(query.types);
|
|
615
|
+
const tags = parseStringList(query.tags);
|
|
616
|
+
const requestId = typeof query.requestId === "string" ? query.requestId : void 0;
|
|
617
|
+
const text = typeof query.text === "string" ? query.text : void 0;
|
|
618
|
+
const cursor = typeof query.cursor === "string" ? query.cursor : void 0;
|
|
619
|
+
const limit = parseLimit(query.limit);
|
|
620
|
+
const from = parseDateInput(query.from);
|
|
621
|
+
const to = parseDateInput(query.to);
|
|
622
|
+
const sortDir = isSortDir(query.sortDir) ? query.sortDir : void 0;
|
|
623
|
+
return {
|
|
624
|
+
cursor,
|
|
625
|
+
types,
|
|
626
|
+
tags,
|
|
627
|
+
requestId,
|
|
628
|
+
text,
|
|
629
|
+
limit,
|
|
630
|
+
from,
|
|
631
|
+
to,
|
|
632
|
+
sortDir
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
function parseStringList(value) {
|
|
636
|
+
if (typeof value !== "string") return void 0;
|
|
637
|
+
const parts = value.split(",").map((p) => p.trim()).filter(Boolean);
|
|
638
|
+
return parts.length ? parts : void 0;
|
|
639
|
+
}
|
|
640
|
+
function parseLimit(value) {
|
|
641
|
+
if (value === void 0) return void 0;
|
|
642
|
+
const num = Number(value);
|
|
643
|
+
if (!Number.isFinite(num) || num <= 0) return void 0;
|
|
644
|
+
return num;
|
|
645
|
+
}
|
|
646
|
+
function parseDateInput(value) {
|
|
647
|
+
if (typeof value !== "string") return void 0;
|
|
648
|
+
const numeric = Number(value);
|
|
649
|
+
if (Number.isFinite(numeric)) return numeric;
|
|
650
|
+
const timestamp = Date.parse(value);
|
|
651
|
+
if (Number.isNaN(timestamp)) return void 0;
|
|
652
|
+
return timestamp;
|
|
653
|
+
}
|
|
654
|
+
function isSortKey(value) {
|
|
655
|
+
return value === "timestamp" || value === "path" || value === "duration";
|
|
656
|
+
}
|
|
657
|
+
function isSortDir(value) {
|
|
658
|
+
return value === "asc" || value === "desc";
|
|
659
|
+
}
|
|
660
|
+
function normalizeWebhookPage(page) {
|
|
661
|
+
if (!page || typeof page !== "object") return { items: [] };
|
|
662
|
+
return {
|
|
663
|
+
items: Array.isArray(page.items) ? page.items : [],
|
|
664
|
+
nextCursor: page.nextCursor,
|
|
665
|
+
prevCursor: page.prevCursor,
|
|
666
|
+
total: page.total
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
function applyHistoryQuery(items, query, hardLimit) {
|
|
670
|
+
const fromTs = typeof query.from === "number" ? query.from : void 0;
|
|
671
|
+
const toTs = typeof query.to === "number" ? query.to : void 0;
|
|
672
|
+
const methods = query.methods ? new Set(query.methods.map((m) => m.toUpperCase())) : void 0;
|
|
673
|
+
const pathNeedle = (query.path || "").toLowerCase();
|
|
674
|
+
const textNeedle = (query.text || "").toLowerCase();
|
|
675
|
+
const statusNeedle = (query.status || "").trim();
|
|
676
|
+
const filtered = (Array.isArray(items) ? items : []).filter((entry) => {
|
|
677
|
+
if (methods?.size && !methods.has(String(entry.method || "").toUpperCase())) return false;
|
|
678
|
+
if (pathNeedle && !String(entry.path || "").toLowerCase().includes(pathNeedle)) return false;
|
|
679
|
+
if (statusNeedle) {
|
|
680
|
+
const statusStr = entry.status !== void 0 && entry.status !== null ? String(entry.status) : "ERR";
|
|
681
|
+
if (!statusStr.startsWith(statusNeedle)) return false;
|
|
682
|
+
}
|
|
683
|
+
if (Number.isFinite(fromTs) && entry.timestamp < fromTs) return false;
|
|
684
|
+
if (Number.isFinite(toTs) && entry.timestamp > toTs) return false;
|
|
685
|
+
if (textNeedle) {
|
|
686
|
+
const haystack = [
|
|
687
|
+
entry.path,
|
|
688
|
+
entry.fullUrl,
|
|
689
|
+
entry.body,
|
|
690
|
+
entry.output,
|
|
691
|
+
entry.error,
|
|
692
|
+
JSON.stringify(entry.params || {}),
|
|
693
|
+
JSON.stringify(entry.query || {})
|
|
694
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
695
|
+
if (!haystack.includes(textNeedle)) return false;
|
|
696
|
+
}
|
|
697
|
+
return true;
|
|
698
|
+
});
|
|
699
|
+
const sortBy = query.sortBy || "timestamp";
|
|
700
|
+
const direction = query.sortDir === "asc" ? 1 : -1;
|
|
701
|
+
const sorted = filtered.slice().sort((a, b) => {
|
|
702
|
+
let delta = 0;
|
|
703
|
+
if (sortBy === "path") {
|
|
704
|
+
delta = String(a.path || "").localeCompare(String(b.path || ""));
|
|
705
|
+
} else if (sortBy === "duration") {
|
|
706
|
+
delta = (a.durationMs || 0) - (b.durationMs || 0);
|
|
707
|
+
} else {
|
|
708
|
+
delta = (a.timestamp || 0) - (b.timestamp || 0);
|
|
709
|
+
}
|
|
710
|
+
return delta * direction;
|
|
711
|
+
});
|
|
712
|
+
return paginateItems(sorted, query.cursor, query.limit, hardLimit, 25);
|
|
713
|
+
}
|
|
714
|
+
function applyLogsQuery(items, query, hardLimit) {
|
|
715
|
+
const fromTs = typeof query.from === "number" ? query.from : void 0;
|
|
716
|
+
const toTs = typeof query.to === "number" ? query.to : void 0;
|
|
717
|
+
const textNeedle = (query.text || "").toLowerCase();
|
|
718
|
+
const requestIdNeedle = (query.requestId || "").toLowerCase();
|
|
719
|
+
const types = query.types ? new Set(query.types) : void 0;
|
|
720
|
+
const tags = query.tags ? new Set(query.tags) : void 0;
|
|
721
|
+
const filtered = (Array.isArray(items) ? items : []).filter((entry) => {
|
|
722
|
+
if (types?.size && !types.has(entry.type)) return false;
|
|
723
|
+
const entryTags = Array.isArray(entry.tags) ? entry.tags : [];
|
|
724
|
+
if (tags?.size && !entryTags.some((tag) => tags.has(tag))) return false;
|
|
725
|
+
if (requestIdNeedle && !(entry.requestId || "").toLowerCase().includes(requestIdNeedle))
|
|
726
|
+
return false;
|
|
727
|
+
if (Number.isFinite(fromTs) && entry.timestamp < fromTs) return false;
|
|
728
|
+
if (Number.isFinite(toTs) && entry.timestamp > toTs) return false;
|
|
729
|
+
if (textNeedle) {
|
|
730
|
+
const haystack = [
|
|
731
|
+
entry.message,
|
|
732
|
+
entry.requestId,
|
|
733
|
+
entryTags.join(" "),
|
|
734
|
+
typeof entry.metadata === "string" ? entry.metadata : JSON.stringify(entry.metadata || "")
|
|
735
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
736
|
+
if (!haystack.includes(textNeedle)) return false;
|
|
737
|
+
}
|
|
738
|
+
return true;
|
|
739
|
+
});
|
|
740
|
+
const direction = query.sortDir === "asc" ? 1 : -1;
|
|
741
|
+
const sorted = filtered.slice().sort((a, b) => (a.timestamp - b.timestamp) * direction);
|
|
742
|
+
return paginateItems(sorted, query.cursor, query.limit, hardLimit, 50);
|
|
743
|
+
}
|
|
744
|
+
function paginateItems(items, cursor, limit, hardLimit, fallbackLimit) {
|
|
745
|
+
const safeLimit = clampLimit(limit, hardLimit, fallbackLimit);
|
|
746
|
+
const start = parseCursor(cursor);
|
|
747
|
+
const end = start + safeLimit;
|
|
748
|
+
const slice = items.slice(start, end);
|
|
749
|
+
const nextCursor = end < items.length ? String(end) : void 0;
|
|
750
|
+
const prevCursor = start > 0 ? String(Math.max(start - safeLimit, 0)) : void 0;
|
|
751
|
+
return {
|
|
752
|
+
items: slice,
|
|
753
|
+
nextCursor,
|
|
754
|
+
prevCursor,
|
|
755
|
+
total: items.length
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
function clampLimit(value, hardLimit, fallback) {
|
|
759
|
+
if (!Number.isFinite(value)) return Math.min(hardLimit, fallback);
|
|
760
|
+
const safe = Math.max(1, Math.min(hardLimit, value));
|
|
761
|
+
return safe;
|
|
762
|
+
}
|
|
763
|
+
function parseCursor(cursor) {
|
|
764
|
+
const num = Number(cursor);
|
|
765
|
+
if (Number.isFinite(num) && num >= 0) return Math.floor(num);
|
|
766
|
+
return 0;
|
|
767
|
+
}
|
|
768
|
+
function normalizeHistorySeeds(seeds, hardLimit) {
|
|
769
|
+
if (!Array.isArray(seeds)) return [];
|
|
770
|
+
return seeds.slice(0, hardLimit).map((entry, idx) => ({
|
|
771
|
+
id: entry.id || randomBytes(8).toString("hex"),
|
|
772
|
+
requestId: typeof entry.requestId === "string" ? entry.requestId : void 0,
|
|
773
|
+
timestamp: entry.timestamp ?? Date.now() - idx * 1e3,
|
|
774
|
+
method: entry.method || "GET",
|
|
775
|
+
path: entry.path || "/",
|
|
776
|
+
fullUrl: entry.fullUrl || entry.path || "/",
|
|
777
|
+
params: entry.params || {},
|
|
778
|
+
query: entry.query || {},
|
|
779
|
+
body: entry.body || "",
|
|
780
|
+
output: entry.output || "",
|
|
781
|
+
status: entry.status,
|
|
782
|
+
durationMs: entry.durationMs ?? 0,
|
|
783
|
+
error: entry.error
|
|
784
|
+
})).filter((entry) => entry.method && entry.path);
|
|
785
|
+
}
|
|
786
|
+
function normalizeLogSeeds(seeds, hardLimit) {
|
|
787
|
+
if (!Array.isArray(seeds)) return [];
|
|
788
|
+
return seeds.slice(0, hardLimit).map((entry, idx) => ({
|
|
789
|
+
id: entry.id || randomBytes(8).toString("hex"),
|
|
790
|
+
type: entry.type || "info",
|
|
791
|
+
message: entry.message || "",
|
|
792
|
+
timestamp: entry.timestamp ?? Date.now() - idx * 1e3,
|
|
793
|
+
requestId: entry.requestId,
|
|
794
|
+
tags: Array.isArray(entry.tags) ? entry.tags : [],
|
|
795
|
+
metadata: entry.metadata
|
|
796
|
+
})).filter((entry) => entry.message);
|
|
797
|
+
}
|
|
798
|
+
function coerceQueryRecord(query) {
|
|
799
|
+
if (!query || typeof query !== "object") return {};
|
|
800
|
+
return Object.fromEntries(
|
|
801
|
+
Object.entries(query).map(([key, value]) => [key, coerceValue(value)])
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
function coercePayload(body) {
|
|
805
|
+
if (body === void 0 || body === null) return "";
|
|
806
|
+
if (typeof body === "string") return body;
|
|
807
|
+
try {
|
|
808
|
+
return JSON.stringify(body);
|
|
809
|
+
} catch {
|
|
810
|
+
return String(body);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
function coerceValue(value) {
|
|
814
|
+
if (value === void 0 || value === null) return "";
|
|
815
|
+
if (Array.isArray(value)) return value.map(coerceValue).join(",");
|
|
816
|
+
if (typeof value === "object") {
|
|
817
|
+
try {
|
|
818
|
+
return JSON.stringify(value);
|
|
819
|
+
} catch {
|
|
820
|
+
return String(value);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return String(value);
|
|
824
|
+
}
|
|
365
825
|
export {
|
|
366
826
|
mountRRRoutesDocs,
|
|
367
827
|
renderLeafDocsHTML2 as renderLeafDocsHTML,
|