@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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { AnyLeaf } from '@emeryld/rrroutes-contract';
|
|
2
2
|
import type { ReactElement } from 'react';
|
|
3
3
|
import type { SerializablePreset } from './presets.js';
|
|
4
|
+
import type { WebhookPaths } from '../webhooks.js';
|
|
5
|
+
import type { LogFeedEntry } from '../webhooks.js';
|
|
4
6
|
export interface RenderOptions {
|
|
5
7
|
/** CSP nonce applied to data + script tags. */
|
|
6
8
|
cspNonce?: string;
|
|
@@ -14,6 +16,10 @@ export interface RenderOptions {
|
|
|
14
16
|
presets?: SerializablePreset[];
|
|
15
17
|
/** Optional seed history entries to pre-populate the UI (useful in dev). */
|
|
16
18
|
historySeeds?: SerializableHistoryEntry[];
|
|
19
|
+
/** Optional seed log entries to pre-populate the logs UI. */
|
|
20
|
+
logSeeds?: LogFeedEntry[];
|
|
21
|
+
/** Paths for webhook-backed history/log feeds. */
|
|
22
|
+
webhooks?: WebhookPaths;
|
|
17
23
|
}
|
|
18
24
|
type DocsDocumentProps = {
|
|
19
25
|
leavesJson: string;
|
|
@@ -21,10 +27,12 @@ type DocsDocumentProps = {
|
|
|
21
27
|
assetBase: string;
|
|
22
28
|
docsBase: string;
|
|
23
29
|
historyJson: string;
|
|
30
|
+
logsJson: string;
|
|
24
31
|
baseUrlSuffix?: string;
|
|
32
|
+
webhooks?: WebhookPaths;
|
|
25
33
|
cspNonce?: string;
|
|
26
34
|
};
|
|
27
|
-
export declare const DocsDocument: ({ leavesJson, presetsJson, assetBase, docsBase, historyJson, baseUrlSuffix, cspNonce, }: DocsDocumentProps) => import("react/jsx-runtime").JSX.Element;
|
|
35
|
+
export declare const DocsDocument: ({ leavesJson, presetsJson, assetBase, docsBase, historyJson, logsJson, baseUrlSuffix, webhooks, cspNonce, }: DocsDocumentProps) => import("react/jsx-runtime").JSX.Element;
|
|
28
36
|
export declare function createLeafDocsDocument(leaves: AnyLeaf[], options?: RenderOptions): ReactElement;
|
|
29
37
|
export declare function renderLeafDocsHTML(leaves: AnyLeaf[], options?: RenderOptions): string;
|
|
30
38
|
export type SerializableHistoryEntry = {
|
package/dist/docs/docs.d.ts
CHANGED
|
@@ -4,3 +4,4 @@ export declare function renderLeafDocsHTML(leaves: AnyLeaf[], options?: RenderOp
|
|
|
4
4
|
export type { RenderOptions, SerializableHistoryEntry } from "./LeafDocsPage.js";
|
|
5
5
|
export { createLeafDocsDocument } from "./LeafDocsPage.js";
|
|
6
6
|
export type { SerializablePreset, SerializablePresetOperation } from "./presets.js";
|
|
7
|
+
export type { LogFeedEntry } from "../webhooks.js";
|
package/dist/index.cjs
CHANGED
|
@@ -221,12 +221,14 @@ var DocsDocument = ({
|
|
|
221
221
|
assetBase,
|
|
222
222
|
docsBase,
|
|
223
223
|
historyJson,
|
|
224
|
+
logsJson,
|
|
224
225
|
baseUrlSuffix,
|
|
226
|
+
webhooks,
|
|
225
227
|
cspNonce
|
|
226
228
|
}) => {
|
|
227
229
|
const cssHref = `${assetBase}/docs.css`;
|
|
228
230
|
const jsSrc = `${assetBase}/docs.js`;
|
|
229
|
-
const configJson = serializeConfig({ docsBasePath: docsBase, baseUrlSuffix });
|
|
231
|
+
const configJson = serializeConfig({ docsBasePath: docsBase, baseUrlSuffix, webhooks });
|
|
230
232
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("html", { lang: "en", children: [
|
|
231
233
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("head", { children: [
|
|
232
234
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("meta", { charSet: "UTF-8" }),
|
|
@@ -263,6 +265,15 @@ var DocsDocument = ({
|
|
|
263
265
|
dangerouslySetInnerHTML: { __html: historyJson }
|
|
264
266
|
}
|
|
265
267
|
),
|
|
268
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
269
|
+
"script",
|
|
270
|
+
{
|
|
271
|
+
id: "logs-data",
|
|
272
|
+
type: "application/json",
|
|
273
|
+
nonce: cspNonce,
|
|
274
|
+
dangerouslySetInnerHTML: { __html: logsJson }
|
|
275
|
+
}
|
|
276
|
+
),
|
|
266
277
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
267
278
|
"script",
|
|
268
279
|
{
|
|
@@ -285,6 +296,9 @@ function serializePresets(presets) {
|
|
|
285
296
|
function serializeHistorySeeds(historySeeds) {
|
|
286
297
|
return JSON.stringify(Array.isArray(historySeeds) ? historySeeds : []).replace(/<\//g, "<\\/");
|
|
287
298
|
}
|
|
299
|
+
function serializeLogSeeds(logSeeds) {
|
|
300
|
+
return JSON.stringify(Array.isArray(logSeeds) ? logSeeds : []).replace(/<\//g, "<\\/");
|
|
301
|
+
}
|
|
288
302
|
function serializeConfig(config) {
|
|
289
303
|
return JSON.stringify(config).replace(/<\//g, "<\\/");
|
|
290
304
|
}
|
|
@@ -294,7 +308,9 @@ function createLeafDocsDocument(leaves, options = {}) {
|
|
|
294
308
|
const presetsJson = serializePresets(options.presets);
|
|
295
309
|
const docsBase = normalizeDocsBase(options.docsBasePath);
|
|
296
310
|
const historyJson = serializeHistorySeeds(options.historySeeds);
|
|
311
|
+
const logsJson = serializeLogSeeds(options.logSeeds);
|
|
297
312
|
const baseUrlSuffix = normalizeBaseUrlSuffix(options.baseUrlSuffix);
|
|
313
|
+
const webhooks = options.webhooks;
|
|
298
314
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
299
315
|
DocsDocument,
|
|
300
316
|
{
|
|
@@ -303,7 +319,9 @@ function createLeafDocsDocument(leaves, options = {}) {
|
|
|
303
319
|
assetBase,
|
|
304
320
|
docsBase,
|
|
305
321
|
historyJson,
|
|
322
|
+
logsJson,
|
|
306
323
|
baseUrlSuffix,
|
|
324
|
+
webhooks,
|
|
307
325
|
cspNonce: options.cspNonce
|
|
308
326
|
}
|
|
309
327
|
);
|
|
@@ -319,6 +337,65 @@ function renderLeafDocsHTML2(leaves, options = {}) {
|
|
|
319
337
|
return renderLeafDocsHTML(leaves, options);
|
|
320
338
|
}
|
|
321
339
|
|
|
340
|
+
// src/webhooks.ts
|
|
341
|
+
var import_zod = require("zod");
|
|
342
|
+
var logTypeSchema = import_zod.z.enum(["debug", "info", "warn", "error", "system"]);
|
|
343
|
+
var historyFeedEntrySchema = import_zod.z.object({
|
|
344
|
+
id: import_zod.z.string(),
|
|
345
|
+
requestId: import_zod.z.string().optional(),
|
|
346
|
+
timestamp: import_zod.z.number(),
|
|
347
|
+
method: import_zod.z.string(),
|
|
348
|
+
path: import_zod.z.string(),
|
|
349
|
+
fullUrl: import_zod.z.string().optional(),
|
|
350
|
+
params: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(),
|
|
351
|
+
query: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(),
|
|
352
|
+
body: import_zod.z.string().optional(),
|
|
353
|
+
output: import_zod.z.string().optional(),
|
|
354
|
+
status: import_zod.z.number().optional(),
|
|
355
|
+
durationMs: import_zod.z.number(),
|
|
356
|
+
error: import_zod.z.string().optional()
|
|
357
|
+
});
|
|
358
|
+
var logFeedEntrySchema = import_zod.z.object({
|
|
359
|
+
id: import_zod.z.string(),
|
|
360
|
+
type: logTypeSchema,
|
|
361
|
+
message: import_zod.z.string(),
|
|
362
|
+
timestamp: import_zod.z.number(),
|
|
363
|
+
requestId: import_zod.z.string().optional(),
|
|
364
|
+
tags: import_zod.z.array(import_zod.z.string()).optional(),
|
|
365
|
+
metadata: import_zod.z.string().optional()
|
|
366
|
+
});
|
|
367
|
+
var historyFeedQuerySchema = import_zod.z.object({
|
|
368
|
+
cursor: import_zod.z.string().optional(),
|
|
369
|
+
limit: import_zod.z.number().int().positive().optional(),
|
|
370
|
+
methods: import_zod.z.array(import_zod.z.string()).optional(),
|
|
371
|
+
path: import_zod.z.string().optional(),
|
|
372
|
+
status: import_zod.z.string().optional(),
|
|
373
|
+
text: import_zod.z.string().optional(),
|
|
374
|
+
from: import_zod.z.number().optional(),
|
|
375
|
+
to: import_zod.z.number().optional(),
|
|
376
|
+
sortBy: import_zod.z.enum(["timestamp", "path", "duration"]).optional(),
|
|
377
|
+
sortDir: import_zod.z.enum(["asc", "desc"]).optional()
|
|
378
|
+
});
|
|
379
|
+
var logFeedQuerySchema = import_zod.z.object({
|
|
380
|
+
cursor: import_zod.z.string().optional(),
|
|
381
|
+
limit: import_zod.z.number().int().positive().optional(),
|
|
382
|
+
types: import_zod.z.array(logTypeSchema).optional(),
|
|
383
|
+
tags: import_zod.z.array(import_zod.z.string()).optional(),
|
|
384
|
+
requestId: import_zod.z.string().optional(),
|
|
385
|
+
text: import_zod.z.string().optional(),
|
|
386
|
+
from: import_zod.z.number().optional(),
|
|
387
|
+
to: import_zod.z.number().optional(),
|
|
388
|
+
sortDir: import_zod.z.enum(["asc", "desc"]).optional()
|
|
389
|
+
});
|
|
390
|
+
var webhookPageSchema = (itemSchema) => import_zod.z.object({
|
|
391
|
+
items: import_zod.z.array(itemSchema).default([]),
|
|
392
|
+
nextCursor: import_zod.z.string().optional(),
|
|
393
|
+
prevCursor: import_zod.z.string().optional(),
|
|
394
|
+
total: import_zod.z.number().optional()
|
|
395
|
+
});
|
|
396
|
+
var historyWebhookResponseSchema = webhookPageSchema(historyFeedEntrySchema);
|
|
397
|
+
var logWebhookResponseSchema = webhookPageSchema(logFeedEntrySchema);
|
|
398
|
+
|
|
322
399
|
// src/index.ts
|
|
323
400
|
var trimTrailingSlash = (value) => value.endsWith("/") && value.length > 1 ? value.slice(0, -1) : value;
|
|
324
401
|
function mountRRRoutesDocs({
|
|
@@ -333,10 +410,150 @@ function mountRRRoutesDocs({
|
|
|
333
410
|
const assetsMountPath = trimTrailingSlash(
|
|
334
411
|
options.assetBasePath ?? `${normalizedDocsPath}/assets`
|
|
335
412
|
);
|
|
413
|
+
const webhookBaseInput = options.logWebhook?.basePath ?? `${normalizedDocsPath}/webhooks`;
|
|
414
|
+
const webhookBasePath = trimTrailingSlash(
|
|
415
|
+
webhookBaseInput.startsWith("/") ? webhookBaseInput : `/${webhookBaseInput}`
|
|
416
|
+
);
|
|
417
|
+
const defaultHistoryLimit = 200;
|
|
418
|
+
const defaultLogLimit = 400;
|
|
419
|
+
const inMemoryHistory = normalizeHistorySeeds(options.historySeeds, defaultHistoryLimit);
|
|
420
|
+
const inMemoryLogs = normalizeLogSeeds(options.logSeeds, defaultLogLimit);
|
|
421
|
+
const webhookPaths = {
|
|
422
|
+
history: `${webhookBasePath}/history`,
|
|
423
|
+
logs: `${webhookBasePath}/logs`
|
|
424
|
+
};
|
|
425
|
+
const webhookSchemas = {
|
|
426
|
+
history: {
|
|
427
|
+
query: historyFeedQuerySchema,
|
|
428
|
+
response: historyWebhookResponseSchema,
|
|
429
|
+
entry: historyFeedEntrySchema
|
|
430
|
+
},
|
|
431
|
+
logs: {
|
|
432
|
+
query: logFeedQuerySchema,
|
|
433
|
+
response: logWebhookResponseSchema,
|
|
434
|
+
entry: logFeedEntrySchema
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
const webhookLeaves = {
|
|
438
|
+
history: {
|
|
439
|
+
method: "get",
|
|
440
|
+
path: webhookPaths.history,
|
|
441
|
+
cfg: {
|
|
442
|
+
summary: "RRRoutes docs history feed",
|
|
443
|
+
description: "Returns request history for the docs UI.",
|
|
444
|
+
querySchema: historyFeedQuerySchema,
|
|
445
|
+
outputSchema: historyWebhookResponseSchema,
|
|
446
|
+
tags: ["rrroutes", "docs"]
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
logs: {
|
|
450
|
+
method: "get",
|
|
451
|
+
path: webhookPaths.logs,
|
|
452
|
+
cfg: {
|
|
453
|
+
summary: "RRRoutes docs request logs",
|
|
454
|
+
description: "Returns request logs for the docs UI.",
|
|
455
|
+
querySchema: logFeedQuerySchema,
|
|
456
|
+
outputSchema: logWebhookResponseSchema,
|
|
457
|
+
tags: ["rrroutes", "docs"]
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
};
|
|
336
461
|
const publicDir = resolvePublicDir();
|
|
337
462
|
const assetsDir = import_node_path.default.join(publicDir, "assets");
|
|
338
463
|
const cspEnabled = options.csp !== false;
|
|
339
464
|
router.use(assetsMountPath, (0, import_express.static)(assetsDir, { immutable: true, maxAge: "365d" }));
|
|
465
|
+
const usingFakeHistory = !options.logWebhook?.history;
|
|
466
|
+
const usingFakeLogs = !options.logWebhook?.logs;
|
|
467
|
+
if (usingFakeHistory || usingFakeLogs) {
|
|
468
|
+
router.use((req, res, next) => {
|
|
469
|
+
if (req.path.startsWith(webhookBasePath) || req.path.startsWith(assetsMountPath) || req.path.startsWith(normalizedDocsPath)) {
|
|
470
|
+
return next();
|
|
471
|
+
}
|
|
472
|
+
const start = Date.now();
|
|
473
|
+
const requestIdHeader = req.headers["x-request-id"] || req.headers["x-requestid"] || req.headers["x-request_id"];
|
|
474
|
+
const requestId = Array.isArray(requestIdHeader) ? requestIdHeader[0] : requestIdHeader;
|
|
475
|
+
res.once("finish", () => {
|
|
476
|
+
const timestamp = Date.now();
|
|
477
|
+
const durationMs = Math.max(timestamp - start, 0);
|
|
478
|
+
const methodUpper = String(req.method || "GET").toUpperCase();
|
|
479
|
+
const pathOnly = req.path || req.originalUrl || "";
|
|
480
|
+
const status = res.statusCode;
|
|
481
|
+
const errorMsg = status >= 400 ? `${status}` : void 0;
|
|
482
|
+
if (usingFakeHistory) {
|
|
483
|
+
inMemoryHistory.unshift({
|
|
484
|
+
id: (0, import_crypto.randomBytes)(8).toString("hex"),
|
|
485
|
+
requestId: requestId ? String(requestId) : void 0,
|
|
486
|
+
timestamp,
|
|
487
|
+
method: methodUpper,
|
|
488
|
+
path: pathOnly,
|
|
489
|
+
fullUrl: req.originalUrl || pathOnly,
|
|
490
|
+
params: {},
|
|
491
|
+
query: coerceQueryRecord(req.query),
|
|
492
|
+
body: coercePayload(req.body),
|
|
493
|
+
output: "",
|
|
494
|
+
status,
|
|
495
|
+
durationMs,
|
|
496
|
+
error: errorMsg
|
|
497
|
+
});
|
|
498
|
+
if (inMemoryHistory.length > defaultHistoryLimit) inMemoryHistory.length = defaultHistoryLimit;
|
|
499
|
+
}
|
|
500
|
+
if (usingFakeLogs) {
|
|
501
|
+
const logType = status >= 500 ? "error" : status >= 400 ? "warn" : "info";
|
|
502
|
+
const metadata = JSON.stringify({
|
|
503
|
+
query: req.query,
|
|
504
|
+
durationMs
|
|
505
|
+
});
|
|
506
|
+
inMemoryLogs.unshift({
|
|
507
|
+
id: (0, import_crypto.randomBytes)(8).toString("hex"),
|
|
508
|
+
type: logType,
|
|
509
|
+
message: `${methodUpper} ${pathOnly} -> ${status}`,
|
|
510
|
+
timestamp,
|
|
511
|
+
requestId: requestId ? String(requestId) : void 0,
|
|
512
|
+
tags: [],
|
|
513
|
+
metadata
|
|
514
|
+
});
|
|
515
|
+
if (inMemoryLogs.length > defaultLogLimit) inMemoryLogs.length = defaultLogLimit;
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
next();
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
router.get(webhookPaths.history, async (req, res) => {
|
|
522
|
+
const handler = options.logWebhook?.history;
|
|
523
|
+
try {
|
|
524
|
+
const query = parseHistoryWebhookQuery(req);
|
|
525
|
+
if (!handler) {
|
|
526
|
+
const filtered2 = applyHistoryQuery(inMemoryHistory, query, defaultHistoryLimit);
|
|
527
|
+
res.json(filtered2);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
const result = await handler({ query, req, res });
|
|
531
|
+
const normalized = normalizeWebhookPage(result);
|
|
532
|
+
const filtered = applyHistoryQuery(normalized.items, query, defaultHistoryLimit);
|
|
533
|
+
res.json(filtered);
|
|
534
|
+
} catch (err) {
|
|
535
|
+
console.error("Failed to serve history webhook", err);
|
|
536
|
+
res.status(500).json({ error: "Failed to load history feed" });
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
router.get(webhookPaths.logs, async (req, res) => {
|
|
540
|
+
const handler = options.logWebhook?.logs;
|
|
541
|
+
try {
|
|
542
|
+
const query = parseLogWebhookQuery(req);
|
|
543
|
+
if (!handler) {
|
|
544
|
+
const filtered2 = applyLogsQuery(inMemoryLogs, query, defaultLogLimit);
|
|
545
|
+
res.json(filtered2);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const result = await handler({ query, req, res });
|
|
549
|
+
const normalized = normalizeWebhookPage(result);
|
|
550
|
+
const filtered = applyLogsQuery(normalized.items, query, defaultLogLimit);
|
|
551
|
+
res.json(filtered);
|
|
552
|
+
} catch (err) {
|
|
553
|
+
console.error("Failed to serve log webhook", err);
|
|
554
|
+
res.status(500).json({ error: "Failed to load logs feed" });
|
|
555
|
+
}
|
|
556
|
+
});
|
|
340
557
|
const docsRoutePaths = [normalizedDocsPath, `${normalizedDocsPath}/`, `${normalizedDocsPath}/*id`];
|
|
341
558
|
router.get(docsRoutePaths, (req, res) => {
|
|
342
559
|
const preparedLeaves = Array.isArray(leaves) ? leaves.filter((leaf) => leaf.cfg.docsHidden !== true) : [];
|
|
@@ -355,7 +572,12 @@ function mountRRRoutesDocs({
|
|
|
355
572
|
docsBasePath: `${prefix}${normalizedDocsPath}`,
|
|
356
573
|
baseUrlSuffix: prefix,
|
|
357
574
|
historySeeds: options.historySeeds,
|
|
358
|
-
|
|
575
|
+
logSeeds: options.logSeeds,
|
|
576
|
+
presets: normalizePresets(finalPresets),
|
|
577
|
+
webhooks: {
|
|
578
|
+
history: `${prefix}${webhookPaths.history}`,
|
|
579
|
+
logs: `${prefix}${webhookPaths.logs}`
|
|
580
|
+
}
|
|
359
581
|
});
|
|
360
582
|
if (cspEnabled && nonce) {
|
|
361
583
|
res.setHeader(
|
|
@@ -373,7 +595,7 @@ function mountRRRoutesDocs({
|
|
|
373
595
|
}
|
|
374
596
|
res.send(html);
|
|
375
597
|
});
|
|
376
|
-
return { path: docsPath };
|
|
598
|
+
return { path: docsPath, webhooks: webhookPaths, webhookLeaves, webhookSchemas };
|
|
377
599
|
}
|
|
378
600
|
function resolvePublicDir() {
|
|
379
601
|
const moduleDir = typeof __dirname !== "undefined" ? __dirname : import_node_path.default.dirname((0, import_node_url.fileURLToPath)(__import_meta_url));
|
|
@@ -399,6 +621,244 @@ function normalizePresets(presets) {
|
|
|
399
621
|
})) : []
|
|
400
622
|
}));
|
|
401
623
|
}
|
|
624
|
+
function parseHistoryWebhookQuery(req) {
|
|
625
|
+
const query = req.query || {};
|
|
626
|
+
const methods = parseStringList(query.methods);
|
|
627
|
+
const path2 = typeof query.path === "string" ? query.path : void 0;
|
|
628
|
+
const status = typeof query.status === "string" ? query.status : void 0;
|
|
629
|
+
const text = typeof query.text === "string" ? query.text : void 0;
|
|
630
|
+
const cursor = typeof query.cursor === "string" ? query.cursor : void 0;
|
|
631
|
+
const sortBy = isSortKey(query.sortBy) ? query.sortBy : void 0;
|
|
632
|
+
const sortDir = isSortDir(query.sortDir) ? query.sortDir : void 0;
|
|
633
|
+
const limit = parseLimit(query.limit);
|
|
634
|
+
const from = parseDateInput(query.from);
|
|
635
|
+
const to = parseDateInput(query.to);
|
|
636
|
+
return {
|
|
637
|
+
cursor,
|
|
638
|
+
methods,
|
|
639
|
+
path: path2,
|
|
640
|
+
status,
|
|
641
|
+
text,
|
|
642
|
+
limit,
|
|
643
|
+
from,
|
|
644
|
+
to,
|
|
645
|
+
sortBy,
|
|
646
|
+
sortDir
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
function parseLogWebhookQuery(req) {
|
|
650
|
+
const query = req.query || {};
|
|
651
|
+
const types = parseStringList(query.types);
|
|
652
|
+
const tags = parseStringList(query.tags);
|
|
653
|
+
const requestId = typeof query.requestId === "string" ? query.requestId : void 0;
|
|
654
|
+
const text = typeof query.text === "string" ? query.text : void 0;
|
|
655
|
+
const cursor = typeof query.cursor === "string" ? query.cursor : void 0;
|
|
656
|
+
const limit = parseLimit(query.limit);
|
|
657
|
+
const from = parseDateInput(query.from);
|
|
658
|
+
const to = parseDateInput(query.to);
|
|
659
|
+
const sortDir = isSortDir(query.sortDir) ? query.sortDir : void 0;
|
|
660
|
+
return {
|
|
661
|
+
cursor,
|
|
662
|
+
types,
|
|
663
|
+
tags,
|
|
664
|
+
requestId,
|
|
665
|
+
text,
|
|
666
|
+
limit,
|
|
667
|
+
from,
|
|
668
|
+
to,
|
|
669
|
+
sortDir
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
function parseStringList(value) {
|
|
673
|
+
if (typeof value !== "string") return void 0;
|
|
674
|
+
const parts = value.split(",").map((p) => p.trim()).filter(Boolean);
|
|
675
|
+
return parts.length ? parts : void 0;
|
|
676
|
+
}
|
|
677
|
+
function parseLimit(value) {
|
|
678
|
+
if (value === void 0) return void 0;
|
|
679
|
+
const num = Number(value);
|
|
680
|
+
if (!Number.isFinite(num) || num <= 0) return void 0;
|
|
681
|
+
return num;
|
|
682
|
+
}
|
|
683
|
+
function parseDateInput(value) {
|
|
684
|
+
if (typeof value !== "string") return void 0;
|
|
685
|
+
const numeric = Number(value);
|
|
686
|
+
if (Number.isFinite(numeric)) return numeric;
|
|
687
|
+
const timestamp = Date.parse(value);
|
|
688
|
+
if (Number.isNaN(timestamp)) return void 0;
|
|
689
|
+
return timestamp;
|
|
690
|
+
}
|
|
691
|
+
function isSortKey(value) {
|
|
692
|
+
return value === "timestamp" || value === "path" || value === "duration";
|
|
693
|
+
}
|
|
694
|
+
function isSortDir(value) {
|
|
695
|
+
return value === "asc" || value === "desc";
|
|
696
|
+
}
|
|
697
|
+
function normalizeWebhookPage(page) {
|
|
698
|
+
if (!page || typeof page !== "object") return { items: [] };
|
|
699
|
+
return {
|
|
700
|
+
items: Array.isArray(page.items) ? page.items : [],
|
|
701
|
+
nextCursor: page.nextCursor,
|
|
702
|
+
prevCursor: page.prevCursor,
|
|
703
|
+
total: page.total
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
function applyHistoryQuery(items, query, hardLimit) {
|
|
707
|
+
const fromTs = typeof query.from === "number" ? query.from : void 0;
|
|
708
|
+
const toTs = typeof query.to === "number" ? query.to : void 0;
|
|
709
|
+
const methods = query.methods ? new Set(query.methods.map((m) => m.toUpperCase())) : void 0;
|
|
710
|
+
const pathNeedle = (query.path || "").toLowerCase();
|
|
711
|
+
const textNeedle = (query.text || "").toLowerCase();
|
|
712
|
+
const statusNeedle = (query.status || "").trim();
|
|
713
|
+
const filtered = (Array.isArray(items) ? items : []).filter((entry) => {
|
|
714
|
+
if (methods?.size && !methods.has(String(entry.method || "").toUpperCase())) return false;
|
|
715
|
+
if (pathNeedle && !String(entry.path || "").toLowerCase().includes(pathNeedle)) return false;
|
|
716
|
+
if (statusNeedle) {
|
|
717
|
+
const statusStr = entry.status !== void 0 && entry.status !== null ? String(entry.status) : "ERR";
|
|
718
|
+
if (!statusStr.startsWith(statusNeedle)) return false;
|
|
719
|
+
}
|
|
720
|
+
if (Number.isFinite(fromTs) && entry.timestamp < fromTs) return false;
|
|
721
|
+
if (Number.isFinite(toTs) && entry.timestamp > toTs) return false;
|
|
722
|
+
if (textNeedle) {
|
|
723
|
+
const haystack = [
|
|
724
|
+
entry.path,
|
|
725
|
+
entry.fullUrl,
|
|
726
|
+
entry.body,
|
|
727
|
+
entry.output,
|
|
728
|
+
entry.error,
|
|
729
|
+
JSON.stringify(entry.params || {}),
|
|
730
|
+
JSON.stringify(entry.query || {})
|
|
731
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
732
|
+
if (!haystack.includes(textNeedle)) return false;
|
|
733
|
+
}
|
|
734
|
+
return true;
|
|
735
|
+
});
|
|
736
|
+
const sortBy = query.sortBy || "timestamp";
|
|
737
|
+
const direction = query.sortDir === "asc" ? 1 : -1;
|
|
738
|
+
const sorted = filtered.slice().sort((a, b) => {
|
|
739
|
+
let delta = 0;
|
|
740
|
+
if (sortBy === "path") {
|
|
741
|
+
delta = String(a.path || "").localeCompare(String(b.path || ""));
|
|
742
|
+
} else if (sortBy === "duration") {
|
|
743
|
+
delta = (a.durationMs || 0) - (b.durationMs || 0);
|
|
744
|
+
} else {
|
|
745
|
+
delta = (a.timestamp || 0) - (b.timestamp || 0);
|
|
746
|
+
}
|
|
747
|
+
return delta * direction;
|
|
748
|
+
});
|
|
749
|
+
return paginateItems(sorted, query.cursor, query.limit, hardLimit, 25);
|
|
750
|
+
}
|
|
751
|
+
function applyLogsQuery(items, query, hardLimit) {
|
|
752
|
+
const fromTs = typeof query.from === "number" ? query.from : void 0;
|
|
753
|
+
const toTs = typeof query.to === "number" ? query.to : void 0;
|
|
754
|
+
const textNeedle = (query.text || "").toLowerCase();
|
|
755
|
+
const requestIdNeedle = (query.requestId || "").toLowerCase();
|
|
756
|
+
const types = query.types ? new Set(query.types) : void 0;
|
|
757
|
+
const tags = query.tags ? new Set(query.tags) : void 0;
|
|
758
|
+
const filtered = (Array.isArray(items) ? items : []).filter((entry) => {
|
|
759
|
+
if (types?.size && !types.has(entry.type)) return false;
|
|
760
|
+
const entryTags = Array.isArray(entry.tags) ? entry.tags : [];
|
|
761
|
+
if (tags?.size && !entryTags.some((tag) => tags.has(tag))) return false;
|
|
762
|
+
if (requestIdNeedle && !(entry.requestId || "").toLowerCase().includes(requestIdNeedle))
|
|
763
|
+
return false;
|
|
764
|
+
if (Number.isFinite(fromTs) && entry.timestamp < fromTs) return false;
|
|
765
|
+
if (Number.isFinite(toTs) && entry.timestamp > toTs) return false;
|
|
766
|
+
if (textNeedle) {
|
|
767
|
+
const haystack = [
|
|
768
|
+
entry.message,
|
|
769
|
+
entry.requestId,
|
|
770
|
+
entryTags.join(" "),
|
|
771
|
+
typeof entry.metadata === "string" ? entry.metadata : JSON.stringify(entry.metadata || "")
|
|
772
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
773
|
+
if (!haystack.includes(textNeedle)) return false;
|
|
774
|
+
}
|
|
775
|
+
return true;
|
|
776
|
+
});
|
|
777
|
+
const direction = query.sortDir === "asc" ? 1 : -1;
|
|
778
|
+
const sorted = filtered.slice().sort((a, b) => (a.timestamp - b.timestamp) * direction);
|
|
779
|
+
return paginateItems(sorted, query.cursor, query.limit, hardLimit, 50);
|
|
780
|
+
}
|
|
781
|
+
function paginateItems(items, cursor, limit, hardLimit, fallbackLimit) {
|
|
782
|
+
const safeLimit = clampLimit(limit, hardLimit, fallbackLimit);
|
|
783
|
+
const start = parseCursor(cursor);
|
|
784
|
+
const end = start + safeLimit;
|
|
785
|
+
const slice = items.slice(start, end);
|
|
786
|
+
const nextCursor = end < items.length ? String(end) : void 0;
|
|
787
|
+
const prevCursor = start > 0 ? String(Math.max(start - safeLimit, 0)) : void 0;
|
|
788
|
+
return {
|
|
789
|
+
items: slice,
|
|
790
|
+
nextCursor,
|
|
791
|
+
prevCursor,
|
|
792
|
+
total: items.length
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
function clampLimit(value, hardLimit, fallback) {
|
|
796
|
+
if (!Number.isFinite(value)) return Math.min(hardLimit, fallback);
|
|
797
|
+
const safe = Math.max(1, Math.min(hardLimit, value));
|
|
798
|
+
return safe;
|
|
799
|
+
}
|
|
800
|
+
function parseCursor(cursor) {
|
|
801
|
+
const num = Number(cursor);
|
|
802
|
+
if (Number.isFinite(num) && num >= 0) return Math.floor(num);
|
|
803
|
+
return 0;
|
|
804
|
+
}
|
|
805
|
+
function normalizeHistorySeeds(seeds, hardLimit) {
|
|
806
|
+
if (!Array.isArray(seeds)) return [];
|
|
807
|
+
return seeds.slice(0, hardLimit).map((entry, idx) => ({
|
|
808
|
+
id: entry.id || (0, import_crypto.randomBytes)(8).toString("hex"),
|
|
809
|
+
requestId: typeof entry.requestId === "string" ? entry.requestId : void 0,
|
|
810
|
+
timestamp: entry.timestamp ?? Date.now() - idx * 1e3,
|
|
811
|
+
method: entry.method || "GET",
|
|
812
|
+
path: entry.path || "/",
|
|
813
|
+
fullUrl: entry.fullUrl || entry.path || "/",
|
|
814
|
+
params: entry.params || {},
|
|
815
|
+
query: entry.query || {},
|
|
816
|
+
body: entry.body || "",
|
|
817
|
+
output: entry.output || "",
|
|
818
|
+
status: entry.status,
|
|
819
|
+
durationMs: entry.durationMs ?? 0,
|
|
820
|
+
error: entry.error
|
|
821
|
+
})).filter((entry) => entry.method && entry.path);
|
|
822
|
+
}
|
|
823
|
+
function normalizeLogSeeds(seeds, hardLimit) {
|
|
824
|
+
if (!Array.isArray(seeds)) return [];
|
|
825
|
+
return seeds.slice(0, hardLimit).map((entry, idx) => ({
|
|
826
|
+
id: entry.id || (0, import_crypto.randomBytes)(8).toString("hex"),
|
|
827
|
+
type: entry.type || "info",
|
|
828
|
+
message: entry.message || "",
|
|
829
|
+
timestamp: entry.timestamp ?? Date.now() - idx * 1e3,
|
|
830
|
+
requestId: entry.requestId,
|
|
831
|
+
tags: Array.isArray(entry.tags) ? entry.tags : [],
|
|
832
|
+
metadata: entry.metadata
|
|
833
|
+
})).filter((entry) => entry.message);
|
|
834
|
+
}
|
|
835
|
+
function coerceQueryRecord(query) {
|
|
836
|
+
if (!query || typeof query !== "object") return {};
|
|
837
|
+
return Object.fromEntries(
|
|
838
|
+
Object.entries(query).map(([key, value]) => [key, coerceValue(value)])
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
function coercePayload(body) {
|
|
842
|
+
if (body === void 0 || body === null) return "";
|
|
843
|
+
if (typeof body === "string") return body;
|
|
844
|
+
try {
|
|
845
|
+
return JSON.stringify(body);
|
|
846
|
+
} catch {
|
|
847
|
+
return String(body);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
function coerceValue(value) {
|
|
851
|
+
if (value === void 0 || value === null) return "";
|
|
852
|
+
if (Array.isArray(value)) return value.map(coerceValue).join(",");
|
|
853
|
+
if (typeof value === "object") {
|
|
854
|
+
try {
|
|
855
|
+
return JSON.stringify(value);
|
|
856
|
+
} catch {
|
|
857
|
+
return String(value);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return String(value);
|
|
861
|
+
}
|
|
402
862
|
// Annotate the CommonJS export names for ESM import in node:
|
|
403
863
|
0 && (module.exports = {
|
|
404
864
|
mountRRRoutesDocs,
|