@dudousxd/nestjs-telescope
Version:
Laravel Telescope-style observability console for NestJS — core: watchers, recorder, correlation, SQLite store, headless API.
839 lines • 37 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
// packages/core/src/nest/telescope.controller.ts
import { BadGatewayException, BadRequestException, Body, Controller, Delete, ForbiddenException, Get, HttpCode, Inject, MethodNotAllowedException, NotFoundException, Param, Post, Query, Req, Res, ServiceUnavailableException, UseGuards, } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { durationToMs } from '../config/parse-duration.js';
import { EntryType } from '../entry/entry.js';
import { ExtensionRegistry } from '../extension/registry.js';
import { collectEntriesInWindow } from '../metrics/collect-window.js';
import { QueueMetricsService } from '../metrics/queue-metrics.service.js';
import { ServerStatsService, } from '../metrics/server-stats.service.js';
import { StatsService } from '../metrics/stats.service.js';
import { TimeseriesService } from '../metrics/timeseries.service.js';
import { TracesService } from '../metrics/traces.service.js';
import { CPU_PROFILE_ENTRY_TYPE, ProfilerService } from '../profiling/profiler.service.js';
import { PulseService } from '../pulse/pulse.service.js';
import { QUEUE_ACTIONS, isQueueState, } from '../queue/queue-manager.js';
import { QueueManagerRegistry } from '../queue/queue-manager.registry.js';
import { ScheduleManagerRegistry } from '../schedule/schedule-manager.registry.js';
import { createExtensionContext } from './extension-context.factory.js';
import { replayRequest } from './request-replay.js';
import { TelescopeActionGuard } from './telescope-action.guard.js';
import { TelescopePruner } from './telescope-pruner.service.js';
import { TelescopeGuard } from './telescope.guard.js';
import { TELESCOPE_CONFIG, TELESCOPE_EXTENSIONS, TELESCOPE_OPTIONS, TELESCOPE_STORAGE, } from './telescope.options.js';
import { TelescopeService } from './telescope.service.js';
/** Maps a queue action to the optional QueueManager method that implements it. */
const ACTION_METHOD = {
retry: 'retry',
remove: 'remove',
promote: 'promote',
'retry-all': 'retryAll',
redrive: 'redrive',
enqueue: 'enqueue',
};
/**
* The per-job actions (those whose QueueManager method has the
* `(queue, id) => Promise<void>` shape). `jobAction` dispatches via
* {@link ACTION_METHOD}, so this subset is the route's allow-list.
*/
const JOB_ACTIONS = ['retry', 'remove', 'promote'];
const isJobAction = (action) => JOB_ACTIONS.includes(action);
/** Type guard: a query entry carries a non-empty SQL string to explain. */
function isQueryContentWithSql(content) {
return (typeof content === 'object' &&
content !== null &&
'sql' in content &&
typeof content.sql === 'string' &&
content.sql !== '');
}
/**
* Parse the `window` query param (default `1h`) into milliseconds, or throw 400.
* Shared by every windowed metrics route (queues/pulse/timeseries/traces/stats).
*/
function parseWindowMs(window) {
let windowMs;
try {
windowMs = durationToMs(window ?? '1h');
}
catch {
throw new BadRequestException(`Invalid window: ${window}`);
}
if (!Number.isFinite(windowMs) || windowMs <= 0) {
throw new BadRequestException(`Window must be positive: ${window}`);
}
return windowMs;
}
let TelescopeController = class TelescopeController {
storage;
service;
queueMetrics;
timeseriesService;
tracesService;
statsService;
serverStats;
pulse;
profiler;
queueManagers;
scheduleManagers;
options;
extensions;
extConfig;
pruner;
moduleRef;
constructor(storage, service, queueMetrics, timeseriesService, tracesService, statsService, serverStats, pulse, profiler, queueManagers, scheduleManagers, options, extensions, extConfig, pruner, moduleRef) {
this.storage = storage;
this.service = service;
this.queueMetrics = queueMetrics;
this.timeseriesService = timeseriesService;
this.tracesService = tracesService;
this.statsService = statsService;
this.serverStats = serverStats;
this.pulse = pulse;
this.profiler = profiler;
this.queueManagers = queueManagers;
this.scheduleManagers = scheduleManagers;
this.options = options;
this.extensions = extensions;
this.extConfig = extConfig;
this.pruner = pruner;
this.moduleRef = moduleRef;
}
list(query) {
const entryQuery = {
...(query.type !== undefined ? { type: query.type } : {}),
...(query.tag !== undefined ? { tag: query.tag } : {}),
...(query.familyHash !== undefined ? { familyHash: query.familyHash } : {}),
...(query.batchId !== undefined ? { batchId: query.batchId } : {}),
...(query.traceId !== undefined ? { traceId: query.traceId } : {}),
...(query.search !== undefined && query.search !== '' ? { search: query.search } : {}),
...(query.cursor !== undefined ? { cursor: query.cursor } : {}),
...(query.limit !== undefined && Number.isFinite(Number(query.limit))
? { limit: Number(query.limit) }
: {}),
};
return this.storage.get(entryQuery);
}
show(id) {
return this.storage.find(id);
}
// Request REPLAY. Re-issues a captured request entry against the local server
// and reports the outcome. It is a MUTATION (it actually hits the app, which
// may write), so — like `prune` — it stays behind the default-deny
// authorizeAction gate rather than the read guard. The queue-shaped
// TelescopeActionGuard can't validate it (no driver/queue/action params), so we
// enforce the same default-deny here directly: no `authorizeAction` → 403.
async replay(id, request) {
if (!this.options.authorizeAction) {
throw new ForbiddenException('Mutations are disabled (no authorizeAction configured).');
}
const entry = await this.storage.find(id);
if (entry === null || entry.type !== EntryType.Request) {
throw new NotFoundException('No request entry with that id.');
}
return replayRequest(entry.content, request);
}
batch(id) {
return this.storage.batch(id);
}
tags(prefix) {
return this.storage.tags(prefix);
}
queues(window) {
const windowMs = parseWindowMs(window);
return this.queueMetrics.getQueueMetrics(windowMs);
}
pulseHealth(window) {
const windowMs = parseWindowMs(window);
return this.pulse.getHealth(windowMs);
}
timeseries(window, buckets, type, tag) {
const windowMs = parseWindowMs(window);
const bucketCount = buckets !== undefined ? Number(buckets) : undefined;
return this.timeseriesService.getTimeseries({
windowMs,
...(bucketCount !== undefined && Number.isFinite(bucketCount)
? { buckets: bucketCount }
: {}),
...(type !== undefined ? { type } : {}),
...(tag !== undefined ? { tag } : {}),
});
}
traces(window, limit) {
const windowMs = parseWindowMs(window);
const limitCount = limit !== undefined ? Number(limit) : undefined;
return this.tracesService.getTraces({
windowMs,
...(limitCount !== undefined && Number.isFinite(limitCount) ? { limit: limitCount } : {}),
});
}
// Nested span waterfall for ONE trace. Returns 404 when the trace has no
// entries (unknown / pruned) so the dashboard can render an empty state.
async waterfall(traceId) {
const waterfall = await this.tracesService.getWaterfall(traceId);
if (waterfall === null) {
throw new NotFoundException(`No entries for trace ${traceId}.`);
}
return waterfall;
}
stats(type, window, buckets) {
if (type === undefined || type === '') {
throw new BadRequestException('Query parameter "type" is required.');
}
const windowMs = parseWindowMs(window);
const bucketCount = buckets !== undefined ? Number(buckets) : undefined;
return this.statsService.getStats({
type,
windowMs,
...(bucketCount !== undefined && Number.isFinite(bucketCount)
? { buckets: bucketCount }
: {}),
});
}
async liveQueues() {
const managers = this.queueManagers.all();
const all = await Promise.all(managers.map((m) => m.listQueues()));
const actionsByDriver = {};
for (const manager of managers) {
actionsByDriver[manager.driver] = QUEUE_ACTIONS.filter((action) => typeof manager[ACTION_METHOD[action]] === 'function');
}
return {
queues: all.flat(),
capabilities: {
mutationsEnabled: Boolean(this.options.authorizeAction),
actionsByDriver,
},
};
}
async liveSchedules() {
const ctx = this.scheduleManagers.context();
const all = await Promise.all(this.scheduleManagers.all().map((m) => m.listTasks(ctx)));
return { tasks: all.flat() };
}
liveCounts(driver, queue) {
return this.requireManager(driver).counts(queue);
}
liveJobs(driver, queue, state, cursor, limit) {
if (!isQueueState(state))
throw new BadRequestException(`Invalid state: ${state}`);
const page = {
...(cursor !== undefined ? { cursor } : {}),
...(limit !== undefined && Number.isFinite(Number(limit)) ? { limit: Number(limit) } : {}),
};
return this.requireManager(driver).listJobs(queue, state, page);
}
liveJob(driver, queue, id) {
return this.requireManager(driver).getJob(queue, id);
}
async jobAction(driver, queue, id, action) {
const manager = this.requireManager(driver);
if (!isJobAction(action))
throw new BadRequestException(`Invalid job action: ${action}`);
await this.callAction(manager[ACTION_METHOD[action]], manager, queue, id, action);
return { ok: true };
}
async queueAction(driver, queue, action, state) {
const manager = this.requireManager(driver);
if (action === 'retry-all') {
if (!isQueueState(state))
throw new BadRequestException(`Invalid state: ${state}`);
if (!manager.retryAll)
throw new MethodNotAllowedException(`Driver ${driver} cannot retry-all`);
return { ok: true, count: await manager.retryAll(queue, state) };
}
if (action === 'redrive') {
if (!manager.redrive)
throw new MethodNotAllowedException(`Driver ${driver} cannot redrive`);
return { ok: true, count: await manager.redrive(queue) };
}
throw new BadRequestException(`Invalid queue action: ${action}`);
}
// Enqueue carries a JSON body (name + payload), so it lives on its own route
// rather than under `:action`. Still gated by the same default-deny guard.
async enqueue(driver, queue, body) {
if (body === undefined || body === null || !('payload' in body)) {
throw new BadRequestException('Body must include a "payload".');
}
const manager = this.requireManager(driver);
if (!manager.enqueue)
throw new NotFoundException(`Driver ${driver} cannot enqueue`);
const opts = body.name !== undefined ? { name: body.name } : {};
return manager.enqueue(queue, body.payload, opts, this.queueManagers.context());
}
async callAction(fn, manager, queue, id, action) {
if (!fn)
throw new MethodNotAllowedException(`Driver ${manager.driver} cannot ${action}`);
await fn.call(manager, queue, id);
}
requireManager(driver) {
const manager = this.queueManagers.get(driver);
if (!manager)
throw new NotFoundException(`Unknown queue driver: ${driver}`);
return manager;
}
meta() {
return this.service.getMeta();
}
serverStatsSnapshot() {
return this.serverStats.getStats();
}
// CPU/mem history ring buffer for the dashboard's resource-history card.
serverStatsHistory() {
return this.serverStats.getHistory();
}
health() {
return this.service.getHealth();
}
// ── Extension data providers ────────────────────────────────────────────────
// Read-shaped: the UI calls this per panel to fetch a named provider's data.
// Sits behind the class-level read guard. 404 for an unknown provider; 502
// when the provider throws (its message is surfaced so panel authors can see
// why). Query params arrive as strings and are passed through verbatim.
async extData(ext, provider, query) {
const found = this.extensions.findProvider(provider);
// The `:ext` segment must name the extension that actually owns the provider —
// a mismatch is treated as not-found so the URL namespace can't be spoofed.
if (!found || this.extensions.providerOwner(provider) !== ext) {
throw new NotFoundException(`Unknown data provider "${provider}".`);
}
const ctx = createExtensionContext(this.moduleRef, this.extConfig);
try {
return await found.resolve(query, ctx);
}
catch (error) {
throw new BadGatewayException(error.message);
}
}
// ── Retention / prune ──────────────────────────────────────────────────────
retention() {
const prune = this.options.prune;
// entryCount / oldestCreatedAt require an ascending scan or a count the
// StorageProvider SPI does not expose cheaply. We deliberately do NOT scan,
// so both stay null until/unless the SPI grows a cheap accessor.
return {
retention: prune
? {
afterMs: durationToMs(prune.after),
keepLast: prune.keepLast ?? null,
}
: null,
entryCount: null,
oldestCreatedAt: null,
pruneSupported: true,
};
}
// Prune-run activity / retention status for the Prunes screen. Read-shaped —
// sits behind the class-level read guard like the other GETs. The runs come
// from the pruner's in-memory ring (PER-POD), the config from the resolved
// config, and nextRunAt is the pruner's predicted next scheduled cycle.
prunes() {
const prune = this.extConfig.prune;
const nextRunAtMs = this.pruner.getNextRunAtMs();
return {
runs: this.pruner.getRuns(),
config: prune
? {
afterMs: prune.afterMs,
intervalMs: prune.intervalMs,
keepLast: prune.keepLast ?? null,
...(Object.keys(prune.perTypeMs).length > 0 ? { perType: prune.perTypeMs } : {}),
}
: null,
nextRunAt: nextRunAtMs !== null ? new Date(nextRunAtMs).toISOString() : null,
};
}
// Prune is a MUTATION (deletes entries), so it stays behind the default-deny
// authorizeAction gate. The queue-shaped TelescopeActionGuard can't validate
// it (no driver/queue/action params), so we enforce the same default-deny
// here directly: no `authorizeAction` configured → 403. The cycle runs through
// the pruner so it is recorded as a `manual` run (and honours per-type +
// archive retention identically to the scheduled cycle).
async prune() {
if (!this.options.authorizeAction) {
throw new ForbiddenException('Mutations are disabled (no authorizeAction configured).');
}
if (!this.options.prune) {
throw new BadRequestException('No `prune` retention window is configured.');
}
return { pruned: await this.pruner.pruneNow() };
}
// ── Query EXPLAIN ──────────────────────────────────────────────────────────
// Read-shaped (it returns a plan, mutates no Telescope state) so it sits behind
// the normal read guard. NOTE: the host hook runs arbitrary `EXPLAIN <sql>`
// against its database — hosts MUST scope that connection read-only.
async explain(body) {
const explainQuery = this.options.explainQuery;
if (!explainQuery) {
throw new NotFoundException('Query EXPLAIN is not configured.');
}
if (body === undefined || body === null || typeof body.entryId !== 'string') {
throw new BadRequestException('Body must include an "entryId".');
}
const entry = await this.storage.find(body.entryId);
if (!entry || entry.type !== EntryType.Query || !isQueryContentWithSql(entry.content)) {
throw new NotFoundException('No query entry with SQL for that id.');
}
try {
// Pass the SQL/bindings EXACTLY as captured — plans carry no user data.
const plan = await explainQuery(entry.content.sql, entry.content.bindings ?? []);
return { plan };
}
catch (error) {
const message = error instanceof Error ? error.message : 'EXPLAIN failed.';
throw new ServiceUnavailableException({ message });
}
}
// ── AI exception diagnosis ──────────────────────────────────────────────────
// Read-shaped ANALYSIS (it produces a markdown explanation, mutates no
// Telescope state and runs no destructive action), so — like `explain` — it
// sits behind the normal dashboard read guard, NOT the default-deny
// authorizeAction mutation gate. 404 when AI isn't configured or the entry
// isn't an exception; 502 when the diagnoser fails (a safe, generic message —
// the model's raw error is never surfaced to the dashboard).
async diagnose(id, force) {
const coordinator = this.service.diagnosisCoordinator;
if (coordinator === null) {
throw new NotFoundException('AI diagnosis is not configured.');
}
const entry = await this.storage.find(id);
if (entry === null ||
(entry.type !== EntryType.Exception && entry.type !== EntryType.ClientException)) {
throw new NotFoundException('No exception entry with that id.');
}
const occurrences = await this.countExceptionFamily(entry.type, entry.familyHash);
try {
return await coordinator.diagnose(entry, occurrences, force === 'true');
}
catch {
// The diagnoser rejected (timeout / model error). Surface a generic 502;
// the raw model error may carry provider internals, so it's never leaked.
throw new ServiceUnavailableException({ message: 'AI diagnosis failed.' });
}
}
// Read-only companion to the POST above: returns the ALREADY-cached diagnosis
// for this entry's family, if one exists, WITHOUT ever computing a new one (no
// model call, no token cost). The detail page fetches this on open so an
// auto-mode (or previously on-demand) diagnosis shows immediately instead of a
// bare "Diagnose with AI" button. Same guards/404 semantics as the POST:
// - 404 when AI isn't configured or the entry isn't an exception;
// - 200 `{ markdown, cached: true }` when a diagnosis is cached;
// - 204 (empty) when none is cached yet (the family hasn't been diagnosed).
// The 204 — not a 200-with-null — keeps "nothing cached" unambiguous on the
// client and never tempts a reader into treating a null body as a result.
async cachedDiagnosis(id, res) {
const coordinator = this.service.diagnosisCoordinator;
if (coordinator === null) {
throw new NotFoundException('AI diagnosis is not configured.');
}
const entry = await this.storage.find(id);
if (entry === null ||
(entry.type !== EntryType.Exception && entry.type !== EntryType.ClientException)) {
throw new NotFoundException('No exception entry with that id.');
}
const markdown = coordinator.peekCached(entry);
if (markdown === null) {
// No diagnosis cached for this family yet. 204 No Content — never invoke
// the diagnoser from a GET (a read must stay free and side-effect-free).
// `@Res` is `unknown` to keep express types out of the public signature
// (same convention as the auth controller); narrow before setting status.
setResponseStatus(res, 204);
return undefined;
}
return { markdown, cached: true };
}
/** Count entries of this exception family in the trailing 24h (>= 1). */
async countExceptionFamily(type, familyHash) {
if (familyHash === null)
return 1;
const after = new Date(Date.now() - durationToMs('24h'));
const result = await collectEntriesInWindow(this.storage, { type, familyHash, after, omitContent: true }, { scanCap: 10_000 });
return Math.max(1, result.entries.length);
}
// ── CPU profiling (flamegraphs) ─────────────────────────────────────────────
/**
* Profiler status for the dashboard's Profiles tab: whether the feature is
* enabled, the sample rate, and current capture activity. Read-shaped — sits
* behind the normal read guard. When profiling is disabled the tab shows an
* "enable `profiling`" empty state from this payload.
*/
profilesStatus() {
return this.profiler.status();
}
/**
* List captured CPU profiles, newest-first, WITHOUT their (potentially large)
* frame trees — `omitContent` keeps the list cheap; the tree is fetched per
* profile via {@link profile}. Read-shaped.
*/
async profiles(limit) {
return this.storage.get({
type: CPU_PROFILE_ENTRY_TYPE,
omitContent: true,
...(limit !== undefined && Number.isFinite(Number(limit)) ? { limit: Number(limit) } : {}),
});
}
/**
* Fetch ONE profile's full frame tree (the flamegraph payload). 404 when the
* id is unknown or not a cpu_profile entry. Read-shaped.
*/
async profile(id) {
const entry = await this.storage.find(id);
if (entry === null || entry.type !== CPU_PROFILE_ENTRY_TYPE) {
throw new NotFoundException('No CPU profile with that id.');
}
return entry;
}
/**
* Arm an on-demand capture of the next N requests (optionally only those whose
* normalized route matches `label`, e.g. "GET /users/:id"). A MUTATION-shaped
* trigger — it incurs real profiling overhead — so it stays behind the same
* default-deny `authorizeAction` gate as prune/replay. 400 when profiling is
* disabled (so the dashboard can explain why nothing happens).
*/
arm(body) {
if (!this.options.authorizeAction) {
throw new ForbiddenException('Mutations are disabled (no authorizeAction configured).');
}
if (!this.profiler.status().enabled) {
throw new BadRequestException('CPU profiling is disabled (set `profiling.enabled`).');
}
const count = body?.count !== undefined ? Number(body.count) : 1;
if (!Number.isFinite(count) || count <= 0) {
throw new BadRequestException('`count` must be a positive number.');
}
return this.profiler.arm({
count,
...(typeof body?.label === 'string' && body.label !== '' ? { label: body.label } : {}),
});
}
async clear() {
await this.storage.clear();
return { cleared: true };
}
};
__decorate([
Get('entries'),
__param(0, Query()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "list", null);
__decorate([
Get('entries/:id'),
__param(0, Param('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "show", null);
__decorate([
Get('entries/:id/replay'),
__param(0, Param('id')),
__param(1, Req()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, Object]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "replay", null);
__decorate([
Get('batches/:id'),
__param(0, Param('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "batch", null);
__decorate([
Get('tags'),
__param(0, Query('prefix')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "tags", null);
__decorate([
Get('queues'),
__param(0, Query('window')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "queues", null);
__decorate([
Get('pulse'),
__param(0, Query('window')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "pulseHealth", null);
__decorate([
Get('timeseries'),
__param(0, Query('window')),
__param(1, Query('buckets')),
__param(2, Query('type')),
__param(3, Query('tag')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String, String, String]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "timeseries", null);
__decorate([
Get('traces'),
__param(0, Query('window')),
__param(1, Query('limit')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "traces", null);
__decorate([
Get('traces/:traceId/waterfall'),
__param(0, Param('traceId')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "waterfall", null);
__decorate([
Get('stats'),
__param(0, Query('type')),
__param(1, Query('window')),
__param(2, Query('buckets')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String, String]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "stats", null);
__decorate([
Get('queues/live'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "liveQueues", null);
__decorate([
Get('schedules/live'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "liveSchedules", null);
__decorate([
Get('queues/live/:driver/:queue/counts'),
__param(0, Param('driver')),
__param(1, Param('queue')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "liveCounts", null);
__decorate([
Get('queues/live/:driver/:queue/jobs'),
__param(0, Param('driver')),
__param(1, Param('queue')),
__param(2, Query('state')),
__param(3, Query('cursor')),
__param(4, Query('limit')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String, String, String, String]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "liveJobs", null);
__decorate([
Get('queues/live/:driver/:queue/jobs/:id'),
__param(0, Param('driver')),
__param(1, Param('queue')),
__param(2, Param('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String, String]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "liveJob", null);
__decorate([
Post('queues/live/:driver/:queue/jobs/:id/:action'),
HttpCode(200),
UseGuards(TelescopeActionGuard),
__param(0, Param('driver')),
__param(1, Param('queue')),
__param(2, Param('id')),
__param(3, Param('action')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String, String, String]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "jobAction", null);
__decorate([
Post('queues/live/:driver/:queue/actions/:action'),
HttpCode(200),
UseGuards(TelescopeActionGuard),
__param(0, Param('driver')),
__param(1, Param('queue')),
__param(2, Param('action')),
__param(3, Query('state')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String, String, String]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "queueAction", null);
__decorate([
Post('queues/live/:driver/:queue/enqueue'),
HttpCode(200),
UseGuards(TelescopeActionGuard),
__param(0, Param('driver')),
__param(1, Param('queue')),
__param(2, Body()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String, Object]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "enqueue", null);
__decorate([
Get('meta'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "meta", null);
__decorate([
Get('server-stats'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Object)
], TelescopeController.prototype, "serverStatsSnapshot", null);
__decorate([
Get('server-stats/history'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Object)
], TelescopeController.prototype, "serverStatsHistory", null);
__decorate([
Get('health'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Object)
], TelescopeController.prototype, "health", null);
__decorate([
Get('ext/:ext/data/:provider'),
__param(0, Param('ext')),
__param(1, Param('provider')),
__param(2, Query()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String, Object]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "extData", null);
__decorate([
Get('retention'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Object)
], TelescopeController.prototype, "retention", null);
__decorate([
Get('prunes'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Object)
], TelescopeController.prototype, "prunes", null);
__decorate([
Post('retention/prune'),
HttpCode(200),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "prune", null);
__decorate([
Post('queries/explain'),
HttpCode(200),
__param(0, Body()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "explain", null);
__decorate([
Post('exceptions/:id/diagnose'),
HttpCode(200),
__param(0, Param('id')),
__param(1, Query('force')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "diagnose", null);
__decorate([
Get('exceptions/:id/diagnosis'),
__param(0, Param('id')),
__param(1, Res({ passthrough: true })),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, Object]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "cachedDiagnosis", null);
__decorate([
Get('profiles/status'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], TelescopeController.prototype, "profilesStatus", null);
__decorate([
Get('profiles'),
__param(0, Query('limit')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "profiles", null);
__decorate([
Get('profiles/:id'),
__param(0, Param('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "profile", null);
__decorate([
Post('profiles/arm'),
HttpCode(200),
__param(0, Body()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Object)
], TelescopeController.prototype, "arm", null);
__decorate([
Delete('entries'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], TelescopeController.prototype, "clear", null);
TelescopeController = __decorate([
UseGuards(TelescopeGuard),
Controller('telescope/api'),
__param(0, Inject(TELESCOPE_STORAGE)),
__param(1, Inject(TelescopeService)),
__param(2, Inject(QueueMetricsService)),
__param(3, Inject(TimeseriesService)),
__param(4, Inject(TracesService)),
__param(5, Inject(StatsService)),
__param(6, Inject(ServerStatsService)),
__param(7, Inject(PulseService)),
__param(8, Inject(ProfilerService)),
__param(9, Inject(QueueManagerRegistry)),
__param(10, Inject(ScheduleManagerRegistry)),
__param(11, Inject(TELESCOPE_OPTIONS)),
__param(12, Inject(TELESCOPE_EXTENSIONS)),
__param(13, Inject(TELESCOPE_CONFIG)),
__param(14, Inject(TelescopePruner)),
__metadata("design:paramtypes", [Object, TelescopeService,
QueueMetricsService,
TimeseriesService,
TracesService,
StatsService,
ServerStatsService,
PulseService,
ProfilerService,
QueueManagerRegistry,
ScheduleManagerRegistry, Object, ExtensionRegistry, Object, TelescopePruner,
ModuleRef])
], TelescopeController);
export { TelescopeController };
/**
* Set an HTTP status on an express-like response without importing express into
* the controller's public signatures (we type `@Res` as `unknown`, matching the
* auth controller's convention). Narrows via a structural check rather than a
* cast so the no-unsafe-typing rule holds; a no-op if the object lacks `.status`.
*/
function setResponseStatus(response, status) {
if (response !== null &&
typeof response === 'object' &&
'status' in response &&
typeof response.status === 'function') {
response.status(status);
}
}
//# sourceMappingURL=telescope.controller.js.map