@dudousxd/nestjs-durable-telescope 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +5 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -362,7 +362,7 @@ function durableStateProvider() {
|
|
|
362
362
|
return {
|
|
363
363
|
name: "durable.state",
|
|
364
364
|
async resolve(query, ctx) {
|
|
365
|
-
const store = ctx.moduleRef.get(import_nestjs_durable_core2.
|
|
365
|
+
const store = ctx.moduleRef.get(import_nestjs_durable_core2.STATE_STORE_CANONICAL, {
|
|
366
366
|
strict: false
|
|
367
367
|
});
|
|
368
368
|
const status = query?.status ?? "dead";
|
|
@@ -434,7 +434,7 @@ function durableRecentFailuresProvider() {
|
|
|
434
434
|
return {
|
|
435
435
|
name: "durable.recentFailures",
|
|
436
436
|
async resolve(query, ctx) {
|
|
437
|
-
const store = ctx.moduleRef.get(import_nestjs_durable_core2.
|
|
437
|
+
const store = ctx.moduleRef.get(import_nestjs_durable_core2.STATE_STORE_CANONICAL, {
|
|
438
438
|
strict: false
|
|
439
439
|
});
|
|
440
440
|
const limit = Math.min(200, Math.max(10, Number(query?.limit ?? 50)));
|
|
@@ -679,6 +679,7 @@ __name(durableThroughputProvider, "durableThroughputProvider");
|
|
|
679
679
|
var STATE_BREAKDOWN_PALETTE = [
|
|
680
680
|
"#34d399",
|
|
681
681
|
"#fbbf24",
|
|
682
|
+
"#f59e0b",
|
|
682
683
|
"#f87171",
|
|
683
684
|
"#38bdf8",
|
|
684
685
|
"#a78bfa"
|
|
@@ -686,6 +687,7 @@ var STATE_BREAKDOWN_PALETTE = [
|
|
|
686
687
|
var STATE_BREAKDOWN_STATUSES = [
|
|
687
688
|
"running",
|
|
688
689
|
"pending",
|
|
690
|
+
"cancelling",
|
|
689
691
|
"completed",
|
|
690
692
|
"failed",
|
|
691
693
|
"dead"
|
|
@@ -694,7 +696,7 @@ function durableStateBreakdownProvider() {
|
|
|
694
696
|
return {
|
|
695
697
|
name: "durable.stateBreakdown",
|
|
696
698
|
async resolve(_query, ctx) {
|
|
697
|
-
const store = ctx.moduleRef.get(import_nestjs_durable_core2.
|
|
699
|
+
const store = ctx.moduleRef.get(import_nestjs_durable_core2.STATE_STORE_CANONICAL, {
|
|
698
700
|
strict: false
|
|
699
701
|
});
|
|
700
702
|
const counts = await Promise.all(STATE_BREAKDOWN_STATUSES.map((status) => store.listRuns({
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/durable-telescope.watcher.ts","../src/durable-telescope.extension.ts","../src/durable-dashboard.spec-data.ts","../src/durable-data-providers.ts"],"sourcesContent":["export * from './durable-telescope.watcher';\nexport * from './durable-telescope.extension';\nexport * from './durable-data-providers';\nexport * from './durable-dashboard.spec-data';\n","import { type EngineEvent, WorkflowEngine } from '@dudousxd/nestjs-durable-core';\nimport type { Watcher, WatcherContext } from '@dudousxd/nestjs-telescope';\nimport { type Span, SpanStatusCode, context, trace } from '@opentelemetry/api';\n\n/**\n * A Telescope watcher that turns durable-workflow lifecycle events into Telescope entries, so\n * runs and steps (including remote/Python steps) show up alongside the app's requests, queries\n * and jobs in the Telescope UI — under the **Workflows** tab.\n *\n * It also **trace-groups** a run: a root span is opened per run and every entry is recorded inside\n * that span's OTel context (step entries inside a child `step …` span). Telescope's trace-context\n * provider stamps each entry with the active span, so a whole workflow shows up as a single trace\n * on the Traces page — run → steps — with each entry carrying the run's `traceId`. When the host has\n * no OTel SDK the spans are no-ops and entries simply carry no trace (the prior behaviour).\n *\n * Add it to `TelescopeModule.forRoot({ watchers: [new DurableTelescopeWatcher()] })`. It resolves\n * the engine from the (global) durable providers and subscribes to its events.\n */\nexport class DurableTelescopeWatcher implements Watcher {\n readonly type = 'durable';\n /** Open root span per run, so step entries nest under it and share its traceId. */\n private readonly roots = new Map<string, Span>();\n\n register(ctx: WatcherContext): void {\n const engine = ctx.moduleRef.get(WorkflowEngine, { strict: false });\n const tracer = trace.getTracer('@dudousxd/nestjs-durable-telescope');\n\n engine.subscribe((event) => {\n const record = () =>\n ctx.record({\n type: this.type,\n content: {\n event: event.type,\n workflow: event.workflow,\n runId: event.runId,\n seq: event.seq,\n name: event.name,\n kind: event.kind,\n output: event.output,\n error: event.error,\n durationMs: event.durationMs,\n },\n tags: this.tags(event),\n });\n\n // Open the run's root span on first sight; later events nest under it.\n if (event.type === 'run.started') {\n this.roots.set(\n event.runId,\n tracer.startSpan(`workflow ${event.workflow ?? '?'}`, {\n attributes: { 'durable.run_id': event.runId, 'durable.workflow': event.workflow },\n }),\n );\n }\n const root = this.roots.get(event.runId);\n const base = root ? trace.setSpan(context.active(), root) : context.active();\n\n // A step/signal/sleep event → give it its own child span so it's a distinct node in the\n // trace; run-level events record directly on the root. Either way the active span at record\n // time carries the run's traceId, so all entries group into one trace.\n if (event.name != null && event.seq != null) {\n const step = tracer.startSpan(\n `step ${event.name}`,\n {\n attributes: {\n 'durable.run_id': event.runId,\n 'durable.step.seq': event.seq,\n 'durable.step.kind': event.kind,\n },\n },\n base,\n );\n context.with(trace.setSpan(base, step), record);\n step.end();\n } else {\n context.with(base, record);\n }\n\n // Close the run's root span when it leaves the running state.\n if (event.type === 'run.completed' || event.type === 'run.suspended') {\n root?.end();\n this.roots.delete(event.runId);\n } else if (event.type === 'run.failed') {\n const message = event.error?.message;\n root?.setStatus(\n message !== undefined\n ? { code: SpanStatusCode.ERROR, message }\n : { code: SpanStatusCode.ERROR },\n );\n root?.end();\n this.roots.delete(event.runId);\n }\n });\n }\n\n private tags(event: EngineEvent): string[] {\n const tags = ['durable', event.type, `run:${event.runId}`];\n if (event.workflow) tags.push(`workflow:${event.workflow}`);\n if (event.kind) tags.push(`kind:${event.kind}`);\n if (event.type === 'run.failed') tags.push('failed');\n return tags;\n }\n}\n","import { defineTelescopeExtension } from '@dudousxd/nestjs-telescope';\nimport { durableDashboard } from './durable-dashboard.spec-data';\nimport {\n durableDurationProvider,\n durableRecentFailuresProvider,\n durableRunsOverTimeProvider,\n durableStateBreakdownProvider,\n durableStateProvider,\n durableSuccessRateProvider,\n durableThroughputProvider,\n durableTimeseriesProvider,\n durableWorkerHealthProvider,\n} from './durable-data-providers';\nimport { DurableTelescopeWatcher } from './durable-telescope.watcher';\n\n/** The first-class Telescope extension for nestjs-durable: watcher + Workflows dashboard. */\nexport function durableTelescopeExtension(\n opts: { runHref?: string; recentFailuresWindowMs?: number } = {},\n) {\n return defineTelescopeExtension({\n name: 'durable',\n watchers: () => [new DurableTelescopeWatcher()],\n entryTypes: () => [{ id: 'durable', label: 'Workflows', dot: 'bg-amber-400' }],\n dashboards: () => [durableDashboard(opts)],\n dataProviders: () => [\n durableStateProvider(),\n durableTimeseriesProvider(),\n durableRecentFailuresProvider(),\n durableWorkerHealthProvider(),\n durableDurationProvider(),\n durableRunsOverTimeProvider(),\n durableSuccessRateProvider(),\n durableThroughputProvider(),\n durableStateBreakdownProvider(),\n ],\n });\n}\n","import type { DashboardSpec } from '@dudousxd/nestjs-telescope';\n\n/**\n * The \"Workflows\" health dashboard — golden-signals sections layout.\n *\n * `runHref` is a URL template for deep-linking a run out to the durable dashboard\n * (e.g. '/durable/runs/{runId}'). `recentFailuresWindowMs` bounds the \"Stuck runs\"\n * table (default 24h) so a healthy system shows an empty table instead of\n * days-old failures; pass `0` to show all.\n */\nexport function durableDashboard(\n opts: { runHref?: string; recentFailuresWindowMs?: number } = {},\n): DashboardSpec {\n const runHref = opts.runHref ?? '/durable/runs/{runId}';\n const windowMs = opts.recentFailuresWindowMs ?? 24 * 60 * 60 * 1000;\n return {\n id: 'durable.workflows',\n label: 'Workflows',\n panels: [],\n sections: [\n {\n title: 'Health',\n cols: 4,\n panels: [\n {\n kind: 'gauge',\n title: 'Success rate',\n data: { provider: 'durable.successRate' },\n max: 1,\n format: 'percent',\n thresholds: { warn: 0.98, bad: 0.95, direction: 'down-bad' },\n },\n {\n kind: 'stat',\n title: 'Duration p95',\n data: { provider: 'durable.duration', query: { metric: 'p95' } },\n format: 'duration',\n spark: false,\n thresholds: { warn: 2000, bad: 5000, direction: 'up-bad' },\n },\n {\n kind: 'stat',\n title: 'Backlog',\n data: { provider: 'durable.state', query: { status: 'pending' } },\n spark: false,\n thresholds: { warn: 50, bad: 200, direction: 'up-bad' },\n },\n {\n kind: 'stat',\n title: 'Throughput',\n data: { provider: 'durable.throughput' },\n format: 'rate',\n spark: true,\n },\n ],\n },\n {\n title: 'Needs attention',\n cols: 3,\n panels: [\n {\n kind: 'topN',\n title: 'Top failing workflows',\n data: { provider: 'durable.timeseries', query: { metric: 'topFailures' } },\n limit: 8,\n },\n {\n kind: 'table',\n title: 'Stuck runs',\n data: { provider: 'durable.recentFailures', query: { windowMs } },\n columns: [\n { key: 'updatedAt', label: 'Updated' },\n { key: 'workflow', label: 'Workflow' },\n { key: 'runId', label: 'Run', link: { href: runHref } },\n { key: 'error', label: 'Error' },\n ],\n },\n {\n kind: 'table',\n title: 'Starved worker groups',\n data: { provider: 'durable.workerHealth' },\n columns: [\n { key: 'group', label: 'Group' },\n { key: 'queued', label: 'Queued' },\n { key: 'liveWorkers', label: 'Workers' },\n { key: 'status', label: 'Status' },\n ],\n },\n ],\n },\n {\n title: 'Trends',\n cols: 3,\n panels: [\n {\n kind: 'timeseries',\n title: 'Runs over time',\n data: { provider: 'durable.runsOverTime' },\n series: ['done', 'failed'],\n style: 'stacked',\n },\n {\n kind: 'distribution',\n title: 'Duration distribution',\n data: { provider: 'durable.duration' },\n markers: ['p50', 'p95', 'p99'],\n format: 'duration',\n },\n {\n kind: 'breakdown',\n title: 'Runs by state',\n data: { provider: 'durable.stateBreakdown' },\n style: 'donut',\n },\n ],\n },\n ],\n };\n}\n","import { STATE_STORE, WorkflowEngine } from '@dudousxd/nestjs-durable-core';\nimport type { RunStatus, StateStore } from '@dudousxd/nestjs-durable-core';\nimport type { DataProvider, ExtensionContext } from '@dudousxd/nestjs-telescope';\nimport { TELESCOPE_STORAGE } from '@dudousxd/nestjs-telescope';\n\n// ─── Shared helpers ───────────────────────────────────────────────────────────\n\n/** Compute the p-th percentile of a SORTED array. */\nfunction percentile(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0;\n const idx = Math.min(sorted.length - 1, Math.ceil((p / 100) * sorted.length) - 1);\n return sorted[Math.max(0, idx)] ?? 0;\n}\n\ntype StorageEntry = { content?: unknown; createdAt?: Date };\n\n/** Fetch the durable entry page from TELESCOPE_STORAGE. */\nasync function fetchEntries(ctx: ExtensionContext): Promise<StorageEntry[]> {\n const storage = ctx.moduleRef.get(TELESCOPE_STORAGE, { strict: false }) as {\n get(q: { type?: string; limit?: number }): Promise<{ data: StorageEntry[] }>;\n };\n const page = await storage.get({ type: 'durable', limit: 5_000 });\n return page.data;\n}\n\n/**\n * Split entries into current/previous equal-length windows.\n * current: (now - windowMs, now]\n * previous: (now - 2*windowMs, now - windowMs]\n */\nfunction splitWindows(\n entries: StorageEntry[],\n windowMs: number,\n now: number,\n): { current: StorageEntry[]; previous: StorageEntry[] } {\n const start = now - windowMs;\n const prevStart = start - windowMs;\n return {\n current: entries.filter((e) => {\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n return t > start && t <= now;\n }),\n previous: entries.filter((e) => {\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n return t > prevStart && t <= start;\n }),\n };\n}\n\n/** Compute success rate (completed / total) for a list of entries, returns 1 when no data. */\nfunction successRateOf(entries: StorageEntry[]): number {\n let completed = 0;\n let failed = 0;\n for (const e of entries) {\n const c = (e.content ?? {}) as { event?: string };\n if (c.event === 'run.completed') completed += 1;\n else if (c.event === 'run.failed') failed += 1;\n }\n const total = completed + failed;\n return total === 0 ? 1 : completed / total;\n}\n\nconst STATE_CAP = 10_000;\n\n// ─── Existing providers ───────────────────────────────────────────────────────\n\n/** Source C: current-state gauge from the durable store. query.status selects which (default 'dead'). */\nexport function durableStateProvider(): DataProvider {\n return {\n name: 'durable.state',\n async resolve(query, ctx: ExtensionContext) {\n const store = ctx.moduleRef.get(STATE_STORE, { strict: false }) as StateStore;\n const status = (query?.status as RunStatus) ?? 'dead';\n const runs = await store.listRuns({ status, limit: STATE_CAP });\n return { value: runs.length };\n },\n };\n}\n\n/**\n * Source A: rollups from captured `durable` entries in Telescope's own storage.\n * Reads recent run.* lifecycle entries and aggregates. Bounded by Telescope's prune\n * window (by design — this is the \"history\" series, not the source of truth).\n */\nexport function durableTimeseriesProvider(): DataProvider {\n return {\n name: 'durable.timeseries',\n async resolve(query, ctx: ExtensionContext) {\n // TELESCOPE_STORAGE.get(query) → { data: Entry[]; nextCursor }. Entry = { content, createdAt, ... }.\n const storage = ctx.moduleRef.get(TELESCOPE_STORAGE, { strict: false }) as {\n get(q: { type?: string; limit?: number }): Promise<{\n data: Array<{ content?: unknown; createdAt?: Date }>;\n }>;\n };\n const limit = Math.min(5_000, Math.max(100, Number(query?.limit ?? 2_000)));\n const page = await storage.get({ type: 'durable', limit });\n\n let completed = 0;\n let failed = 0;\n const failByWorkflow = new Map<string, number>();\n for (const e of page.data) {\n const c = (e.content ?? {}) as { event?: string; workflow?: string };\n if (c.event === 'run.completed') completed += 1;\n else if (c.event === 'run.failed') {\n failed += 1;\n const wf = c.workflow ?? 'unknown';\n failByWorkflow.set(wf, (failByWorkflow.get(wf) ?? 0) + 1);\n }\n }\n const total = completed + failed;\n const metric = (query?.metric as string) ?? 'successRate';\n if (metric === 'successRate') return { value: total === 0 ? 1 : completed / total };\n if (metric === 'failed') return { value: failed };\n if (metric === 'total') return { value: total };\n if (metric === 'topFailures') {\n const items = [...failByWorkflow.entries()]\n .map(([label, value]) => ({ label, value }))\n .sort((a, b) => b.value - a.value);\n return { items };\n }\n return { value: total };\n },\n };\n}\n\n/**\n * Source C: recent failed + dead runs as table rows (newest first), time-bounded.\n * Only failures updated within `query.windowMs` (default 24h) are returned, so a\n * healthy system shows an EMPTY table instead of surfacing days-old failures as if\n * they were a live incident. `windowMs: 0` disables the window (return all). Each\n * row carries a compact `updatedAt` stamp so recency is visible in the table.\n */\nexport function durableRecentFailuresProvider(): DataProvider {\n return {\n name: 'durable.recentFailures',\n async resolve(query, ctx: ExtensionContext) {\n const store = ctx.moduleRef.get(STATE_STORE, { strict: false }) as StateStore;\n const limit = Math.min(200, Math.max(10, Number(query?.limit ?? 50)));\n const windowMs = query?.windowMs === undefined ? 24 * 60 * 60 * 1000 : Number(query.windowMs);\n const cutoff = windowMs > 0 ? Date.now() - windowMs : 0;\n const [failed, dead] = await Promise.all([\n store.listRuns({ status: 'failed', limit }),\n store.listRuns({ status: 'dead', limit }),\n ]);\n const rows = [...failed, ...dead]\n .filter((r) => +new Date(r.updatedAt) >= cutoff)\n .sort((a, b) => +new Date(b.updatedAt) - +new Date(a.updatedAt))\n .slice(0, limit)\n .map((r) => ({\n // Compact UTC stamp (\"YYYY-MM-DD HH:mm Z\"); the generic table renderer\n // prints column values as-is, so we format here.\n updatedAt: `${new Date(r.updatedAt).toISOString().replace('T', ' ').slice(0, 16)}Z`,\n workflow: r.workflow,\n runId: r.id,\n error: r.error?.message ?? '',\n }));\n return { rows };\n },\n };\n}\n\n/**\n * Source D: per-group worker health from the engine (`WorkflowEngine.workerHealth()` — queue depth\n * vs. live worker heartbeats). `query.metric: 'starvedCount'` returns the number of groups with work\n * queued and zero live workers (the \"alive but not consuming\" alert state) as a stat; otherwise a\n * table, starved groups sorted first. Empty when the transport can't report health (in-process).\n */\nexport function durableWorkerHealthProvider(): DataProvider {\n return {\n name: 'durable.workerHealth',\n async resolve(query, ctx: ExtensionContext) {\n const engine = ctx.moduleRef.get(WorkflowEngine, { strict: false }) as WorkflowEngine;\n const health = await engine.workerHealth();\n const isStarved = (g: { depth: number; liveWorkers: unknown[] }) =>\n g.depth > 0 && g.liveWorkers.length === 0;\n if ((query?.metric as string) === 'starvedCount') {\n return { value: health.filter(isStarved).length };\n }\n const rows = health\n .slice()\n // Starved groups first, then deepest backlog — the rows that need attention rise to the top.\n .sort((a, b) => Number(isStarved(b)) - Number(isStarved(a)) || b.depth - a.depth)\n .map((g) => ({\n group: g.group,\n queued: g.depth,\n liveWorkers: g.liveWorkers.length,\n status: isStarved(g) ? 'STARVED' : 'ok',\n }));\n return { rows };\n },\n };\n}\n\n// ─── New golden-signals providers ────────────────────────────────────────────\n\n/**\n * Duration percentiles (p50/p95/p99) + a histogram of ~8 buckets.\n * Durations are read from `content.durationMs` on `run.completed`/`run.failed`; when absent,\n * computed by pairing a `run.started` and its corresponding `run.completed`/`run.failed` entry\n * by `runId` using their `createdAt` timestamps.\n *\n * With `query.metric === 'p50'|'p95'|'p99'` returns `{ value }` for use as a stat panel.\n */\nexport function durableDurationProvider(): DataProvider {\n return {\n name: 'durable.duration',\n async resolve(query, ctx: ExtensionContext) {\n const entries = await fetchEntries(ctx);\n\n // Index run.started entries by runId for the pairing fallback.\n const startedAt = new Map<string, number>();\n for (const e of entries) {\n const c = (e.content ?? {}) as { event?: string; runId?: string };\n if (c.event === 'run.started' && c.runId && e.createdAt) {\n startedAt.set(c.runId, +new Date(e.createdAt));\n }\n }\n\n const durs: number[] = [];\n for (const e of entries) {\n const c = (e.content ?? {}) as { event?: string; runId?: string; durationMs?: number };\n if (c.event === 'run.completed' || c.event === 'run.failed') {\n if (typeof c.durationMs === 'number') {\n durs.push(c.durationMs);\n } else if (c.runId) {\n const start = startedAt.get(c.runId);\n const end = e.createdAt ? +new Date(e.createdAt) : undefined;\n if (start !== undefined && end !== undefined && end >= start) {\n durs.push(end - start);\n }\n }\n }\n }\n durs.sort((a, b) => a - b);\n\n const p50 = percentile(durs, 50);\n const p95 = percentile(durs, 95);\n const p99 = percentile(durs, 99);\n\n // Stat shortcut: return a single value for use in stat panels.\n const metric = (query as Record<string, unknown>)?.metric as string | undefined;\n if (metric === 'p50') return { value: p50 };\n if (metric === 'p95') return { value: p95 };\n if (metric === 'p99') return { value: p99 };\n\n // Build ~8 equal-width buckets between 0 and max.\n const max = durs.at(-1) ?? 0;\n const bucketCount = 8;\n const size = Math.max(1, Math.ceil((max + 1) / bucketCount));\n const buckets = Array.from({ length: bucketCount }, (_, i) => ({\n label: `${Math.round((i * size) / 100) / 10}s`,\n count: 0,\n }));\n for (const d of durs) {\n const bi = Math.min(bucketCount - 1, Math.floor(d / size));\n const bucket = buckets[bi];\n if (bucket) bucket.count += 1;\n }\n\n return { buckets, p50, p95, p99 };\n },\n };\n}\n\n/**\n * Buckets `run.completed` (-> done) and `run.failed` (-> failed) entries by `createdAt` into\n * N=`query.buckets ?? 24` equal-width time buckets. Returns `{ rows: Array<{ label; done; failed }> }`.\n */\nexport function durableRunsOverTimeProvider(): DataProvider {\n return {\n name: 'durable.runsOverTime',\n async resolve(query, ctx: ExtensionContext) {\n const entries = await fetchEntries(ctx);\n const n = Number((query as Record<string, unknown>)?.buckets ?? 24);\n const now = Date.now();\n\n // Determine the time window from the oldest entry or default to 24h.\n let minT = now;\n for (const e of entries) {\n if (e.createdAt) {\n const t = +new Date(e.createdAt);\n if (t < minT) minT = t;\n }\n }\n const span = Math.max(now - minT, 1);\n const bucketSize = span / n;\n\n const rows: Array<{ label: string; done: number; failed: number }> = Array.from(\n { length: n },\n (_, i) => {\n const bucketStart = new Date(minT + i * bucketSize);\n const label = bucketStart.toISOString().slice(11, 16); // \"HH:mm\"\n return { label, done: 0, failed: 0 };\n },\n );\n\n for (const e of entries) {\n const c = (e.content ?? {}) as { event?: string };\n if (c.event !== 'run.completed' && c.event !== 'run.failed') continue;\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n const idx = Math.min(n - 1, Math.floor((t - minT) / bucketSize));\n const row = rows[idx];\n if (row) {\n if (c.event === 'run.completed') row.done += 1;\n else row.failed += 1;\n }\n }\n\n return { rows };\n },\n };\n}\n\n/**\n * Success rate over the last `query.windowMs` (default 24h), with a `delta` vs. the previous\n * equal-length window and a `spark` array of per-sub-bucket success rates.\n */\nexport function durableSuccessRateProvider(): DataProvider {\n return {\n name: 'durable.successRate',\n async resolve(query, ctx: ExtensionContext) {\n const entries = await fetchEntries(ctx);\n const windowMs = Number((query as Record<string, unknown>)?.windowMs ?? 24 * 60 * 60 * 1000);\n const now = Date.now();\n const { current, previous } = splitWindows(entries, windowMs, now);\n\n const value = successRateOf(current);\n const prevRate = successRateOf(previous);\n const delta = previous.length > 0 ? value - prevRate : undefined;\n\n // Spark: split the current window into 8 sub-buckets.\n const sparkBuckets = 8;\n const bucketSize = windowMs / sparkBuckets;\n const sparkStart = now - windowMs;\n const spark = Array.from({ length: sparkBuckets }, (_, i) => {\n const bStart = sparkStart + i * bucketSize;\n const bEnd = bStart + bucketSize;\n const bEntries = current.filter((e) => {\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n return t > bStart && t <= bEnd;\n });\n return successRateOf(bEntries);\n });\n\n return { value, delta, spark };\n },\n };\n}\n\n/**\n * Completed runs per hour over the last `query.windowMs` (default 24h), with a `delta` vs. the\n * previous equal-length window and a `spark` array of per-sub-bucket throughput values.\n */\nexport function durableThroughputProvider(): DataProvider {\n return {\n name: 'durable.throughput',\n async resolve(query, ctx: ExtensionContext) {\n const entries = await fetchEntries(ctx);\n const windowMs = Number((query as Record<string, unknown>)?.windowMs ?? 24 * 60 * 60 * 1000);\n const now = Date.now();\n const { current, previous } = splitWindows(entries, windowMs, now);\n\n const countCompleted = (es: StorageEntry[]) =>\n es.filter((e) => ((e.content ?? {}) as { event?: string }).event === 'run.completed')\n .length;\n\n const windowHours = windowMs / (60 * 60 * 1000);\n const value = countCompleted(current) / windowHours;\n const prevValue = countCompleted(previous) / windowHours;\n const delta = previous.length > 0 ? value - prevValue : undefined;\n\n // Spark: 8 sub-buckets of current window.\n const sparkBuckets = 8;\n const bucketSize = windowMs / sparkBuckets;\n const bucketHours = bucketSize / (60 * 60 * 1000);\n const sparkStart = now - windowMs;\n const spark = Array.from({ length: sparkBuckets }, (_, i) => {\n const bStart = sparkStart + i * bucketSize;\n const bEnd = bStart + bucketSize;\n const bEntries = current.filter((e) => {\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n return t > bStart && t <= bEnd;\n });\n return countCompleted(bEntries) / bucketHours;\n });\n\n return { value, delta, spark };\n },\n };\n}\n\n/** Color palette for the state breakdown pie segments. */\nconst STATE_BREAKDOWN_PALETTE = ['#34d399', '#fbbf24', '#f87171', '#38bdf8', '#a78bfa'];\nconst STATE_BREAKDOWN_STATUSES: RunStatus[] = ['running', 'pending', 'completed', 'failed', 'dead'];\n\n/**\n * Counts from `STATE_STORE.listRuns({ status })` for each of the 5 statuses and returns pie\n * segments with the standard palette: `{ segments: Array<{ label, value, color }> }`.\n */\nexport function durableStateBreakdownProvider(): DataProvider {\n return {\n name: 'durable.stateBreakdown',\n async resolve(_query, ctx: ExtensionContext) {\n const store = ctx.moduleRef.get(STATE_STORE, { strict: false }) as StateStore;\n const counts = await Promise.all(\n STATE_BREAKDOWN_STATUSES.map((status) =>\n store.listRuns({ status, limit: STATE_CAP }).then((runs) => runs.length),\n ),\n );\n const segments = STATE_BREAKDOWN_STATUSES.map((label, i) => ({\n label,\n value: counts[i],\n color: STATE_BREAKDOWN_PALETTE[i],\n }));\n return { segments };\n },\n };\n}\n\n// NOTE: durableRetryHotspotsProvider is NOT implemented.\n// StateStore only exposes `listCheckpoints(runId: string)` (per-run lookup), with no cross-run\n// query by step `attempts`. There is no API to enumerate all step checkpoints across runs without\n// first listing every run — which would be O(N*M) and impractical. Task 13 should omit that panel.\n"],"mappings":"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","names":["DurableTelescopeWatcher","type","roots","Map","register","ctx","engine","moduleRef","get","WorkflowEngine","strict","tracer","trace","getTracer","subscribe","event","record","content","workflow","runId","seq","name","kind","output","error","durationMs","tags","set","startSpan","attributes","root","base","setSpan","context","active","step","with","end","delete","message","setStatus","undefined","code","SpanStatusCode","ERROR","push","import_nestjs_telescope","durableDashboard","opts","runHref","windowMs","recentFailuresWindowMs","id","label","panels","sections","title","cols","kind","data","provider","max","format","thresholds","warn","bad","direction","query","metric","spark","status","limit","columns","key","link","href","series","style","markers","import_nestjs_durable_core","percentile","sorted","p","length","idx","Math","min","ceil","max","fetchEntries","ctx","storage","moduleRef","get","TELESCOPE_STORAGE","strict","page","type","limit","data","splitWindows","entries","windowMs","now","start","prevStart","current","filter","e","t","createdAt","Date","previous","successRateOf","completed","failed","c","content","event","total","STATE_CAP","durableStateProvider","name","resolve","query","store","STATE_STORE","status","runs","listRuns","value","durableTimeseriesProvider","Number","failByWorkflow","Map","wf","workflow","set","metric","items","map","label","sort","a","b","durableRecentFailuresProvider","undefined","cutoff","dead","Promise","all","rows","r","updatedAt","slice","toISOString","replace","runId","id","error","message","durableWorkerHealthProvider","engine","WorkflowEngine","health","workerHealth","isStarved","g","depth","liveWorkers","group","queued","durableDurationProvider","startedAt","durs","durationMs","push","end","p50","p95","p99","at","bucketCount","size","buckets","Array","from","_","i","round","count","d","bi","floor","bucket","durableRunsOverTimeProvider","n","minT","span","bucketSize","bucketStart","done","row","durableSuccessRateProvider","prevRate","delta","sparkBuckets","sparkStart","spark","bStart","bEnd","bEntries","durableThroughputProvider","countCompleted","es","windowHours","prevValue","bucketHours","STATE_BREAKDOWN_PALETTE","STATE_BREAKDOWN_STATUSES","durableStateBreakdownProvider","_query","counts","then","segments","color","durableTelescopeExtension","opts","defineTelescopeExtension","name","watchers","DurableTelescopeWatcher","entryTypes","id","label","dot","dashboards","durableDashboard","dataProviders","durableStateProvider","durableTimeseriesProvider","durableRecentFailuresProvider","durableWorkerHealthProvider","durableDurationProvider","durableRunsOverTimeProvider","durableSuccessRateProvider","durableThroughputProvider","durableStateBreakdownProvider"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/durable-telescope.watcher.ts","../src/durable-telescope.extension.ts","../src/durable-dashboard.spec-data.ts","../src/durable-data-providers.ts"],"sourcesContent":["export * from './durable-telescope.watcher';\nexport * from './durable-telescope.extension';\nexport * from './durable-data-providers';\nexport * from './durable-dashboard.spec-data';\n","import { type EngineEvent, WorkflowEngine } from '@dudousxd/nestjs-durable-core';\nimport type { Watcher, WatcherContext } from '@dudousxd/nestjs-telescope';\nimport { type Span, SpanStatusCode, context, trace } from '@opentelemetry/api';\n\n/**\n * A Telescope watcher that turns durable-workflow lifecycle events into Telescope entries, so\n * runs and steps (including remote/Python steps) show up alongside the app's requests, queries\n * and jobs in the Telescope UI — under the **Workflows** tab.\n *\n * It also **trace-groups** a run: a root span is opened per run and every entry is recorded inside\n * that span's OTel context (step entries inside a child `step …` span). Telescope's trace-context\n * provider stamps each entry with the active span, so a whole workflow shows up as a single trace\n * on the Traces page — run → steps — with each entry carrying the run's `traceId`. When the host has\n * no OTel SDK the spans are no-ops and entries simply carry no trace (the prior behaviour).\n *\n * Add it to `TelescopeModule.forRoot({ watchers: [new DurableTelescopeWatcher()] })`. It resolves\n * the engine from the (global) durable providers and subscribes to its events.\n */\nexport class DurableTelescopeWatcher implements Watcher {\n readonly type = 'durable';\n /** Open root span per run, so step entries nest under it and share its traceId. */\n private readonly roots = new Map<string, Span>();\n\n register(ctx: WatcherContext): void {\n const engine = ctx.moduleRef.get(WorkflowEngine, { strict: false });\n const tracer = trace.getTracer('@dudousxd/nestjs-durable-telescope');\n\n engine.subscribe((event) => {\n const record = () =>\n ctx.record({\n type: this.type,\n content: {\n event: event.type,\n workflow: event.workflow,\n runId: event.runId,\n seq: event.seq,\n name: event.name,\n kind: event.kind,\n output: event.output,\n error: event.error,\n durationMs: event.durationMs,\n },\n tags: this.tags(event),\n });\n\n // Open the run's root span on first sight; later events nest under it.\n if (event.type === 'run.started') {\n this.roots.set(\n event.runId,\n tracer.startSpan(`workflow ${event.workflow ?? '?'}`, {\n attributes: { 'durable.run_id': event.runId, 'durable.workflow': event.workflow },\n }),\n );\n }\n const root = this.roots.get(event.runId);\n const base = root ? trace.setSpan(context.active(), root) : context.active();\n\n // A step/signal/sleep event → give it its own child span so it's a distinct node in the\n // trace; run-level events record directly on the root. Either way the active span at record\n // time carries the run's traceId, so all entries group into one trace.\n if (event.name != null && event.seq != null) {\n const step = tracer.startSpan(\n `step ${event.name}`,\n {\n attributes: {\n 'durable.run_id': event.runId,\n 'durable.step.seq': event.seq,\n 'durable.step.kind': event.kind,\n },\n },\n base,\n );\n context.with(trace.setSpan(base, step), record);\n step.end();\n } else {\n context.with(base, record);\n }\n\n // Close the run's root span when it leaves the running state.\n if (event.type === 'run.completed' || event.type === 'run.suspended') {\n root?.end();\n this.roots.delete(event.runId);\n } else if (event.type === 'run.failed') {\n const message = event.error?.message;\n root?.setStatus(\n message !== undefined\n ? { code: SpanStatusCode.ERROR, message }\n : { code: SpanStatusCode.ERROR },\n );\n root?.end();\n this.roots.delete(event.runId);\n }\n });\n }\n\n private tags(event: EngineEvent): string[] {\n const tags = ['durable', event.type, `run:${event.runId}`];\n if (event.workflow) tags.push(`workflow:${event.workflow}`);\n if (event.kind) tags.push(`kind:${event.kind}`);\n if (event.type === 'run.failed') tags.push('failed');\n return tags;\n }\n}\n","import { defineTelescopeExtension } from '@dudousxd/nestjs-telescope';\nimport { durableDashboard } from './durable-dashboard.spec-data';\nimport {\n durableDurationProvider,\n durableRecentFailuresProvider,\n durableRunsOverTimeProvider,\n durableStateBreakdownProvider,\n durableStateProvider,\n durableSuccessRateProvider,\n durableThroughputProvider,\n durableTimeseriesProvider,\n durableWorkerHealthProvider,\n} from './durable-data-providers';\nimport { DurableTelescopeWatcher } from './durable-telescope.watcher';\n\n/** The first-class Telescope extension for nestjs-durable: watcher + Workflows dashboard. */\nexport function durableTelescopeExtension(\n opts: { runHref?: string; recentFailuresWindowMs?: number } = {},\n) {\n return defineTelescopeExtension({\n name: 'durable',\n watchers: () => [new DurableTelescopeWatcher()],\n entryTypes: () => [{ id: 'durable', label: 'Workflows', dot: 'bg-amber-400' }],\n dashboards: () => [durableDashboard(opts)],\n dataProviders: () => [\n durableStateProvider(),\n durableTimeseriesProvider(),\n durableRecentFailuresProvider(),\n durableWorkerHealthProvider(),\n durableDurationProvider(),\n durableRunsOverTimeProvider(),\n durableSuccessRateProvider(),\n durableThroughputProvider(),\n durableStateBreakdownProvider(),\n ],\n });\n}\n","import type { DashboardSpec } from '@dudousxd/nestjs-telescope';\n\n/**\n * The \"Workflows\" health dashboard — golden-signals sections layout.\n *\n * `runHref` is a URL template for deep-linking a run out to the durable dashboard\n * (e.g. '/durable/runs/{runId}'). `recentFailuresWindowMs` bounds the \"Stuck runs\"\n * table (default 24h) so a healthy system shows an empty table instead of\n * days-old failures; pass `0` to show all.\n */\nexport function durableDashboard(\n opts: { runHref?: string; recentFailuresWindowMs?: number } = {},\n): DashboardSpec {\n const runHref = opts.runHref ?? '/durable/runs/{runId}';\n const windowMs = opts.recentFailuresWindowMs ?? 24 * 60 * 60 * 1000;\n return {\n id: 'durable.workflows',\n label: 'Workflows',\n panels: [],\n sections: [\n {\n title: 'Health',\n cols: 4,\n panels: [\n {\n kind: 'gauge',\n title: 'Success rate',\n data: { provider: 'durable.successRate' },\n max: 1,\n format: 'percent',\n thresholds: { warn: 0.98, bad: 0.95, direction: 'down-bad' },\n },\n {\n kind: 'stat',\n title: 'Duration p95',\n data: { provider: 'durable.duration', query: { metric: 'p95' } },\n format: 'duration',\n spark: false,\n thresholds: { warn: 2000, bad: 5000, direction: 'up-bad' },\n },\n {\n kind: 'stat',\n title: 'Backlog',\n data: { provider: 'durable.state', query: { status: 'pending' } },\n spark: false,\n thresholds: { warn: 50, bad: 200, direction: 'up-bad' },\n },\n {\n kind: 'stat',\n title: 'Throughput',\n data: { provider: 'durable.throughput' },\n format: 'rate',\n spark: true,\n },\n ],\n },\n {\n title: 'Needs attention',\n cols: 3,\n panels: [\n {\n kind: 'topN',\n title: 'Top failing workflows',\n data: { provider: 'durable.timeseries', query: { metric: 'topFailures' } },\n limit: 8,\n },\n {\n kind: 'table',\n title: 'Stuck runs',\n data: { provider: 'durable.recentFailures', query: { windowMs } },\n columns: [\n { key: 'updatedAt', label: 'Updated' },\n { key: 'workflow', label: 'Workflow' },\n { key: 'runId', label: 'Run', link: { href: runHref } },\n { key: 'error', label: 'Error' },\n ],\n },\n {\n kind: 'table',\n title: 'Starved worker groups',\n data: { provider: 'durable.workerHealth' },\n columns: [\n { key: 'group', label: 'Group' },\n { key: 'queued', label: 'Queued' },\n { key: 'liveWorkers', label: 'Workers' },\n { key: 'status', label: 'Status' },\n ],\n },\n ],\n },\n {\n title: 'Trends',\n cols: 3,\n panels: [\n {\n kind: 'timeseries',\n title: 'Runs over time',\n data: { provider: 'durable.runsOverTime' },\n series: ['done', 'failed'],\n style: 'stacked',\n },\n {\n kind: 'distribution',\n title: 'Duration distribution',\n data: { provider: 'durable.duration' },\n markers: ['p50', 'p95', 'p99'],\n format: 'duration',\n },\n {\n kind: 'breakdown',\n title: 'Runs by state',\n data: { provider: 'durable.stateBreakdown' },\n style: 'donut',\n },\n ],\n },\n ],\n };\n}\n","import { STATE_STORE_CANONICAL, WorkflowEngine } from '@dudousxd/nestjs-durable-core';\nimport type { RunStatus, StateStore } from '@dudousxd/nestjs-durable-core';\nimport type { DataProvider, ExtensionContext } from '@dudousxd/nestjs-telescope';\nimport { TELESCOPE_STORAGE } from '@dudousxd/nestjs-telescope';\n\n// ─── Shared helpers ───────────────────────────────────────────────────────────\n\n/** Compute the p-th percentile of a SORTED array. */\nfunction percentile(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0;\n const idx = Math.min(sorted.length - 1, Math.ceil((p / 100) * sorted.length) - 1);\n return sorted[Math.max(0, idx)] ?? 0;\n}\n\ntype StorageEntry = { content?: unknown; createdAt?: Date };\n\n/** Fetch the durable entry page from TELESCOPE_STORAGE. */\nasync function fetchEntries(ctx: ExtensionContext): Promise<StorageEntry[]> {\n const storage = ctx.moduleRef.get(TELESCOPE_STORAGE, { strict: false }) as {\n get(q: { type?: string; limit?: number }): Promise<{ data: StorageEntry[] }>;\n };\n const page = await storage.get({ type: 'durable', limit: 5_000 });\n return page.data;\n}\n\n/**\n * Split entries into current/previous equal-length windows.\n * current: (now - windowMs, now]\n * previous: (now - 2*windowMs, now - windowMs]\n */\nfunction splitWindows(\n entries: StorageEntry[],\n windowMs: number,\n now: number,\n): { current: StorageEntry[]; previous: StorageEntry[] } {\n const start = now - windowMs;\n const prevStart = start - windowMs;\n return {\n current: entries.filter((e) => {\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n return t > start && t <= now;\n }),\n previous: entries.filter((e) => {\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n return t > prevStart && t <= start;\n }),\n };\n}\n\n/** Compute success rate (completed / total) for a list of entries, returns 1 when no data. */\nfunction successRateOf(entries: StorageEntry[]): number {\n let completed = 0;\n let failed = 0;\n for (const e of entries) {\n const c = (e.content ?? {}) as { event?: string };\n if (c.event === 'run.completed') completed += 1;\n else if (c.event === 'run.failed') failed += 1;\n }\n const total = completed + failed;\n return total === 0 ? 1 : completed / total;\n}\n\nconst STATE_CAP = 10_000;\n\n// ─── Existing providers ───────────────────────────────────────────────────────\n\n/** Source C: current-state gauge from the durable store. query.status selects which (default 'dead'). */\nexport function durableStateProvider(): DataProvider {\n return {\n name: 'durable.state',\n async resolve(query, ctx: ExtensionContext) {\n const store = ctx.moduleRef.get(STATE_STORE_CANONICAL, { strict: false }) as StateStore;\n const status = (query?.status as RunStatus) ?? 'dead';\n const runs = await store.listRuns({ status, limit: STATE_CAP });\n return { value: runs.length };\n },\n };\n}\n\n/**\n * Source A: rollups from captured `durable` entries in Telescope's own storage.\n * Reads recent run.* lifecycle entries and aggregates. Bounded by Telescope's prune\n * window (by design — this is the \"history\" series, not the source of truth).\n */\nexport function durableTimeseriesProvider(): DataProvider {\n return {\n name: 'durable.timeseries',\n async resolve(query, ctx: ExtensionContext) {\n // TELESCOPE_STORAGE.get(query) → { data: Entry[]; nextCursor }. Entry = { content, createdAt, ... }.\n const storage = ctx.moduleRef.get(TELESCOPE_STORAGE, { strict: false }) as {\n get(q: { type?: string; limit?: number }): Promise<{\n data: Array<{ content?: unknown; createdAt?: Date }>;\n }>;\n };\n const limit = Math.min(5_000, Math.max(100, Number(query?.limit ?? 2_000)));\n const page = await storage.get({ type: 'durable', limit });\n\n let completed = 0;\n let failed = 0;\n const failByWorkflow = new Map<string, number>();\n for (const e of page.data) {\n const c = (e.content ?? {}) as { event?: string; workflow?: string };\n if (c.event === 'run.completed') completed += 1;\n else if (c.event === 'run.failed') {\n failed += 1;\n const wf = c.workflow ?? 'unknown';\n failByWorkflow.set(wf, (failByWorkflow.get(wf) ?? 0) + 1);\n }\n }\n const total = completed + failed;\n const metric = (query?.metric as string) ?? 'successRate';\n if (metric === 'successRate') return { value: total === 0 ? 1 : completed / total };\n if (metric === 'failed') return { value: failed };\n if (metric === 'total') return { value: total };\n if (metric === 'topFailures') {\n const items = [...failByWorkflow.entries()]\n .map(([label, value]) => ({ label, value }))\n .sort((a, b) => b.value - a.value);\n return { items };\n }\n return { value: total };\n },\n };\n}\n\n/**\n * Source C: recent failed + dead runs as table rows (newest first), time-bounded.\n * Only failures updated within `query.windowMs` (default 24h) are returned, so a\n * healthy system shows an EMPTY table instead of surfacing days-old failures as if\n * they were a live incident. `windowMs: 0` disables the window (return all). Each\n * row carries a compact `updatedAt` stamp so recency is visible in the table.\n */\nexport function durableRecentFailuresProvider(): DataProvider {\n return {\n name: 'durable.recentFailures',\n async resolve(query, ctx: ExtensionContext) {\n const store = ctx.moduleRef.get(STATE_STORE_CANONICAL, { strict: false }) as StateStore;\n const limit = Math.min(200, Math.max(10, Number(query?.limit ?? 50)));\n const windowMs = query?.windowMs === undefined ? 24 * 60 * 60 * 1000 : Number(query.windowMs);\n const cutoff = windowMs > 0 ? Date.now() - windowMs : 0;\n const [failed, dead] = await Promise.all([\n store.listRuns({ status: 'failed', limit }),\n store.listRuns({ status: 'dead', limit }),\n ]);\n const rows = [...failed, ...dead]\n .filter((r) => +new Date(r.updatedAt) >= cutoff)\n .sort((a, b) => +new Date(b.updatedAt) - +new Date(a.updatedAt))\n .slice(0, limit)\n .map((r) => ({\n // Compact UTC stamp (\"YYYY-MM-DD HH:mm Z\"); the generic table renderer\n // prints column values as-is, so we format here.\n updatedAt: `${new Date(r.updatedAt).toISOString().replace('T', ' ').slice(0, 16)}Z`,\n workflow: r.workflow,\n runId: r.id,\n error: r.error?.message ?? '',\n }));\n return { rows };\n },\n };\n}\n\n/**\n * Source D: per-group worker health from the engine (`WorkflowEngine.workerHealth()` — queue depth\n * vs. live worker heartbeats). `query.metric: 'starvedCount'` returns the number of groups with work\n * queued and zero live workers (the \"alive but not consuming\" alert state) as a stat; otherwise a\n * table, starved groups sorted first. Empty when the transport can't report health (in-process).\n */\nexport function durableWorkerHealthProvider(): DataProvider {\n return {\n name: 'durable.workerHealth',\n async resolve(query, ctx: ExtensionContext) {\n const engine = ctx.moduleRef.get(WorkflowEngine, { strict: false }) as WorkflowEngine;\n const health = await engine.workerHealth();\n const isStarved = (g: { depth: number; liveWorkers: unknown[] }) =>\n g.depth > 0 && g.liveWorkers.length === 0;\n if ((query?.metric as string) === 'starvedCount') {\n return { value: health.filter(isStarved).length };\n }\n const rows = health\n .slice()\n // Starved groups first, then deepest backlog — the rows that need attention rise to the top.\n .sort((a, b) => Number(isStarved(b)) - Number(isStarved(a)) || b.depth - a.depth)\n .map((g) => ({\n group: g.group,\n queued: g.depth,\n liveWorkers: g.liveWorkers.length,\n status: isStarved(g) ? 'STARVED' : 'ok',\n }));\n return { rows };\n },\n };\n}\n\n// ─── New golden-signals providers ────────────────────────────────────────────\n\n/**\n * Duration percentiles (p50/p95/p99) + a histogram of ~8 buckets.\n * Durations are read from `content.durationMs` on `run.completed`/`run.failed`; when absent,\n * computed by pairing a `run.started` and its corresponding `run.completed`/`run.failed` entry\n * by `runId` using their `createdAt` timestamps.\n *\n * With `query.metric === 'p50'|'p95'|'p99'` returns `{ value }` for use as a stat panel.\n */\nexport function durableDurationProvider(): DataProvider {\n return {\n name: 'durable.duration',\n async resolve(query, ctx: ExtensionContext) {\n const entries = await fetchEntries(ctx);\n\n // Index run.started entries by runId for the pairing fallback.\n const startedAt = new Map<string, number>();\n for (const e of entries) {\n const c = (e.content ?? {}) as { event?: string; runId?: string };\n if (c.event === 'run.started' && c.runId && e.createdAt) {\n startedAt.set(c.runId, +new Date(e.createdAt));\n }\n }\n\n const durs: number[] = [];\n for (const e of entries) {\n const c = (e.content ?? {}) as { event?: string; runId?: string; durationMs?: number };\n if (c.event === 'run.completed' || c.event === 'run.failed') {\n if (typeof c.durationMs === 'number') {\n durs.push(c.durationMs);\n } else if (c.runId) {\n const start = startedAt.get(c.runId);\n const end = e.createdAt ? +new Date(e.createdAt) : undefined;\n if (start !== undefined && end !== undefined && end >= start) {\n durs.push(end - start);\n }\n }\n }\n }\n durs.sort((a, b) => a - b);\n\n const p50 = percentile(durs, 50);\n const p95 = percentile(durs, 95);\n const p99 = percentile(durs, 99);\n\n // Stat shortcut: return a single value for use in stat panels.\n const metric = (query as Record<string, unknown>)?.metric as string | undefined;\n if (metric === 'p50') return { value: p50 };\n if (metric === 'p95') return { value: p95 };\n if (metric === 'p99') return { value: p99 };\n\n // Build ~8 equal-width buckets between 0 and max.\n const max = durs.at(-1) ?? 0;\n const bucketCount = 8;\n const size = Math.max(1, Math.ceil((max + 1) / bucketCount));\n const buckets = Array.from({ length: bucketCount }, (_, i) => ({\n label: `${Math.round((i * size) / 100) / 10}s`,\n count: 0,\n }));\n for (const d of durs) {\n const bi = Math.min(bucketCount - 1, Math.floor(d / size));\n const bucket = buckets[bi];\n if (bucket) bucket.count += 1;\n }\n\n return { buckets, p50, p95, p99 };\n },\n };\n}\n\n/**\n * Buckets `run.completed` (-> done) and `run.failed` (-> failed) entries by `createdAt` into\n * N=`query.buckets ?? 24` equal-width time buckets. Returns `{ rows: Array<{ label; done; failed }> }`.\n */\nexport function durableRunsOverTimeProvider(): DataProvider {\n return {\n name: 'durable.runsOverTime',\n async resolve(query, ctx: ExtensionContext) {\n const entries = await fetchEntries(ctx);\n const n = Number((query as Record<string, unknown>)?.buckets ?? 24);\n const now = Date.now();\n\n // Determine the time window from the oldest entry or default to 24h.\n let minT = now;\n for (const e of entries) {\n if (e.createdAt) {\n const t = +new Date(e.createdAt);\n if (t < minT) minT = t;\n }\n }\n const span = Math.max(now - minT, 1);\n const bucketSize = span / n;\n\n const rows: Array<{ label: string; done: number; failed: number }> = Array.from(\n { length: n },\n (_, i) => {\n const bucketStart = new Date(minT + i * bucketSize);\n const label = bucketStart.toISOString().slice(11, 16); // \"HH:mm\"\n return { label, done: 0, failed: 0 };\n },\n );\n\n for (const e of entries) {\n const c = (e.content ?? {}) as { event?: string };\n if (c.event !== 'run.completed' && c.event !== 'run.failed') continue;\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n const idx = Math.min(n - 1, Math.floor((t - minT) / bucketSize));\n const row = rows[idx];\n if (row) {\n if (c.event === 'run.completed') row.done += 1;\n else row.failed += 1;\n }\n }\n\n return { rows };\n },\n };\n}\n\n/**\n * Success rate over the last `query.windowMs` (default 24h), with a `delta` vs. the previous\n * equal-length window and a `spark` array of per-sub-bucket success rates.\n */\nexport function durableSuccessRateProvider(): DataProvider {\n return {\n name: 'durable.successRate',\n async resolve(query, ctx: ExtensionContext) {\n const entries = await fetchEntries(ctx);\n const windowMs = Number((query as Record<string, unknown>)?.windowMs ?? 24 * 60 * 60 * 1000);\n const now = Date.now();\n const { current, previous } = splitWindows(entries, windowMs, now);\n\n const value = successRateOf(current);\n const prevRate = successRateOf(previous);\n const delta = previous.length > 0 ? value - prevRate : undefined;\n\n // Spark: split the current window into 8 sub-buckets.\n const sparkBuckets = 8;\n const bucketSize = windowMs / sparkBuckets;\n const sparkStart = now - windowMs;\n const spark = Array.from({ length: sparkBuckets }, (_, i) => {\n const bStart = sparkStart + i * bucketSize;\n const bEnd = bStart + bucketSize;\n const bEntries = current.filter((e) => {\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n return t > bStart && t <= bEnd;\n });\n return successRateOf(bEntries);\n });\n\n return { value, delta, spark };\n },\n };\n}\n\n/**\n * Completed runs per hour over the last `query.windowMs` (default 24h), with a `delta` vs. the\n * previous equal-length window and a `spark` array of per-sub-bucket throughput values.\n */\nexport function durableThroughputProvider(): DataProvider {\n return {\n name: 'durable.throughput',\n async resolve(query, ctx: ExtensionContext) {\n const entries = await fetchEntries(ctx);\n const windowMs = Number((query as Record<string, unknown>)?.windowMs ?? 24 * 60 * 60 * 1000);\n const now = Date.now();\n const { current, previous } = splitWindows(entries, windowMs, now);\n\n const countCompleted = (es: StorageEntry[]) =>\n es.filter((e) => ((e.content ?? {}) as { event?: string }).event === 'run.completed')\n .length;\n\n const windowHours = windowMs / (60 * 60 * 1000);\n const value = countCompleted(current) / windowHours;\n const prevValue = countCompleted(previous) / windowHours;\n const delta = previous.length > 0 ? value - prevValue : undefined;\n\n // Spark: 8 sub-buckets of current window.\n const sparkBuckets = 8;\n const bucketSize = windowMs / sparkBuckets;\n const bucketHours = bucketSize / (60 * 60 * 1000);\n const sparkStart = now - windowMs;\n const spark = Array.from({ length: sparkBuckets }, (_, i) => {\n const bStart = sparkStart + i * bucketSize;\n const bEnd = bStart + bucketSize;\n const bEntries = current.filter((e) => {\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n return t > bStart && t <= bEnd;\n });\n return countCompleted(bEntries) / bucketHours;\n });\n\n return { value, delta, spark };\n },\n };\n}\n\n/** Color palette for the state breakdown pie segments. */\nconst STATE_BREAKDOWN_PALETTE = ['#34d399', '#fbbf24', '#f59e0b', '#f87171', '#38bdf8', '#a78bfa'];\nconst STATE_BREAKDOWN_STATUSES: RunStatus[] = [\n 'running',\n 'pending',\n 'cancelling',\n 'completed',\n 'failed',\n 'dead',\n];\n\n/**\n * Counts from `STATE_STORE.listRuns({ status })` for each of the statuses and returns pie\n * segments with the standard palette: `{ segments: Array<{ label, value, color }> }`.\n */\nexport function durableStateBreakdownProvider(): DataProvider {\n return {\n name: 'durable.stateBreakdown',\n async resolve(_query, ctx: ExtensionContext) {\n const store = ctx.moduleRef.get(STATE_STORE_CANONICAL, { strict: false }) as StateStore;\n const counts = await Promise.all(\n STATE_BREAKDOWN_STATUSES.map((status) =>\n store.listRuns({ status, limit: STATE_CAP }).then((runs) => runs.length),\n ),\n );\n const segments = STATE_BREAKDOWN_STATUSES.map((label, i) => ({\n label,\n value: counts[i],\n color: STATE_BREAKDOWN_PALETTE[i],\n }));\n return { segments };\n },\n };\n}\n\n// NOTE: durableRetryHotspotsProvider is NOT implemented.\n// StateStore only exposes `listCheckpoints(runId: string)` (per-run lookup), with no cross-run\n// query by step `attempts`. There is no API to enumerate all step checkpoints across runs without\n// first listing every run — which would be O(N*M) and impractical. Task 13 should omit that panel.\n"],"mappings":"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","names":["DurableTelescopeWatcher","type","roots","Map","register","ctx","engine","moduleRef","get","WorkflowEngine","strict","tracer","trace","getTracer","subscribe","event","record","content","workflow","runId","seq","name","kind","output","error","durationMs","tags","set","startSpan","attributes","root","base","setSpan","context","active","step","with","end","delete","message","setStatus","undefined","code","SpanStatusCode","ERROR","push","import_nestjs_telescope","durableDashboard","opts","runHref","windowMs","recentFailuresWindowMs","id","label","panels","sections","title","cols","kind","data","provider","max","format","thresholds","warn","bad","direction","query","metric","spark","status","limit","columns","key","link","href","series","style","markers","import_nestjs_durable_core","percentile","sorted","p","length","idx","Math","min","ceil","max","fetchEntries","ctx","storage","moduleRef","get","TELESCOPE_STORAGE","strict","page","type","limit","data","splitWindows","entries","windowMs","now","start","prevStart","current","filter","e","t","createdAt","Date","previous","successRateOf","completed","failed","c","content","event","total","STATE_CAP","durableStateProvider","name","resolve","query","store","STATE_STORE_CANONICAL","status","runs","listRuns","value","durableTimeseriesProvider","Number","failByWorkflow","Map","wf","workflow","set","metric","items","map","label","sort","a","b","durableRecentFailuresProvider","undefined","cutoff","dead","Promise","all","rows","r","updatedAt","slice","toISOString","replace","runId","id","error","message","durableWorkerHealthProvider","engine","WorkflowEngine","health","workerHealth","isStarved","g","depth","liveWorkers","group","queued","durableDurationProvider","startedAt","durs","durationMs","push","end","p50","p95","p99","at","bucketCount","size","buckets","Array","from","_","i","round","count","d","bi","floor","bucket","durableRunsOverTimeProvider","n","minT","span","bucketSize","bucketStart","done","row","durableSuccessRateProvider","prevRate","delta","sparkBuckets","sparkStart","spark","bStart","bEnd","bEntries","durableThroughputProvider","countCompleted","es","windowHours","prevValue","bucketHours","STATE_BREAKDOWN_PALETTE","STATE_BREAKDOWN_STATUSES","durableStateBreakdownProvider","_query","counts","then","segments","color","durableTelescopeExtension","opts","defineTelescopeExtension","name","watchers","DurableTelescopeWatcher","entryTypes","id","label","dot","dashboards","durableDashboard","dataProviders","durableStateProvider","durableTimeseriesProvider","durableRecentFailuresProvider","durableWorkerHealthProvider","durableDurationProvider","durableRunsOverTimeProvider","durableSuccessRateProvider","durableThroughputProvider","durableStateBreakdownProvider"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -77,7 +77,7 @@ declare function durableSuccessRateProvider(): DataProvider;
|
|
|
77
77
|
*/
|
|
78
78
|
declare function durableThroughputProvider(): DataProvider;
|
|
79
79
|
/**
|
|
80
|
-
* Counts from `STATE_STORE.listRuns({ status })` for each of the
|
|
80
|
+
* Counts from `STATE_STORE.listRuns({ status })` for each of the statuses and returns pie
|
|
81
81
|
* segments with the standard palette: `{ segments: Array<{ label, value, color }> }`.
|
|
82
82
|
*/
|
|
83
83
|
declare function durableStateBreakdownProvider(): DataProvider;
|
package/dist/index.d.ts
CHANGED
|
@@ -77,7 +77,7 @@ declare function durableSuccessRateProvider(): DataProvider;
|
|
|
77
77
|
*/
|
|
78
78
|
declare function durableThroughputProvider(): DataProvider;
|
|
79
79
|
/**
|
|
80
|
-
* Counts from `STATE_STORE.listRuns({ status })` for each of the
|
|
80
|
+
* Counts from `STATE_STORE.listRuns({ status })` for each of the statuses and returns pie
|
|
81
81
|
* segments with the standard palette: `{ segments: Array<{ label, value, color }> }`.
|
|
82
82
|
*/
|
|
83
83
|
declare function durableStateBreakdownProvider(): DataProvider;
|
package/dist/index.js
CHANGED
|
@@ -276,7 +276,7 @@ function durableDashboard(opts = {}) {
|
|
|
276
276
|
__name(durableDashboard, "durableDashboard");
|
|
277
277
|
|
|
278
278
|
// src/durable-data-providers.ts
|
|
279
|
-
import {
|
|
279
|
+
import { STATE_STORE_CANONICAL, WorkflowEngine as WorkflowEngine2 } from "@dudousxd/nestjs-durable-core";
|
|
280
280
|
import { TELESCOPE_STORAGE } from "@dudousxd/nestjs-telescope";
|
|
281
281
|
function percentile(sorted, p) {
|
|
282
282
|
if (sorted.length === 0) return 0;
|
|
@@ -327,7 +327,7 @@ function durableStateProvider() {
|
|
|
327
327
|
return {
|
|
328
328
|
name: "durable.state",
|
|
329
329
|
async resolve(query, ctx) {
|
|
330
|
-
const store = ctx.moduleRef.get(
|
|
330
|
+
const store = ctx.moduleRef.get(STATE_STORE_CANONICAL, {
|
|
331
331
|
strict: false
|
|
332
332
|
});
|
|
333
333
|
const status = query?.status ?? "dead";
|
|
@@ -399,7 +399,7 @@ function durableRecentFailuresProvider() {
|
|
|
399
399
|
return {
|
|
400
400
|
name: "durable.recentFailures",
|
|
401
401
|
async resolve(query, ctx) {
|
|
402
|
-
const store = ctx.moduleRef.get(
|
|
402
|
+
const store = ctx.moduleRef.get(STATE_STORE_CANONICAL, {
|
|
403
403
|
strict: false
|
|
404
404
|
});
|
|
405
405
|
const limit = Math.min(200, Math.max(10, Number(query?.limit ?? 50)));
|
|
@@ -644,6 +644,7 @@ __name(durableThroughputProvider, "durableThroughputProvider");
|
|
|
644
644
|
var STATE_BREAKDOWN_PALETTE = [
|
|
645
645
|
"#34d399",
|
|
646
646
|
"#fbbf24",
|
|
647
|
+
"#f59e0b",
|
|
647
648
|
"#f87171",
|
|
648
649
|
"#38bdf8",
|
|
649
650
|
"#a78bfa"
|
|
@@ -651,6 +652,7 @@ var STATE_BREAKDOWN_PALETTE = [
|
|
|
651
652
|
var STATE_BREAKDOWN_STATUSES = [
|
|
652
653
|
"running",
|
|
653
654
|
"pending",
|
|
655
|
+
"cancelling",
|
|
654
656
|
"completed",
|
|
655
657
|
"failed",
|
|
656
658
|
"dead"
|
|
@@ -659,7 +661,7 @@ function durableStateBreakdownProvider() {
|
|
|
659
661
|
return {
|
|
660
662
|
name: "durable.stateBreakdown",
|
|
661
663
|
async resolve(_query, ctx) {
|
|
662
|
-
const store = ctx.moduleRef.get(
|
|
664
|
+
const store = ctx.moduleRef.get(STATE_STORE_CANONICAL, {
|
|
663
665
|
strict: false
|
|
664
666
|
});
|
|
665
667
|
const counts = await Promise.all(STATE_BREAKDOWN_STATUSES.map((status) => store.listRuns({
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/durable-telescope.watcher.ts","../src/durable-telescope.extension.ts","../src/durable-dashboard.spec-data.ts","../src/durable-data-providers.ts"],"sourcesContent":["import { type EngineEvent, WorkflowEngine } from '@dudousxd/nestjs-durable-core';\nimport type { Watcher, WatcherContext } from '@dudousxd/nestjs-telescope';\nimport { type Span, SpanStatusCode, context, trace } from '@opentelemetry/api';\n\n/**\n * A Telescope watcher that turns durable-workflow lifecycle events into Telescope entries, so\n * runs and steps (including remote/Python steps) show up alongside the app's requests, queries\n * and jobs in the Telescope UI — under the **Workflows** tab.\n *\n * It also **trace-groups** a run: a root span is opened per run and every entry is recorded inside\n * that span's OTel context (step entries inside a child `step …` span). Telescope's trace-context\n * provider stamps each entry with the active span, so a whole workflow shows up as a single trace\n * on the Traces page — run → steps — with each entry carrying the run's `traceId`. When the host has\n * no OTel SDK the spans are no-ops and entries simply carry no trace (the prior behaviour).\n *\n * Add it to `TelescopeModule.forRoot({ watchers: [new DurableTelescopeWatcher()] })`. It resolves\n * the engine from the (global) durable providers and subscribes to its events.\n */\nexport class DurableTelescopeWatcher implements Watcher {\n readonly type = 'durable';\n /** Open root span per run, so step entries nest under it and share its traceId. */\n private readonly roots = new Map<string, Span>();\n\n register(ctx: WatcherContext): void {\n const engine = ctx.moduleRef.get(WorkflowEngine, { strict: false });\n const tracer = trace.getTracer('@dudousxd/nestjs-durable-telescope');\n\n engine.subscribe((event) => {\n const record = () =>\n ctx.record({\n type: this.type,\n content: {\n event: event.type,\n workflow: event.workflow,\n runId: event.runId,\n seq: event.seq,\n name: event.name,\n kind: event.kind,\n output: event.output,\n error: event.error,\n durationMs: event.durationMs,\n },\n tags: this.tags(event),\n });\n\n // Open the run's root span on first sight; later events nest under it.\n if (event.type === 'run.started') {\n this.roots.set(\n event.runId,\n tracer.startSpan(`workflow ${event.workflow ?? '?'}`, {\n attributes: { 'durable.run_id': event.runId, 'durable.workflow': event.workflow },\n }),\n );\n }\n const root = this.roots.get(event.runId);\n const base = root ? trace.setSpan(context.active(), root) : context.active();\n\n // A step/signal/sleep event → give it its own child span so it's a distinct node in the\n // trace; run-level events record directly on the root. Either way the active span at record\n // time carries the run's traceId, so all entries group into one trace.\n if (event.name != null && event.seq != null) {\n const step = tracer.startSpan(\n `step ${event.name}`,\n {\n attributes: {\n 'durable.run_id': event.runId,\n 'durable.step.seq': event.seq,\n 'durable.step.kind': event.kind,\n },\n },\n base,\n );\n context.with(trace.setSpan(base, step), record);\n step.end();\n } else {\n context.with(base, record);\n }\n\n // Close the run's root span when it leaves the running state.\n if (event.type === 'run.completed' || event.type === 'run.suspended') {\n root?.end();\n this.roots.delete(event.runId);\n } else if (event.type === 'run.failed') {\n const message = event.error?.message;\n root?.setStatus(\n message !== undefined\n ? { code: SpanStatusCode.ERROR, message }\n : { code: SpanStatusCode.ERROR },\n );\n root?.end();\n this.roots.delete(event.runId);\n }\n });\n }\n\n private tags(event: EngineEvent): string[] {\n const tags = ['durable', event.type, `run:${event.runId}`];\n if (event.workflow) tags.push(`workflow:${event.workflow}`);\n if (event.kind) tags.push(`kind:${event.kind}`);\n if (event.type === 'run.failed') tags.push('failed');\n return tags;\n }\n}\n","import { defineTelescopeExtension } from '@dudousxd/nestjs-telescope';\nimport { durableDashboard } from './durable-dashboard.spec-data';\nimport {\n durableDurationProvider,\n durableRecentFailuresProvider,\n durableRunsOverTimeProvider,\n durableStateBreakdownProvider,\n durableStateProvider,\n durableSuccessRateProvider,\n durableThroughputProvider,\n durableTimeseriesProvider,\n durableWorkerHealthProvider,\n} from './durable-data-providers';\nimport { DurableTelescopeWatcher } from './durable-telescope.watcher';\n\n/** The first-class Telescope extension for nestjs-durable: watcher + Workflows dashboard. */\nexport function durableTelescopeExtension(\n opts: { runHref?: string; recentFailuresWindowMs?: number } = {},\n) {\n return defineTelescopeExtension({\n name: 'durable',\n watchers: () => [new DurableTelescopeWatcher()],\n entryTypes: () => [{ id: 'durable', label: 'Workflows', dot: 'bg-amber-400' }],\n dashboards: () => [durableDashboard(opts)],\n dataProviders: () => [\n durableStateProvider(),\n durableTimeseriesProvider(),\n durableRecentFailuresProvider(),\n durableWorkerHealthProvider(),\n durableDurationProvider(),\n durableRunsOverTimeProvider(),\n durableSuccessRateProvider(),\n durableThroughputProvider(),\n durableStateBreakdownProvider(),\n ],\n });\n}\n","import type { DashboardSpec } from '@dudousxd/nestjs-telescope';\n\n/**\n * The \"Workflows\" health dashboard — golden-signals sections layout.\n *\n * `runHref` is a URL template for deep-linking a run out to the durable dashboard\n * (e.g. '/durable/runs/{runId}'). `recentFailuresWindowMs` bounds the \"Stuck runs\"\n * table (default 24h) so a healthy system shows an empty table instead of\n * days-old failures; pass `0` to show all.\n */\nexport function durableDashboard(\n opts: { runHref?: string; recentFailuresWindowMs?: number } = {},\n): DashboardSpec {\n const runHref = opts.runHref ?? '/durable/runs/{runId}';\n const windowMs = opts.recentFailuresWindowMs ?? 24 * 60 * 60 * 1000;\n return {\n id: 'durable.workflows',\n label: 'Workflows',\n panels: [],\n sections: [\n {\n title: 'Health',\n cols: 4,\n panels: [\n {\n kind: 'gauge',\n title: 'Success rate',\n data: { provider: 'durable.successRate' },\n max: 1,\n format: 'percent',\n thresholds: { warn: 0.98, bad: 0.95, direction: 'down-bad' },\n },\n {\n kind: 'stat',\n title: 'Duration p95',\n data: { provider: 'durable.duration', query: { metric: 'p95' } },\n format: 'duration',\n spark: false,\n thresholds: { warn: 2000, bad: 5000, direction: 'up-bad' },\n },\n {\n kind: 'stat',\n title: 'Backlog',\n data: { provider: 'durable.state', query: { status: 'pending' } },\n spark: false,\n thresholds: { warn: 50, bad: 200, direction: 'up-bad' },\n },\n {\n kind: 'stat',\n title: 'Throughput',\n data: { provider: 'durable.throughput' },\n format: 'rate',\n spark: true,\n },\n ],\n },\n {\n title: 'Needs attention',\n cols: 3,\n panels: [\n {\n kind: 'topN',\n title: 'Top failing workflows',\n data: { provider: 'durable.timeseries', query: { metric: 'topFailures' } },\n limit: 8,\n },\n {\n kind: 'table',\n title: 'Stuck runs',\n data: { provider: 'durable.recentFailures', query: { windowMs } },\n columns: [\n { key: 'updatedAt', label: 'Updated' },\n { key: 'workflow', label: 'Workflow' },\n { key: 'runId', label: 'Run', link: { href: runHref } },\n { key: 'error', label: 'Error' },\n ],\n },\n {\n kind: 'table',\n title: 'Starved worker groups',\n data: { provider: 'durable.workerHealth' },\n columns: [\n { key: 'group', label: 'Group' },\n { key: 'queued', label: 'Queued' },\n { key: 'liveWorkers', label: 'Workers' },\n { key: 'status', label: 'Status' },\n ],\n },\n ],\n },\n {\n title: 'Trends',\n cols: 3,\n panels: [\n {\n kind: 'timeseries',\n title: 'Runs over time',\n data: { provider: 'durable.runsOverTime' },\n series: ['done', 'failed'],\n style: 'stacked',\n },\n {\n kind: 'distribution',\n title: 'Duration distribution',\n data: { provider: 'durable.duration' },\n markers: ['p50', 'p95', 'p99'],\n format: 'duration',\n },\n {\n kind: 'breakdown',\n title: 'Runs by state',\n data: { provider: 'durable.stateBreakdown' },\n style: 'donut',\n },\n ],\n },\n ],\n };\n}\n","import { STATE_STORE, WorkflowEngine } from '@dudousxd/nestjs-durable-core';\nimport type { RunStatus, StateStore } from '@dudousxd/nestjs-durable-core';\nimport type { DataProvider, ExtensionContext } from '@dudousxd/nestjs-telescope';\nimport { TELESCOPE_STORAGE } from '@dudousxd/nestjs-telescope';\n\n// ─── Shared helpers ───────────────────────────────────────────────────────────\n\n/** Compute the p-th percentile of a SORTED array. */\nfunction percentile(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0;\n const idx = Math.min(sorted.length - 1, Math.ceil((p / 100) * sorted.length) - 1);\n return sorted[Math.max(0, idx)] ?? 0;\n}\n\ntype StorageEntry = { content?: unknown; createdAt?: Date };\n\n/** Fetch the durable entry page from TELESCOPE_STORAGE. */\nasync function fetchEntries(ctx: ExtensionContext): Promise<StorageEntry[]> {\n const storage = ctx.moduleRef.get(TELESCOPE_STORAGE, { strict: false }) as {\n get(q: { type?: string; limit?: number }): Promise<{ data: StorageEntry[] }>;\n };\n const page = await storage.get({ type: 'durable', limit: 5_000 });\n return page.data;\n}\n\n/**\n * Split entries into current/previous equal-length windows.\n * current: (now - windowMs, now]\n * previous: (now - 2*windowMs, now - windowMs]\n */\nfunction splitWindows(\n entries: StorageEntry[],\n windowMs: number,\n now: number,\n): { current: StorageEntry[]; previous: StorageEntry[] } {\n const start = now - windowMs;\n const prevStart = start - windowMs;\n return {\n current: entries.filter((e) => {\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n return t > start && t <= now;\n }),\n previous: entries.filter((e) => {\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n return t > prevStart && t <= start;\n }),\n };\n}\n\n/** Compute success rate (completed / total) for a list of entries, returns 1 when no data. */\nfunction successRateOf(entries: StorageEntry[]): number {\n let completed = 0;\n let failed = 0;\n for (const e of entries) {\n const c = (e.content ?? {}) as { event?: string };\n if (c.event === 'run.completed') completed += 1;\n else if (c.event === 'run.failed') failed += 1;\n }\n const total = completed + failed;\n return total === 0 ? 1 : completed / total;\n}\n\nconst STATE_CAP = 10_000;\n\n// ─── Existing providers ───────────────────────────────────────────────────────\n\n/** Source C: current-state gauge from the durable store. query.status selects which (default 'dead'). */\nexport function durableStateProvider(): DataProvider {\n return {\n name: 'durable.state',\n async resolve(query, ctx: ExtensionContext) {\n const store = ctx.moduleRef.get(STATE_STORE, { strict: false }) as StateStore;\n const status = (query?.status as RunStatus) ?? 'dead';\n const runs = await store.listRuns({ status, limit: STATE_CAP });\n return { value: runs.length };\n },\n };\n}\n\n/**\n * Source A: rollups from captured `durable` entries in Telescope's own storage.\n * Reads recent run.* lifecycle entries and aggregates. Bounded by Telescope's prune\n * window (by design — this is the \"history\" series, not the source of truth).\n */\nexport function durableTimeseriesProvider(): DataProvider {\n return {\n name: 'durable.timeseries',\n async resolve(query, ctx: ExtensionContext) {\n // TELESCOPE_STORAGE.get(query) → { data: Entry[]; nextCursor }. Entry = { content, createdAt, ... }.\n const storage = ctx.moduleRef.get(TELESCOPE_STORAGE, { strict: false }) as {\n get(q: { type?: string; limit?: number }): Promise<{\n data: Array<{ content?: unknown; createdAt?: Date }>;\n }>;\n };\n const limit = Math.min(5_000, Math.max(100, Number(query?.limit ?? 2_000)));\n const page = await storage.get({ type: 'durable', limit });\n\n let completed = 0;\n let failed = 0;\n const failByWorkflow = new Map<string, number>();\n for (const e of page.data) {\n const c = (e.content ?? {}) as { event?: string; workflow?: string };\n if (c.event === 'run.completed') completed += 1;\n else if (c.event === 'run.failed') {\n failed += 1;\n const wf = c.workflow ?? 'unknown';\n failByWorkflow.set(wf, (failByWorkflow.get(wf) ?? 0) + 1);\n }\n }\n const total = completed + failed;\n const metric = (query?.metric as string) ?? 'successRate';\n if (metric === 'successRate') return { value: total === 0 ? 1 : completed / total };\n if (metric === 'failed') return { value: failed };\n if (metric === 'total') return { value: total };\n if (metric === 'topFailures') {\n const items = [...failByWorkflow.entries()]\n .map(([label, value]) => ({ label, value }))\n .sort((a, b) => b.value - a.value);\n return { items };\n }\n return { value: total };\n },\n };\n}\n\n/**\n * Source C: recent failed + dead runs as table rows (newest first), time-bounded.\n * Only failures updated within `query.windowMs` (default 24h) are returned, so a\n * healthy system shows an EMPTY table instead of surfacing days-old failures as if\n * they were a live incident. `windowMs: 0` disables the window (return all). Each\n * row carries a compact `updatedAt` stamp so recency is visible in the table.\n */\nexport function durableRecentFailuresProvider(): DataProvider {\n return {\n name: 'durable.recentFailures',\n async resolve(query, ctx: ExtensionContext) {\n const store = ctx.moduleRef.get(STATE_STORE, { strict: false }) as StateStore;\n const limit = Math.min(200, Math.max(10, Number(query?.limit ?? 50)));\n const windowMs = query?.windowMs === undefined ? 24 * 60 * 60 * 1000 : Number(query.windowMs);\n const cutoff = windowMs > 0 ? Date.now() - windowMs : 0;\n const [failed, dead] = await Promise.all([\n store.listRuns({ status: 'failed', limit }),\n store.listRuns({ status: 'dead', limit }),\n ]);\n const rows = [...failed, ...dead]\n .filter((r) => +new Date(r.updatedAt) >= cutoff)\n .sort((a, b) => +new Date(b.updatedAt) - +new Date(a.updatedAt))\n .slice(0, limit)\n .map((r) => ({\n // Compact UTC stamp (\"YYYY-MM-DD HH:mm Z\"); the generic table renderer\n // prints column values as-is, so we format here.\n updatedAt: `${new Date(r.updatedAt).toISOString().replace('T', ' ').slice(0, 16)}Z`,\n workflow: r.workflow,\n runId: r.id,\n error: r.error?.message ?? '',\n }));\n return { rows };\n },\n };\n}\n\n/**\n * Source D: per-group worker health from the engine (`WorkflowEngine.workerHealth()` — queue depth\n * vs. live worker heartbeats). `query.metric: 'starvedCount'` returns the number of groups with work\n * queued and zero live workers (the \"alive but not consuming\" alert state) as a stat; otherwise a\n * table, starved groups sorted first. Empty when the transport can't report health (in-process).\n */\nexport function durableWorkerHealthProvider(): DataProvider {\n return {\n name: 'durable.workerHealth',\n async resolve(query, ctx: ExtensionContext) {\n const engine = ctx.moduleRef.get(WorkflowEngine, { strict: false }) as WorkflowEngine;\n const health = await engine.workerHealth();\n const isStarved = (g: { depth: number; liveWorkers: unknown[] }) =>\n g.depth > 0 && g.liveWorkers.length === 0;\n if ((query?.metric as string) === 'starvedCount') {\n return { value: health.filter(isStarved).length };\n }\n const rows = health\n .slice()\n // Starved groups first, then deepest backlog — the rows that need attention rise to the top.\n .sort((a, b) => Number(isStarved(b)) - Number(isStarved(a)) || b.depth - a.depth)\n .map((g) => ({\n group: g.group,\n queued: g.depth,\n liveWorkers: g.liveWorkers.length,\n status: isStarved(g) ? 'STARVED' : 'ok',\n }));\n return { rows };\n },\n };\n}\n\n// ─── New golden-signals providers ────────────────────────────────────────────\n\n/**\n * Duration percentiles (p50/p95/p99) + a histogram of ~8 buckets.\n * Durations are read from `content.durationMs` on `run.completed`/`run.failed`; when absent,\n * computed by pairing a `run.started` and its corresponding `run.completed`/`run.failed` entry\n * by `runId` using their `createdAt` timestamps.\n *\n * With `query.metric === 'p50'|'p95'|'p99'` returns `{ value }` for use as a stat panel.\n */\nexport function durableDurationProvider(): DataProvider {\n return {\n name: 'durable.duration',\n async resolve(query, ctx: ExtensionContext) {\n const entries = await fetchEntries(ctx);\n\n // Index run.started entries by runId for the pairing fallback.\n const startedAt = new Map<string, number>();\n for (const e of entries) {\n const c = (e.content ?? {}) as { event?: string; runId?: string };\n if (c.event === 'run.started' && c.runId && e.createdAt) {\n startedAt.set(c.runId, +new Date(e.createdAt));\n }\n }\n\n const durs: number[] = [];\n for (const e of entries) {\n const c = (e.content ?? {}) as { event?: string; runId?: string; durationMs?: number };\n if (c.event === 'run.completed' || c.event === 'run.failed') {\n if (typeof c.durationMs === 'number') {\n durs.push(c.durationMs);\n } else if (c.runId) {\n const start = startedAt.get(c.runId);\n const end = e.createdAt ? +new Date(e.createdAt) : undefined;\n if (start !== undefined && end !== undefined && end >= start) {\n durs.push(end - start);\n }\n }\n }\n }\n durs.sort((a, b) => a - b);\n\n const p50 = percentile(durs, 50);\n const p95 = percentile(durs, 95);\n const p99 = percentile(durs, 99);\n\n // Stat shortcut: return a single value for use in stat panels.\n const metric = (query as Record<string, unknown>)?.metric as string | undefined;\n if (metric === 'p50') return { value: p50 };\n if (metric === 'p95') return { value: p95 };\n if (metric === 'p99') return { value: p99 };\n\n // Build ~8 equal-width buckets between 0 and max.\n const max = durs.at(-1) ?? 0;\n const bucketCount = 8;\n const size = Math.max(1, Math.ceil((max + 1) / bucketCount));\n const buckets = Array.from({ length: bucketCount }, (_, i) => ({\n label: `${Math.round((i * size) / 100) / 10}s`,\n count: 0,\n }));\n for (const d of durs) {\n const bi = Math.min(bucketCount - 1, Math.floor(d / size));\n const bucket = buckets[bi];\n if (bucket) bucket.count += 1;\n }\n\n return { buckets, p50, p95, p99 };\n },\n };\n}\n\n/**\n * Buckets `run.completed` (-> done) and `run.failed` (-> failed) entries by `createdAt` into\n * N=`query.buckets ?? 24` equal-width time buckets. Returns `{ rows: Array<{ label; done; failed }> }`.\n */\nexport function durableRunsOverTimeProvider(): DataProvider {\n return {\n name: 'durable.runsOverTime',\n async resolve(query, ctx: ExtensionContext) {\n const entries = await fetchEntries(ctx);\n const n = Number((query as Record<string, unknown>)?.buckets ?? 24);\n const now = Date.now();\n\n // Determine the time window from the oldest entry or default to 24h.\n let minT = now;\n for (const e of entries) {\n if (e.createdAt) {\n const t = +new Date(e.createdAt);\n if (t < minT) minT = t;\n }\n }\n const span = Math.max(now - minT, 1);\n const bucketSize = span / n;\n\n const rows: Array<{ label: string; done: number; failed: number }> = Array.from(\n { length: n },\n (_, i) => {\n const bucketStart = new Date(minT + i * bucketSize);\n const label = bucketStart.toISOString().slice(11, 16); // \"HH:mm\"\n return { label, done: 0, failed: 0 };\n },\n );\n\n for (const e of entries) {\n const c = (e.content ?? {}) as { event?: string };\n if (c.event !== 'run.completed' && c.event !== 'run.failed') continue;\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n const idx = Math.min(n - 1, Math.floor((t - minT) / bucketSize));\n const row = rows[idx];\n if (row) {\n if (c.event === 'run.completed') row.done += 1;\n else row.failed += 1;\n }\n }\n\n return { rows };\n },\n };\n}\n\n/**\n * Success rate over the last `query.windowMs` (default 24h), with a `delta` vs. the previous\n * equal-length window and a `spark` array of per-sub-bucket success rates.\n */\nexport function durableSuccessRateProvider(): DataProvider {\n return {\n name: 'durable.successRate',\n async resolve(query, ctx: ExtensionContext) {\n const entries = await fetchEntries(ctx);\n const windowMs = Number((query as Record<string, unknown>)?.windowMs ?? 24 * 60 * 60 * 1000);\n const now = Date.now();\n const { current, previous } = splitWindows(entries, windowMs, now);\n\n const value = successRateOf(current);\n const prevRate = successRateOf(previous);\n const delta = previous.length > 0 ? value - prevRate : undefined;\n\n // Spark: split the current window into 8 sub-buckets.\n const sparkBuckets = 8;\n const bucketSize = windowMs / sparkBuckets;\n const sparkStart = now - windowMs;\n const spark = Array.from({ length: sparkBuckets }, (_, i) => {\n const bStart = sparkStart + i * bucketSize;\n const bEnd = bStart + bucketSize;\n const bEntries = current.filter((e) => {\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n return t > bStart && t <= bEnd;\n });\n return successRateOf(bEntries);\n });\n\n return { value, delta, spark };\n },\n };\n}\n\n/**\n * Completed runs per hour over the last `query.windowMs` (default 24h), with a `delta` vs. the\n * previous equal-length window and a `spark` array of per-sub-bucket throughput values.\n */\nexport function durableThroughputProvider(): DataProvider {\n return {\n name: 'durable.throughput',\n async resolve(query, ctx: ExtensionContext) {\n const entries = await fetchEntries(ctx);\n const windowMs = Number((query as Record<string, unknown>)?.windowMs ?? 24 * 60 * 60 * 1000);\n const now = Date.now();\n const { current, previous } = splitWindows(entries, windowMs, now);\n\n const countCompleted = (es: StorageEntry[]) =>\n es.filter((e) => ((e.content ?? {}) as { event?: string }).event === 'run.completed')\n .length;\n\n const windowHours = windowMs / (60 * 60 * 1000);\n const value = countCompleted(current) / windowHours;\n const prevValue = countCompleted(previous) / windowHours;\n const delta = previous.length > 0 ? value - prevValue : undefined;\n\n // Spark: 8 sub-buckets of current window.\n const sparkBuckets = 8;\n const bucketSize = windowMs / sparkBuckets;\n const bucketHours = bucketSize / (60 * 60 * 1000);\n const sparkStart = now - windowMs;\n const spark = Array.from({ length: sparkBuckets }, (_, i) => {\n const bStart = sparkStart + i * bucketSize;\n const bEnd = bStart + bucketSize;\n const bEntries = current.filter((e) => {\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n return t > bStart && t <= bEnd;\n });\n return countCompleted(bEntries) / bucketHours;\n });\n\n return { value, delta, spark };\n },\n };\n}\n\n/** Color palette for the state breakdown pie segments. */\nconst STATE_BREAKDOWN_PALETTE = ['#34d399', '#fbbf24', '#f87171', '#38bdf8', '#a78bfa'];\nconst STATE_BREAKDOWN_STATUSES: RunStatus[] = ['running', 'pending', 'completed', 'failed', 'dead'];\n\n/**\n * Counts from `STATE_STORE.listRuns({ status })` for each of the 5 statuses and returns pie\n * segments with the standard palette: `{ segments: Array<{ label, value, color }> }`.\n */\nexport function durableStateBreakdownProvider(): DataProvider {\n return {\n name: 'durable.stateBreakdown',\n async resolve(_query, ctx: ExtensionContext) {\n const store = ctx.moduleRef.get(STATE_STORE, { strict: false }) as StateStore;\n const counts = await Promise.all(\n STATE_BREAKDOWN_STATUSES.map((status) =>\n store.listRuns({ status, limit: STATE_CAP }).then((runs) => runs.length),\n ),\n );\n const segments = STATE_BREAKDOWN_STATUSES.map((label, i) => ({\n label,\n value: counts[i],\n color: STATE_BREAKDOWN_PALETTE[i],\n }));\n return { segments };\n },\n };\n}\n\n// NOTE: durableRetryHotspotsProvider is NOT implemented.\n// StateStore only exposes `listCheckpoints(runId: string)` (per-run lookup), with no cross-run\n// query by step `attempts`. There is no API to enumerate all step checkpoints across runs without\n// first listing every run — which would be O(N*M) and impractical. Task 13 should omit that panel.\n"],"mappings":";;;;AAAA,SAA2BA,sBAAsB;AAEjD,SAAoBC,gBAAgBC,SAASC,aAAa;AAgBnD,IAAMC,0BAAN,MAAMA;EAlBb,OAkBaA;;;EACFC,OAAO;;EAECC,QAAQ,oBAAIC,IAAAA;EAE7BC,SAASC,KAA2B;AAClC,UAAMC,SAASD,IAAIE,UAAUC,IAAIC,gBAAgB;MAAEC,QAAQ;IAAM,CAAA;AACjE,UAAMC,SAASC,MAAMC,UAAU,oCAAA;AAE/BP,WAAOQ,UAAU,CAACC,UAAAA;AAChB,YAAMC,SAAS,6BACbX,IAAIW,OAAO;QACTf,MAAM,KAAKA;QACXgB,SAAS;UACPF,OAAOA,MAAMd;UACbiB,UAAUH,MAAMG;UAChBC,OAAOJ,MAAMI;UACbC,KAAKL,MAAMK;UACXC,MAAMN,MAAMM;UACZC,MAAMP,MAAMO;UACZC,QAAQR,MAAMQ;UACdC,OAAOT,MAAMS;UACbC,YAAYV,MAAMU;QACpB;QACAC,MAAM,KAAKA,KAAKX,KAAAA;MAClB,CAAA,GAfa;AAkBf,UAAIA,MAAMd,SAAS,eAAe;AAChC,aAAKC,MAAMyB,IACTZ,MAAMI,OACNR,OAAOiB,UAAU,YAAYb,MAAMG,YAAY,GAAA,IAAO;UACpDW,YAAY;YAAE,kBAAkBd,MAAMI;YAAO,oBAAoBJ,MAAMG;UAAS;QAClF,CAAA,CAAA;MAEJ;AACA,YAAMY,OAAO,KAAK5B,MAAMM,IAAIO,MAAMI,KAAK;AACvC,YAAMY,OAAOD,OAAOlB,MAAMoB,QAAQC,QAAQC,OAAM,GAAIJ,IAAAA,IAAQG,QAAQC,OAAM;AAK1E,UAAInB,MAAMM,QAAQ,QAAQN,MAAMK,OAAO,MAAM;AAC3C,cAAMe,OAAOxB,OAAOiB,UAClB,QAAQb,MAAMM,IAAI,IAClB;UACEQ,YAAY;YACV,kBAAkBd,MAAMI;YACxB,oBAAoBJ,MAAMK;YAC1B,qBAAqBL,MAAMO;UAC7B;QACF,GACAS,IAAAA;AAEFE,gBAAQG,KAAKxB,MAAMoB,QAAQD,MAAMI,IAAAA,GAAOnB,MAAAA;AACxCmB,aAAKE,IAAG;MACV,OAAO;AACLJ,gBAAQG,KAAKL,MAAMf,MAAAA;MACrB;AAGA,UAAID,MAAMd,SAAS,mBAAmBc,MAAMd,SAAS,iBAAiB;AACpE6B,cAAMO,IAAAA;AACN,aAAKnC,MAAMoC,OAAOvB,MAAMI,KAAK;MAC/B,WAAWJ,MAAMd,SAAS,cAAc;AACtC,cAAMsC,UAAUxB,MAAMS,OAAOe;AAC7BT,cAAMU,UACJD,YAAYE,SACR;UAAEC,MAAMC,eAAeC;UAAOL;QAAQ,IACtC;UAAEG,MAAMC,eAAeC;QAAM,CAAA;AAEnCd,cAAMO,IAAAA;AACN,aAAKnC,MAAMoC,OAAOvB,MAAMI,KAAK;MAC/B;IACF,CAAA;EACF;EAEQO,KAAKX,OAA8B;AACzC,UAAMW,OAAO;MAAC;MAAWX,MAAMd;MAAM,OAAOc,MAAMI,KAAK;;AACvD,QAAIJ,MAAMG,SAAUQ,MAAKmB,KAAK,YAAY9B,MAAMG,QAAQ,EAAE;AAC1D,QAAIH,MAAMO,KAAMI,MAAKmB,KAAK,QAAQ9B,MAAMO,IAAI,EAAE;AAC9C,QAAIP,MAAMd,SAAS,aAAcyB,MAAKmB,KAAK,QAAA;AAC3C,WAAOnB;EACT;AACF;;;ACtGA,SAASoB,gCAAgC;;;ACUlC,SAASC,iBACdC,OAA8D,CAAC,GAAC;AAEhE,QAAMC,UAAUD,KAAKC,WAAW;AAChC,QAAMC,WAAWF,KAAKG,0BAA0B,KAAK,KAAK,KAAK;AAC/D,SAAO;IACLC,IAAI;IACJC,OAAO;IACPC,QAAQ,CAAA;IACRC,UAAU;MACR;QACEC,OAAO;QACPC,MAAM;QACNH,QAAQ;UACN;YACEI,MAAM;YACNF,OAAO;YACPG,MAAM;cAAEC,UAAU;YAAsB;YACxCC,KAAK;YACLC,QAAQ;YACRC,YAAY;cAAEC,MAAM;cAAMC,KAAK;cAAMC,WAAW;YAAW;UAC7D;UACA;YACER,MAAM;YACNF,OAAO;YACPG,MAAM;cAAEC,UAAU;cAAoBO,OAAO;gBAAEC,QAAQ;cAAM;YAAE;YAC/DN,QAAQ;YACRO,OAAO;YACPN,YAAY;cAAEC,MAAM;cAAMC,KAAK;cAAMC,WAAW;YAAS;UAC3D;UACA;YACER,MAAM;YACNF,OAAO;YACPG,MAAM;cAAEC,UAAU;cAAiBO,OAAO;gBAAEG,QAAQ;cAAU;YAAE;YAChED,OAAO;YACPN,YAAY;cAAEC,MAAM;cAAIC,KAAK;cAAKC,WAAW;YAAS;UACxD;UACA;YACER,MAAM;YACNF,OAAO;YACPG,MAAM;cAAEC,UAAU;YAAqB;YACvCE,QAAQ;YACRO,OAAO;UACT;;MAEJ;MACA;QACEb,OAAO;QACPC,MAAM;QACNH,QAAQ;UACN;YACEI,MAAM;YACNF,OAAO;YACPG,MAAM;cAAEC,UAAU;cAAsBO,OAAO;gBAAEC,QAAQ;cAAc;YAAE;YACzEG,OAAO;UACT;UACA;YACEb,MAAM;YACNF,OAAO;YACPG,MAAM;cAAEC,UAAU;cAA0BO,OAAO;gBAAEjB;cAAS;YAAE;YAChEsB,SAAS;cACP;gBAAEC,KAAK;gBAAapB,OAAO;cAAU;cACrC;gBAAEoB,KAAK;gBAAYpB,OAAO;cAAW;cACrC;gBAAEoB,KAAK;gBAASpB,OAAO;gBAAOqB,MAAM;kBAAEC,MAAM1B;gBAAQ;cAAE;cACtD;gBAAEwB,KAAK;gBAASpB,OAAO;cAAQ;;UAEnC;UACA;YACEK,MAAM;YACNF,OAAO;YACPG,MAAM;cAAEC,UAAU;YAAuB;YACzCY,SAAS;cACP;gBAAEC,KAAK;gBAASpB,OAAO;cAAQ;cAC/B;gBAAEoB,KAAK;gBAAUpB,OAAO;cAAS;cACjC;gBAAEoB,KAAK;gBAAepB,OAAO;cAAU;cACvC;gBAAEoB,KAAK;gBAAUpB,OAAO;cAAS;;UAErC;;MAEJ;MACA;QACEG,OAAO;QACPC,MAAM;QACNH,QAAQ;UACN;YACEI,MAAM;YACNF,OAAO;YACPG,MAAM;cAAEC,UAAU;YAAuB;YACzCgB,QAAQ;cAAC;cAAQ;;YACjBC,OAAO;UACT;UACA;YACEnB,MAAM;YACNF,OAAO;YACPG,MAAM;cAAEC,UAAU;YAAmB;YACrCkB,SAAS;cAAC;cAAO;cAAO;;YACxBhB,QAAQ;UACV;UACA;YACEJ,MAAM;YACNF,OAAO;YACPG,MAAM;cAAEC,UAAU;YAAyB;YAC3CiB,OAAO;UACT;;MAEJ;;EAEJ;AACF;AA5GgB9B;;;ACVhB,SAASgC,aAAaC,kBAAAA,uBAAsB;AAG5C,SAASC,yBAAyB;AAKlC,SAASC,WAAWC,QAAkBC,GAAS;AAC7C,MAAID,OAAOE,WAAW,EAAG,QAAO;AAChC,QAAMC,MAAMC,KAAKC,IAAIL,OAAOE,SAAS,GAAGE,KAAKE,KAAML,IAAI,MAAOD,OAAOE,MAAM,IAAI,CAAA;AAC/E,SAAOF,OAAOI,KAAKG,IAAI,GAAGJ,GAAAA,CAAAA,KAAS;AACrC;AAJSJ;AAST,eAAeS,aAAaC,KAAqB;AAC/C,QAAMC,UAAUD,IAAIE,UAAUC,IAAIC,mBAAmB;IAAEC,QAAQ;EAAM,CAAA;AAGrE,QAAMC,OAAO,MAAML,QAAQE,IAAI;IAAEI,MAAM;IAAWC,OAAO;EAAM,CAAA;AAC/D,SAAOF,KAAKG;AACd;AANeV;AAaf,SAASW,aACPC,SACAC,UACAC,KAAW;AAEX,QAAMC,QAAQD,MAAMD;AACpB,QAAMG,YAAYD,QAAQF;AAC1B,SAAO;IACLI,SAASL,QAAQM,OAAO,CAACC,MAAAA;AACvB,YAAMC,IAAID,EAAEE,YAAY,CAAC,IAAIC,KAAKH,EAAEE,SAAS,IAAI;AACjD,aAAOD,IAAIL,SAASK,KAAKN;IAC3B,CAAA;IACAS,UAAUX,QAAQM,OAAO,CAACC,MAAAA;AACxB,YAAMC,IAAID,EAAEE,YAAY,CAAC,IAAIC,KAAKH,EAAEE,SAAS,IAAI;AACjD,aAAOD,IAAIJ,aAAaI,KAAKL;IAC/B,CAAA;EACF;AACF;AAjBSJ;AAoBT,SAASa,cAAcZ,SAAuB;AAC5C,MAAIa,YAAY;AAChB,MAAIC,SAAS;AACb,aAAWP,KAAKP,SAAS;AACvB,UAAMe,IAAKR,EAAES,WAAW,CAAC;AACzB,QAAID,EAAEE,UAAU,gBAAiBJ,cAAa;aACrCE,EAAEE,UAAU,aAAcH,WAAU;EAC/C;AACA,QAAMI,QAAQL,YAAYC;AAC1B,SAAOI,UAAU,IAAI,IAAIL,YAAYK;AACvC;AAVSN;AAYT,IAAMO,YAAY;AAKX,SAASC,uBAAAA;AACd,SAAO;IACLC,MAAM;IACN,MAAMC,QAAQC,OAAOlC,KAAqB;AACxC,YAAMmC,QAAQnC,IAAIE,UAAUC,IAAIiC,aAAa;QAAE/B,QAAQ;MAAM,CAAA;AAC7D,YAAMgC,SAAUH,OAAOG,UAAwB;AAC/C,YAAMC,OAAO,MAAMH,MAAMI,SAAS;QAAEF;QAAQ7B,OAAOsB;MAAU,CAAA;AAC7D,aAAO;QAAEU,OAAOF,KAAK7C;MAAO;IAC9B;EACF;AACF;AAVgBsC;AAiBT,SAASU,4BAAAA;AACd,SAAO;IACLT,MAAM;IACN,MAAMC,QAAQC,OAAOlC,KAAqB;AAExC,YAAMC,UAAUD,IAAIE,UAAUC,IAAIC,mBAAmB;QAAEC,QAAQ;MAAM,CAAA;AAKrE,YAAMG,QAAQb,KAAKC,IAAI,KAAOD,KAAKG,IAAI,KAAK4C,OAAOR,OAAO1B,SAAS,GAAA,CAAA,CAAA;AACnE,YAAMF,OAAO,MAAML,QAAQE,IAAI;QAAEI,MAAM;QAAWC;MAAM,CAAA;AAExD,UAAIgB,YAAY;AAChB,UAAIC,SAAS;AACb,YAAMkB,iBAAiB,oBAAIC,IAAAA;AAC3B,iBAAW1B,KAAKZ,KAAKG,MAAM;AACzB,cAAMiB,IAAKR,EAAES,WAAW,CAAC;AACzB,YAAID,EAAEE,UAAU,gBAAiBJ,cAAa;iBACrCE,EAAEE,UAAU,cAAc;AACjCH,oBAAU;AACV,gBAAMoB,KAAKnB,EAAEoB,YAAY;AACzBH,yBAAeI,IAAIF,KAAKF,eAAexC,IAAI0C,EAAAA,KAAO,KAAK,CAAA;QACzD;MACF;AACA,YAAMhB,QAAQL,YAAYC;AAC1B,YAAMuB,SAAUd,OAAOc,UAAqB;AAC5C,UAAIA,WAAW,cAAe,QAAO;QAAER,OAAOX,UAAU,IAAI,IAAIL,YAAYK;MAAM;AAClF,UAAImB,WAAW,SAAU,QAAO;QAAER,OAAOf;MAAO;AAChD,UAAIuB,WAAW,QAAS,QAAO;QAAER,OAAOX;MAAM;AAC9C,UAAImB,WAAW,eAAe;AAC5B,cAAMC,QAAQ;aAAIN,eAAehC,QAAO;UACrCuC,IAAI,CAAC,CAACC,OAAOX,KAAAA,OAAY;UAAEW;UAAOX;QAAM,EAAA,EACxCY,KAAK,CAACC,GAAGC,MAAMA,EAAEd,QAAQa,EAAEb,KAAK;AACnC,eAAO;UAAES;QAAM;MACjB;AACA,aAAO;QAAET,OAAOX;MAAM;IACxB;EACF;AACF;AAvCgBY;AAgDT,SAASc,gCAAAA;AACd,SAAO;IACLvB,MAAM;IACN,MAAMC,QAAQC,OAAOlC,KAAqB;AACxC,YAAMmC,QAAQnC,IAAIE,UAAUC,IAAIiC,aAAa;QAAE/B,QAAQ;MAAM,CAAA;AAC7D,YAAMG,QAAQb,KAAKC,IAAI,KAAKD,KAAKG,IAAI,IAAI4C,OAAOR,OAAO1B,SAAS,EAAA,CAAA,CAAA;AAChE,YAAMI,WAAWsB,OAAOtB,aAAa4C,SAAY,KAAK,KAAK,KAAK,MAAOd,OAAOR,MAAMtB,QAAQ;AAC5F,YAAM6C,SAAS7C,WAAW,IAAIS,KAAKR,IAAG,IAAKD,WAAW;AACtD,YAAM,CAACa,QAAQiC,IAAAA,IAAQ,MAAMC,QAAQC,IAAI;QACvCzB,MAAMI,SAAS;UAAEF,QAAQ;UAAU7B;QAAM,CAAA;QACzC2B,MAAMI,SAAS;UAAEF,QAAQ;UAAQ7B;QAAM,CAAA;OACxC;AACD,YAAMqD,OAAO;WAAIpC;WAAWiC;QACzBzC,OAAO,CAAC6C,MAAM,CAAC,IAAIzC,KAAKyC,EAAEC,SAAS,KAAKN,MAAAA,EACxCL,KAAK,CAACC,GAAGC,MAAM,CAAC,IAAIjC,KAAKiC,EAAES,SAAS,IAAI,CAAC,IAAI1C,KAAKgC,EAAEU,SAAS,CAAA,EAC7DC,MAAM,GAAGxD,KAAAA,EACT0C,IAAI,CAACY,OAAO;;;QAGXC,WAAW,GAAG,IAAI1C,KAAKyC,EAAEC,SAAS,EAAEE,YAAW,EAAGC,QAAQ,KAAK,GAAA,EAAKF,MAAM,GAAG,EAAA,CAAA;QAC7ElB,UAAUgB,EAAEhB;QACZqB,OAAOL,EAAEM;QACTC,OAAOP,EAAEO,OAAOC,WAAW;MAC7B,EAAA;AACF,aAAO;QAAET;MAAK;IAChB;EACF;AACF;AA3BgBN;AAmCT,SAASgB,8BAAAA;AACd,SAAO;IACLvC,MAAM;IACN,MAAMC,QAAQC,OAAOlC,KAAqB;AACxC,YAAMwE,SAASxE,IAAIE,UAAUC,IAAIsE,iBAAgB;QAAEpE,QAAQ;MAAM,CAAA;AACjE,YAAMqE,SAAS,MAAMF,OAAOG,aAAY;AACxC,YAAMC,YAAY,wBAACC,MACjBA,EAAEC,QAAQ,KAAKD,EAAEE,YAAYtF,WAAW,GADxB;AAElB,UAAKyC,OAAOc,WAAsB,gBAAgB;AAChD,eAAO;UAAER,OAAOkC,OAAOzD,OAAO2D,SAAAA,EAAWnF;QAAO;MAClD;AACA,YAAMoE,OAAOa,OACVV,MAAK,EAELZ,KAAK,CAACC,GAAGC,MAAMZ,OAAOkC,UAAUtB,CAAAA,CAAAA,IAAMZ,OAAOkC,UAAUvB,CAAAA,CAAAA,KAAOC,EAAEwB,QAAQzB,EAAEyB,KAAK,EAC/E5B,IAAI,CAAC2B,OAAO;QACXG,OAAOH,EAAEG;QACTC,QAAQJ,EAAEC;QACVC,aAAaF,EAAEE,YAAYtF;QAC3B4C,QAAQuC,UAAUC,CAAAA,IAAK,YAAY;MACrC,EAAA;AACF,aAAO;QAAEhB;MAAK;IAChB;EACF;AACF;AAxBgBU;AAoCT,SAASW,0BAAAA;AACd,SAAO;IACLlD,MAAM;IACN,MAAMC,QAAQC,OAAOlC,KAAqB;AACxC,YAAMW,UAAU,MAAMZ,aAAaC,GAAAA;AAGnC,YAAMmF,YAAY,oBAAIvC,IAAAA;AACtB,iBAAW1B,KAAKP,SAAS;AACvB,cAAMe,IAAKR,EAAES,WAAW,CAAC;AACzB,YAAID,EAAEE,UAAU,iBAAiBF,EAAEyC,SAASjD,EAAEE,WAAW;AACvD+D,oBAAUpC,IAAIrB,EAAEyC,OAAO,CAAC,IAAI9C,KAAKH,EAAEE,SAAS,CAAA;QAC9C;MACF;AAEA,YAAMgE,OAAiB,CAAA;AACvB,iBAAWlE,KAAKP,SAAS;AACvB,cAAMe,IAAKR,EAAES,WAAW,CAAC;AACzB,YAAID,EAAEE,UAAU,mBAAmBF,EAAEE,UAAU,cAAc;AAC3D,cAAI,OAAOF,EAAE2D,eAAe,UAAU;AACpCD,iBAAKE,KAAK5D,EAAE2D,UAAU;UACxB,WAAW3D,EAAEyC,OAAO;AAClB,kBAAMrD,QAAQqE,UAAUhF,IAAIuB,EAAEyC,KAAK;AACnC,kBAAMoB,MAAMrE,EAAEE,YAAY,CAAC,IAAIC,KAAKH,EAAEE,SAAS,IAAIoC;AACnD,gBAAI1C,UAAU0C,UAAa+B,QAAQ/B,UAAa+B,OAAOzE,OAAO;AAC5DsE,mBAAKE,KAAKC,MAAMzE,KAAAA;YAClB;UACF;QACF;MACF;AACAsE,WAAKhC,KAAK,CAACC,GAAGC,MAAMD,IAAIC,CAAAA;AAExB,YAAMkC,MAAMlG,WAAW8F,MAAM,EAAA;AAC7B,YAAMK,MAAMnG,WAAW8F,MAAM,EAAA;AAC7B,YAAMM,MAAMpG,WAAW8F,MAAM,EAAA;AAG7B,YAAMpC,SAAUd,OAAmCc;AACnD,UAAIA,WAAW,MAAO,QAAO;QAAER,OAAOgD;MAAI;AAC1C,UAAIxC,WAAW,MAAO,QAAO;QAAER,OAAOiD;MAAI;AAC1C,UAAIzC,WAAW,MAAO,QAAO;QAAER,OAAOkD;MAAI;AAG1C,YAAM5F,MAAMsF,KAAKO,GAAG,EAAC,KAAM;AAC3B,YAAMC,cAAc;AACpB,YAAMC,OAAOlG,KAAKG,IAAI,GAAGH,KAAKE,MAAMC,MAAM,KAAK8F,WAAAA,CAAAA;AAC/C,YAAME,UAAUC,MAAMC,KAAK;QAAEvG,QAAQmG;MAAY,GAAG,CAACK,GAAGC,OAAO;QAC7D/C,OAAO,GAAGxD,KAAKwG,MAAOD,IAAIL,OAAQ,GAAA,IAAO,EAAA;QACzCO,OAAO;MACT,EAAA;AACA,iBAAWC,KAAKjB,MAAM;AACpB,cAAMkB,KAAK3G,KAAKC,IAAIgG,cAAc,GAAGjG,KAAK4G,MAAMF,IAAIR,IAAAA,CAAAA;AACpD,cAAMW,SAASV,QAAQQ,EAAAA;AACvB,YAAIE,OAAQA,QAAOJ,SAAS;MAC9B;AAEA,aAAO;QAAEN;QAASN;QAAKC;QAAKC;MAAI;IAClC;EACF;AACF;AA3DgBR;AAiET,SAASuB,8BAAAA;AACd,SAAO;IACLzE,MAAM;IACN,MAAMC,QAAQC,OAAOlC,KAAqB;AACxC,YAAMW,UAAU,MAAMZ,aAAaC,GAAAA;AACnC,YAAM0G,IAAIhE,OAAQR,OAAmC4D,WAAW,EAAA;AAChE,YAAMjF,MAAMQ,KAAKR,IAAG;AAGpB,UAAI8F,OAAO9F;AACX,iBAAWK,KAAKP,SAAS;AACvB,YAAIO,EAAEE,WAAW;AACf,gBAAMD,IAAI,CAAC,IAAIE,KAAKH,EAAEE,SAAS;AAC/B,cAAID,IAAIwF,KAAMA,QAAOxF;QACvB;MACF;AACA,YAAMyF,OAAOjH,KAAKG,IAAIe,MAAM8F,MAAM,CAAA;AAClC,YAAME,aAAaD,OAAOF;AAE1B,YAAM7C,OAA+DkC,MAAMC,KACzE;QAAEvG,QAAQiH;MAAE,GACZ,CAACT,GAAGC,MAAAA;AACF,cAAMY,cAAc,IAAIzF,KAAKsF,OAAOT,IAAIW,UAAAA;AACxC,cAAM1D,QAAQ2D,YAAY7C,YAAW,EAAGD,MAAM,IAAI,EAAA;AAClD,eAAO;UAAEb;UAAO4D,MAAM;UAAGtF,QAAQ;QAAE;MACrC,CAAA;AAGF,iBAAWP,KAAKP,SAAS;AACvB,cAAMe,IAAKR,EAAES,WAAW,CAAC;AACzB,YAAID,EAAEE,UAAU,mBAAmBF,EAAEE,UAAU,aAAc;AAC7D,cAAMT,IAAID,EAAEE,YAAY,CAAC,IAAIC,KAAKH,EAAEE,SAAS,IAAI;AACjD,cAAM1B,MAAMC,KAAKC,IAAI8G,IAAI,GAAG/G,KAAK4G,OAAOpF,IAAIwF,QAAQE,UAAAA,CAAAA;AACpD,cAAMG,MAAMnD,KAAKnE,GAAAA;AACjB,YAAIsH,KAAK;AACP,cAAItF,EAAEE,UAAU,gBAAiBoF,KAAID,QAAQ;cACxCC,KAAIvF,UAAU;QACrB;MACF;AAEA,aAAO;QAAEoC;MAAK;IAChB;EACF;AACF;AA3CgB4C;AAiDT,SAASQ,6BAAAA;AACd,SAAO;IACLjF,MAAM;IACN,MAAMC,QAAQC,OAAOlC,KAAqB;AACxC,YAAMW,UAAU,MAAMZ,aAAaC,GAAAA;AACnC,YAAMY,WAAW8B,OAAQR,OAAmCtB,YAAY,KAAK,KAAK,KAAK,GAAA;AACvF,YAAMC,MAAMQ,KAAKR,IAAG;AACpB,YAAM,EAAEG,SAASM,SAAQ,IAAKZ,aAAaC,SAASC,UAAUC,GAAAA;AAE9D,YAAM2B,QAAQjB,cAAcP,OAAAA;AAC5B,YAAMkG,WAAW3F,cAAcD,QAAAA;AAC/B,YAAM6F,QAAQ7F,SAAS7B,SAAS,IAAI+C,QAAQ0E,WAAW1D;AAGvD,YAAM4D,eAAe;AACrB,YAAMP,aAAajG,WAAWwG;AAC9B,YAAMC,aAAaxG,MAAMD;AACzB,YAAM0G,QAAQvB,MAAMC,KAAK;QAAEvG,QAAQ2H;MAAa,GAAG,CAACnB,GAAGC,MAAAA;AACrD,cAAMqB,SAASF,aAAanB,IAAIW;AAChC,cAAMW,OAAOD,SAASV;AACtB,cAAMY,WAAWzG,QAAQC,OAAO,CAACC,MAAAA;AAC/B,gBAAMC,IAAID,EAAEE,YAAY,CAAC,IAAIC,KAAKH,EAAEE,SAAS,IAAI;AACjD,iBAAOD,IAAIoG,UAAUpG,KAAKqG;QAC5B,CAAA;AACA,eAAOjG,cAAckG,QAAAA;MACvB,CAAA;AAEA,aAAO;QAAEjF;QAAO2E;QAAOG;MAAM;IAC/B;EACF;AACF;AA9BgBL;AAoCT,SAASS,4BAAAA;AACd,SAAO;IACL1F,MAAM;IACN,MAAMC,QAAQC,OAAOlC,KAAqB;AACxC,YAAMW,UAAU,MAAMZ,aAAaC,GAAAA;AACnC,YAAMY,WAAW8B,OAAQR,OAAmCtB,YAAY,KAAK,KAAK,KAAK,GAAA;AACvF,YAAMC,MAAMQ,KAAKR,IAAG;AACpB,YAAM,EAAEG,SAASM,SAAQ,IAAKZ,aAAaC,SAASC,UAAUC,GAAAA;AAE9D,YAAM8G,iBAAiB,wBAACC,OACtBA,GAAG3G,OAAO,CAACC,OAAQA,EAAES,WAAW,CAAC,GAA0BC,UAAU,eAAA,EAClEnC,QAFkB;AAIvB,YAAMoI,cAAcjH,YAAY,KAAK,KAAK;AAC1C,YAAM4B,QAAQmF,eAAe3G,OAAAA,IAAW6G;AACxC,YAAMC,YAAYH,eAAerG,QAAAA,IAAYuG;AAC7C,YAAMV,QAAQ7F,SAAS7B,SAAS,IAAI+C,QAAQsF,YAAYtE;AAGxD,YAAM4D,eAAe;AACrB,YAAMP,aAAajG,WAAWwG;AAC9B,YAAMW,cAAclB,cAAc,KAAK,KAAK;AAC5C,YAAMQ,aAAaxG,MAAMD;AACzB,YAAM0G,QAAQvB,MAAMC,KAAK;QAAEvG,QAAQ2H;MAAa,GAAG,CAACnB,GAAGC,MAAAA;AACrD,cAAMqB,SAASF,aAAanB,IAAIW;AAChC,cAAMW,OAAOD,SAASV;AACtB,cAAMY,WAAWzG,QAAQC,OAAO,CAACC,MAAAA;AAC/B,gBAAMC,IAAID,EAAEE,YAAY,CAAC,IAAIC,KAAKH,EAAEE,SAAS,IAAI;AACjD,iBAAOD,IAAIoG,UAAUpG,KAAKqG;QAC5B,CAAA;AACA,eAAOG,eAAeF,QAAAA,IAAYM;MACpC,CAAA;AAEA,aAAO;QAAEvF;QAAO2E;QAAOG;MAAM;IAC/B;EACF;AACF;AApCgBI;AAuChB,IAAMM,0BAA0B;EAAC;EAAW;EAAW;EAAW;EAAW;;AAC7E,IAAMC,2BAAwC;EAAC;EAAW;EAAW;EAAa;EAAU;;AAMrF,SAASC,gCAAAA;AACd,SAAO;IACLlG,MAAM;IACN,MAAMC,QAAQkG,QAAQnI,KAAqB;AACzC,YAAMmC,QAAQnC,IAAIE,UAAUC,IAAIiC,aAAa;QAAE/B,QAAQ;MAAM,CAAA;AAC7D,YAAM+H,SAAS,MAAMzE,QAAQC,IAC3BqE,yBAAyB/E,IAAI,CAACb,WAC5BF,MAAMI,SAAS;QAAEF;QAAQ7B,OAAOsB;MAAU,CAAA,EAAGuG,KAAK,CAAC/F,SAASA,KAAK7C,MAAM,CAAA,CAAA;AAG3E,YAAM6I,WAAWL,yBAAyB/E,IAAI,CAACC,OAAO+C,OAAO;QAC3D/C;QACAX,OAAO4F,OAAOlC,CAAAA;QACdqC,OAAOP,wBAAwB9B,CAAAA;MACjC,EAAA;AACA,aAAO;QAAEoC;MAAS;IACpB;EACF;AACF;AAlBgBJ;;;AF/XT,SAASM,0BACdC,OAA8D,CAAC,GAAC;AAEhE,SAAOC,yBAAyB;IAC9BC,MAAM;IACNC,UAAU,6BAAM;MAAC,IAAIC,wBAAAA;OAAX;IACVC,YAAY,6BAAM;MAAC;QAAEC,IAAI;QAAWC,OAAO;QAAaC,KAAK;MAAe;OAAhE;IACZC,YAAY,6BAAM;MAACC,iBAAiBV,IAAAA;OAAxB;IACZW,eAAe,6BAAM;MACnBC,qBAAAA;MACAC,0BAAAA;MACAC,8BAAAA;MACAC,4BAAAA;MACAC,wBAAAA;MACAC,4BAAAA;MACAC,2BAAAA;MACAC,0BAAAA;MACAC,8BAAAA;OATa;EAWjB,CAAA;AACF;AApBgBrB;","names":["WorkflowEngine","SpanStatusCode","context","trace","DurableTelescopeWatcher","type","roots","Map","register","ctx","engine","moduleRef","get","WorkflowEngine","strict","tracer","trace","getTracer","subscribe","event","record","content","workflow","runId","seq","name","kind","output","error","durationMs","tags","set","startSpan","attributes","root","base","setSpan","context","active","step","with","end","delete","message","setStatus","undefined","code","SpanStatusCode","ERROR","push","defineTelescopeExtension","durableDashboard","opts","runHref","windowMs","recentFailuresWindowMs","id","label","panels","sections","title","cols","kind","data","provider","max","format","thresholds","warn","bad","direction","query","metric","spark","status","limit","columns","key","link","href","series","style","markers","STATE_STORE","WorkflowEngine","TELESCOPE_STORAGE","percentile","sorted","p","length","idx","Math","min","ceil","max","fetchEntries","ctx","storage","moduleRef","get","TELESCOPE_STORAGE","strict","page","type","limit","data","splitWindows","entries","windowMs","now","start","prevStart","current","filter","e","t","createdAt","Date","previous","successRateOf","completed","failed","c","content","event","total","STATE_CAP","durableStateProvider","name","resolve","query","store","STATE_STORE","status","runs","listRuns","value","durableTimeseriesProvider","Number","failByWorkflow","Map","wf","workflow","set","metric","items","map","label","sort","a","b","durableRecentFailuresProvider","undefined","cutoff","dead","Promise","all","rows","r","updatedAt","slice","toISOString","replace","runId","id","error","message","durableWorkerHealthProvider","engine","WorkflowEngine","health","workerHealth","isStarved","g","depth","liveWorkers","group","queued","durableDurationProvider","startedAt","durs","durationMs","push","end","p50","p95","p99","at","bucketCount","size","buckets","Array","from","_","i","round","count","d","bi","floor","bucket","durableRunsOverTimeProvider","n","minT","span","bucketSize","bucketStart","done","row","durableSuccessRateProvider","prevRate","delta","sparkBuckets","sparkStart","spark","bStart","bEnd","bEntries","durableThroughputProvider","countCompleted","es","windowHours","prevValue","bucketHours","STATE_BREAKDOWN_PALETTE","STATE_BREAKDOWN_STATUSES","durableStateBreakdownProvider","_query","counts","then","segments","color","durableTelescopeExtension","opts","defineTelescopeExtension","name","watchers","DurableTelescopeWatcher","entryTypes","id","label","dot","dashboards","durableDashboard","dataProviders","durableStateProvider","durableTimeseriesProvider","durableRecentFailuresProvider","durableWorkerHealthProvider","durableDurationProvider","durableRunsOverTimeProvider","durableSuccessRateProvider","durableThroughputProvider","durableStateBreakdownProvider"]}
|
|
1
|
+
{"version":3,"sources":["../src/durable-telescope.watcher.ts","../src/durable-telescope.extension.ts","../src/durable-dashboard.spec-data.ts","../src/durable-data-providers.ts"],"sourcesContent":["import { type EngineEvent, WorkflowEngine } from '@dudousxd/nestjs-durable-core';\nimport type { Watcher, WatcherContext } from '@dudousxd/nestjs-telescope';\nimport { type Span, SpanStatusCode, context, trace } from '@opentelemetry/api';\n\n/**\n * A Telescope watcher that turns durable-workflow lifecycle events into Telescope entries, so\n * runs and steps (including remote/Python steps) show up alongside the app's requests, queries\n * and jobs in the Telescope UI — under the **Workflows** tab.\n *\n * It also **trace-groups** a run: a root span is opened per run and every entry is recorded inside\n * that span's OTel context (step entries inside a child `step …` span). Telescope's trace-context\n * provider stamps each entry with the active span, so a whole workflow shows up as a single trace\n * on the Traces page — run → steps — with each entry carrying the run's `traceId`. When the host has\n * no OTel SDK the spans are no-ops and entries simply carry no trace (the prior behaviour).\n *\n * Add it to `TelescopeModule.forRoot({ watchers: [new DurableTelescopeWatcher()] })`. It resolves\n * the engine from the (global) durable providers and subscribes to its events.\n */\nexport class DurableTelescopeWatcher implements Watcher {\n readonly type = 'durable';\n /** Open root span per run, so step entries nest under it and share its traceId. */\n private readonly roots = new Map<string, Span>();\n\n register(ctx: WatcherContext): void {\n const engine = ctx.moduleRef.get(WorkflowEngine, { strict: false });\n const tracer = trace.getTracer('@dudousxd/nestjs-durable-telescope');\n\n engine.subscribe((event) => {\n const record = () =>\n ctx.record({\n type: this.type,\n content: {\n event: event.type,\n workflow: event.workflow,\n runId: event.runId,\n seq: event.seq,\n name: event.name,\n kind: event.kind,\n output: event.output,\n error: event.error,\n durationMs: event.durationMs,\n },\n tags: this.tags(event),\n });\n\n // Open the run's root span on first sight; later events nest under it.\n if (event.type === 'run.started') {\n this.roots.set(\n event.runId,\n tracer.startSpan(`workflow ${event.workflow ?? '?'}`, {\n attributes: { 'durable.run_id': event.runId, 'durable.workflow': event.workflow },\n }),\n );\n }\n const root = this.roots.get(event.runId);\n const base = root ? trace.setSpan(context.active(), root) : context.active();\n\n // A step/signal/sleep event → give it its own child span so it's a distinct node in the\n // trace; run-level events record directly on the root. Either way the active span at record\n // time carries the run's traceId, so all entries group into one trace.\n if (event.name != null && event.seq != null) {\n const step = tracer.startSpan(\n `step ${event.name}`,\n {\n attributes: {\n 'durable.run_id': event.runId,\n 'durable.step.seq': event.seq,\n 'durable.step.kind': event.kind,\n },\n },\n base,\n );\n context.with(trace.setSpan(base, step), record);\n step.end();\n } else {\n context.with(base, record);\n }\n\n // Close the run's root span when it leaves the running state.\n if (event.type === 'run.completed' || event.type === 'run.suspended') {\n root?.end();\n this.roots.delete(event.runId);\n } else if (event.type === 'run.failed') {\n const message = event.error?.message;\n root?.setStatus(\n message !== undefined\n ? { code: SpanStatusCode.ERROR, message }\n : { code: SpanStatusCode.ERROR },\n );\n root?.end();\n this.roots.delete(event.runId);\n }\n });\n }\n\n private tags(event: EngineEvent): string[] {\n const tags = ['durable', event.type, `run:${event.runId}`];\n if (event.workflow) tags.push(`workflow:${event.workflow}`);\n if (event.kind) tags.push(`kind:${event.kind}`);\n if (event.type === 'run.failed') tags.push('failed');\n return tags;\n }\n}\n","import { defineTelescopeExtension } from '@dudousxd/nestjs-telescope';\nimport { durableDashboard } from './durable-dashboard.spec-data';\nimport {\n durableDurationProvider,\n durableRecentFailuresProvider,\n durableRunsOverTimeProvider,\n durableStateBreakdownProvider,\n durableStateProvider,\n durableSuccessRateProvider,\n durableThroughputProvider,\n durableTimeseriesProvider,\n durableWorkerHealthProvider,\n} from './durable-data-providers';\nimport { DurableTelescopeWatcher } from './durable-telescope.watcher';\n\n/** The first-class Telescope extension for nestjs-durable: watcher + Workflows dashboard. */\nexport function durableTelescopeExtension(\n opts: { runHref?: string; recentFailuresWindowMs?: number } = {},\n) {\n return defineTelescopeExtension({\n name: 'durable',\n watchers: () => [new DurableTelescopeWatcher()],\n entryTypes: () => [{ id: 'durable', label: 'Workflows', dot: 'bg-amber-400' }],\n dashboards: () => [durableDashboard(opts)],\n dataProviders: () => [\n durableStateProvider(),\n durableTimeseriesProvider(),\n durableRecentFailuresProvider(),\n durableWorkerHealthProvider(),\n durableDurationProvider(),\n durableRunsOverTimeProvider(),\n durableSuccessRateProvider(),\n durableThroughputProvider(),\n durableStateBreakdownProvider(),\n ],\n });\n}\n","import type { DashboardSpec } from '@dudousxd/nestjs-telescope';\n\n/**\n * The \"Workflows\" health dashboard — golden-signals sections layout.\n *\n * `runHref` is a URL template for deep-linking a run out to the durable dashboard\n * (e.g. '/durable/runs/{runId}'). `recentFailuresWindowMs` bounds the \"Stuck runs\"\n * table (default 24h) so a healthy system shows an empty table instead of\n * days-old failures; pass `0` to show all.\n */\nexport function durableDashboard(\n opts: { runHref?: string; recentFailuresWindowMs?: number } = {},\n): DashboardSpec {\n const runHref = opts.runHref ?? '/durable/runs/{runId}';\n const windowMs = opts.recentFailuresWindowMs ?? 24 * 60 * 60 * 1000;\n return {\n id: 'durable.workflows',\n label: 'Workflows',\n panels: [],\n sections: [\n {\n title: 'Health',\n cols: 4,\n panels: [\n {\n kind: 'gauge',\n title: 'Success rate',\n data: { provider: 'durable.successRate' },\n max: 1,\n format: 'percent',\n thresholds: { warn: 0.98, bad: 0.95, direction: 'down-bad' },\n },\n {\n kind: 'stat',\n title: 'Duration p95',\n data: { provider: 'durable.duration', query: { metric: 'p95' } },\n format: 'duration',\n spark: false,\n thresholds: { warn: 2000, bad: 5000, direction: 'up-bad' },\n },\n {\n kind: 'stat',\n title: 'Backlog',\n data: { provider: 'durable.state', query: { status: 'pending' } },\n spark: false,\n thresholds: { warn: 50, bad: 200, direction: 'up-bad' },\n },\n {\n kind: 'stat',\n title: 'Throughput',\n data: { provider: 'durable.throughput' },\n format: 'rate',\n spark: true,\n },\n ],\n },\n {\n title: 'Needs attention',\n cols: 3,\n panels: [\n {\n kind: 'topN',\n title: 'Top failing workflows',\n data: { provider: 'durable.timeseries', query: { metric: 'topFailures' } },\n limit: 8,\n },\n {\n kind: 'table',\n title: 'Stuck runs',\n data: { provider: 'durable.recentFailures', query: { windowMs } },\n columns: [\n { key: 'updatedAt', label: 'Updated' },\n { key: 'workflow', label: 'Workflow' },\n { key: 'runId', label: 'Run', link: { href: runHref } },\n { key: 'error', label: 'Error' },\n ],\n },\n {\n kind: 'table',\n title: 'Starved worker groups',\n data: { provider: 'durable.workerHealth' },\n columns: [\n { key: 'group', label: 'Group' },\n { key: 'queued', label: 'Queued' },\n { key: 'liveWorkers', label: 'Workers' },\n { key: 'status', label: 'Status' },\n ],\n },\n ],\n },\n {\n title: 'Trends',\n cols: 3,\n panels: [\n {\n kind: 'timeseries',\n title: 'Runs over time',\n data: { provider: 'durable.runsOverTime' },\n series: ['done', 'failed'],\n style: 'stacked',\n },\n {\n kind: 'distribution',\n title: 'Duration distribution',\n data: { provider: 'durable.duration' },\n markers: ['p50', 'p95', 'p99'],\n format: 'duration',\n },\n {\n kind: 'breakdown',\n title: 'Runs by state',\n data: { provider: 'durable.stateBreakdown' },\n style: 'donut',\n },\n ],\n },\n ],\n };\n}\n","import { STATE_STORE_CANONICAL, WorkflowEngine } from '@dudousxd/nestjs-durable-core';\nimport type { RunStatus, StateStore } from '@dudousxd/nestjs-durable-core';\nimport type { DataProvider, ExtensionContext } from '@dudousxd/nestjs-telescope';\nimport { TELESCOPE_STORAGE } from '@dudousxd/nestjs-telescope';\n\n// ─── Shared helpers ───────────────────────────────────────────────────────────\n\n/** Compute the p-th percentile of a SORTED array. */\nfunction percentile(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0;\n const idx = Math.min(sorted.length - 1, Math.ceil((p / 100) * sorted.length) - 1);\n return sorted[Math.max(0, idx)] ?? 0;\n}\n\ntype StorageEntry = { content?: unknown; createdAt?: Date };\n\n/** Fetch the durable entry page from TELESCOPE_STORAGE. */\nasync function fetchEntries(ctx: ExtensionContext): Promise<StorageEntry[]> {\n const storage = ctx.moduleRef.get(TELESCOPE_STORAGE, { strict: false }) as {\n get(q: { type?: string; limit?: number }): Promise<{ data: StorageEntry[] }>;\n };\n const page = await storage.get({ type: 'durable', limit: 5_000 });\n return page.data;\n}\n\n/**\n * Split entries into current/previous equal-length windows.\n * current: (now - windowMs, now]\n * previous: (now - 2*windowMs, now - windowMs]\n */\nfunction splitWindows(\n entries: StorageEntry[],\n windowMs: number,\n now: number,\n): { current: StorageEntry[]; previous: StorageEntry[] } {\n const start = now - windowMs;\n const prevStart = start - windowMs;\n return {\n current: entries.filter((e) => {\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n return t > start && t <= now;\n }),\n previous: entries.filter((e) => {\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n return t > prevStart && t <= start;\n }),\n };\n}\n\n/** Compute success rate (completed / total) for a list of entries, returns 1 when no data. */\nfunction successRateOf(entries: StorageEntry[]): number {\n let completed = 0;\n let failed = 0;\n for (const e of entries) {\n const c = (e.content ?? {}) as { event?: string };\n if (c.event === 'run.completed') completed += 1;\n else if (c.event === 'run.failed') failed += 1;\n }\n const total = completed + failed;\n return total === 0 ? 1 : completed / total;\n}\n\nconst STATE_CAP = 10_000;\n\n// ─── Existing providers ───────────────────────────────────────────────────────\n\n/** Source C: current-state gauge from the durable store. query.status selects which (default 'dead'). */\nexport function durableStateProvider(): DataProvider {\n return {\n name: 'durable.state',\n async resolve(query, ctx: ExtensionContext) {\n const store = ctx.moduleRef.get(STATE_STORE_CANONICAL, { strict: false }) as StateStore;\n const status = (query?.status as RunStatus) ?? 'dead';\n const runs = await store.listRuns({ status, limit: STATE_CAP });\n return { value: runs.length };\n },\n };\n}\n\n/**\n * Source A: rollups from captured `durable` entries in Telescope's own storage.\n * Reads recent run.* lifecycle entries and aggregates. Bounded by Telescope's prune\n * window (by design — this is the \"history\" series, not the source of truth).\n */\nexport function durableTimeseriesProvider(): DataProvider {\n return {\n name: 'durable.timeseries',\n async resolve(query, ctx: ExtensionContext) {\n // TELESCOPE_STORAGE.get(query) → { data: Entry[]; nextCursor }. Entry = { content, createdAt, ... }.\n const storage = ctx.moduleRef.get(TELESCOPE_STORAGE, { strict: false }) as {\n get(q: { type?: string; limit?: number }): Promise<{\n data: Array<{ content?: unknown; createdAt?: Date }>;\n }>;\n };\n const limit = Math.min(5_000, Math.max(100, Number(query?.limit ?? 2_000)));\n const page = await storage.get({ type: 'durable', limit });\n\n let completed = 0;\n let failed = 0;\n const failByWorkflow = new Map<string, number>();\n for (const e of page.data) {\n const c = (e.content ?? {}) as { event?: string; workflow?: string };\n if (c.event === 'run.completed') completed += 1;\n else if (c.event === 'run.failed') {\n failed += 1;\n const wf = c.workflow ?? 'unknown';\n failByWorkflow.set(wf, (failByWorkflow.get(wf) ?? 0) + 1);\n }\n }\n const total = completed + failed;\n const metric = (query?.metric as string) ?? 'successRate';\n if (metric === 'successRate') return { value: total === 0 ? 1 : completed / total };\n if (metric === 'failed') return { value: failed };\n if (metric === 'total') return { value: total };\n if (metric === 'topFailures') {\n const items = [...failByWorkflow.entries()]\n .map(([label, value]) => ({ label, value }))\n .sort((a, b) => b.value - a.value);\n return { items };\n }\n return { value: total };\n },\n };\n}\n\n/**\n * Source C: recent failed + dead runs as table rows (newest first), time-bounded.\n * Only failures updated within `query.windowMs` (default 24h) are returned, so a\n * healthy system shows an EMPTY table instead of surfacing days-old failures as if\n * they were a live incident. `windowMs: 0` disables the window (return all). Each\n * row carries a compact `updatedAt` stamp so recency is visible in the table.\n */\nexport function durableRecentFailuresProvider(): DataProvider {\n return {\n name: 'durable.recentFailures',\n async resolve(query, ctx: ExtensionContext) {\n const store = ctx.moduleRef.get(STATE_STORE_CANONICAL, { strict: false }) as StateStore;\n const limit = Math.min(200, Math.max(10, Number(query?.limit ?? 50)));\n const windowMs = query?.windowMs === undefined ? 24 * 60 * 60 * 1000 : Number(query.windowMs);\n const cutoff = windowMs > 0 ? Date.now() - windowMs : 0;\n const [failed, dead] = await Promise.all([\n store.listRuns({ status: 'failed', limit }),\n store.listRuns({ status: 'dead', limit }),\n ]);\n const rows = [...failed, ...dead]\n .filter((r) => +new Date(r.updatedAt) >= cutoff)\n .sort((a, b) => +new Date(b.updatedAt) - +new Date(a.updatedAt))\n .slice(0, limit)\n .map((r) => ({\n // Compact UTC stamp (\"YYYY-MM-DD HH:mm Z\"); the generic table renderer\n // prints column values as-is, so we format here.\n updatedAt: `${new Date(r.updatedAt).toISOString().replace('T', ' ').slice(0, 16)}Z`,\n workflow: r.workflow,\n runId: r.id,\n error: r.error?.message ?? '',\n }));\n return { rows };\n },\n };\n}\n\n/**\n * Source D: per-group worker health from the engine (`WorkflowEngine.workerHealth()` — queue depth\n * vs. live worker heartbeats). `query.metric: 'starvedCount'` returns the number of groups with work\n * queued and zero live workers (the \"alive but not consuming\" alert state) as a stat; otherwise a\n * table, starved groups sorted first. Empty when the transport can't report health (in-process).\n */\nexport function durableWorkerHealthProvider(): DataProvider {\n return {\n name: 'durable.workerHealth',\n async resolve(query, ctx: ExtensionContext) {\n const engine = ctx.moduleRef.get(WorkflowEngine, { strict: false }) as WorkflowEngine;\n const health = await engine.workerHealth();\n const isStarved = (g: { depth: number; liveWorkers: unknown[] }) =>\n g.depth > 0 && g.liveWorkers.length === 0;\n if ((query?.metric as string) === 'starvedCount') {\n return { value: health.filter(isStarved).length };\n }\n const rows = health\n .slice()\n // Starved groups first, then deepest backlog — the rows that need attention rise to the top.\n .sort((a, b) => Number(isStarved(b)) - Number(isStarved(a)) || b.depth - a.depth)\n .map((g) => ({\n group: g.group,\n queued: g.depth,\n liveWorkers: g.liveWorkers.length,\n status: isStarved(g) ? 'STARVED' : 'ok',\n }));\n return { rows };\n },\n };\n}\n\n// ─── New golden-signals providers ────────────────────────────────────────────\n\n/**\n * Duration percentiles (p50/p95/p99) + a histogram of ~8 buckets.\n * Durations are read from `content.durationMs` on `run.completed`/`run.failed`; when absent,\n * computed by pairing a `run.started` and its corresponding `run.completed`/`run.failed` entry\n * by `runId` using their `createdAt` timestamps.\n *\n * With `query.metric === 'p50'|'p95'|'p99'` returns `{ value }` for use as a stat panel.\n */\nexport function durableDurationProvider(): DataProvider {\n return {\n name: 'durable.duration',\n async resolve(query, ctx: ExtensionContext) {\n const entries = await fetchEntries(ctx);\n\n // Index run.started entries by runId for the pairing fallback.\n const startedAt = new Map<string, number>();\n for (const e of entries) {\n const c = (e.content ?? {}) as { event?: string; runId?: string };\n if (c.event === 'run.started' && c.runId && e.createdAt) {\n startedAt.set(c.runId, +new Date(e.createdAt));\n }\n }\n\n const durs: number[] = [];\n for (const e of entries) {\n const c = (e.content ?? {}) as { event?: string; runId?: string; durationMs?: number };\n if (c.event === 'run.completed' || c.event === 'run.failed') {\n if (typeof c.durationMs === 'number') {\n durs.push(c.durationMs);\n } else if (c.runId) {\n const start = startedAt.get(c.runId);\n const end = e.createdAt ? +new Date(e.createdAt) : undefined;\n if (start !== undefined && end !== undefined && end >= start) {\n durs.push(end - start);\n }\n }\n }\n }\n durs.sort((a, b) => a - b);\n\n const p50 = percentile(durs, 50);\n const p95 = percentile(durs, 95);\n const p99 = percentile(durs, 99);\n\n // Stat shortcut: return a single value for use in stat panels.\n const metric = (query as Record<string, unknown>)?.metric as string | undefined;\n if (metric === 'p50') return { value: p50 };\n if (metric === 'p95') return { value: p95 };\n if (metric === 'p99') return { value: p99 };\n\n // Build ~8 equal-width buckets between 0 and max.\n const max = durs.at(-1) ?? 0;\n const bucketCount = 8;\n const size = Math.max(1, Math.ceil((max + 1) / bucketCount));\n const buckets = Array.from({ length: bucketCount }, (_, i) => ({\n label: `${Math.round((i * size) / 100) / 10}s`,\n count: 0,\n }));\n for (const d of durs) {\n const bi = Math.min(bucketCount - 1, Math.floor(d / size));\n const bucket = buckets[bi];\n if (bucket) bucket.count += 1;\n }\n\n return { buckets, p50, p95, p99 };\n },\n };\n}\n\n/**\n * Buckets `run.completed` (-> done) and `run.failed` (-> failed) entries by `createdAt` into\n * N=`query.buckets ?? 24` equal-width time buckets. Returns `{ rows: Array<{ label; done; failed }> }`.\n */\nexport function durableRunsOverTimeProvider(): DataProvider {\n return {\n name: 'durable.runsOverTime',\n async resolve(query, ctx: ExtensionContext) {\n const entries = await fetchEntries(ctx);\n const n = Number((query as Record<string, unknown>)?.buckets ?? 24);\n const now = Date.now();\n\n // Determine the time window from the oldest entry or default to 24h.\n let minT = now;\n for (const e of entries) {\n if (e.createdAt) {\n const t = +new Date(e.createdAt);\n if (t < minT) minT = t;\n }\n }\n const span = Math.max(now - minT, 1);\n const bucketSize = span / n;\n\n const rows: Array<{ label: string; done: number; failed: number }> = Array.from(\n { length: n },\n (_, i) => {\n const bucketStart = new Date(minT + i * bucketSize);\n const label = bucketStart.toISOString().slice(11, 16); // \"HH:mm\"\n return { label, done: 0, failed: 0 };\n },\n );\n\n for (const e of entries) {\n const c = (e.content ?? {}) as { event?: string };\n if (c.event !== 'run.completed' && c.event !== 'run.failed') continue;\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n const idx = Math.min(n - 1, Math.floor((t - minT) / bucketSize));\n const row = rows[idx];\n if (row) {\n if (c.event === 'run.completed') row.done += 1;\n else row.failed += 1;\n }\n }\n\n return { rows };\n },\n };\n}\n\n/**\n * Success rate over the last `query.windowMs` (default 24h), with a `delta` vs. the previous\n * equal-length window and a `spark` array of per-sub-bucket success rates.\n */\nexport function durableSuccessRateProvider(): DataProvider {\n return {\n name: 'durable.successRate',\n async resolve(query, ctx: ExtensionContext) {\n const entries = await fetchEntries(ctx);\n const windowMs = Number((query as Record<string, unknown>)?.windowMs ?? 24 * 60 * 60 * 1000);\n const now = Date.now();\n const { current, previous } = splitWindows(entries, windowMs, now);\n\n const value = successRateOf(current);\n const prevRate = successRateOf(previous);\n const delta = previous.length > 0 ? value - prevRate : undefined;\n\n // Spark: split the current window into 8 sub-buckets.\n const sparkBuckets = 8;\n const bucketSize = windowMs / sparkBuckets;\n const sparkStart = now - windowMs;\n const spark = Array.from({ length: sparkBuckets }, (_, i) => {\n const bStart = sparkStart + i * bucketSize;\n const bEnd = bStart + bucketSize;\n const bEntries = current.filter((e) => {\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n return t > bStart && t <= bEnd;\n });\n return successRateOf(bEntries);\n });\n\n return { value, delta, spark };\n },\n };\n}\n\n/**\n * Completed runs per hour over the last `query.windowMs` (default 24h), with a `delta` vs. the\n * previous equal-length window and a `spark` array of per-sub-bucket throughput values.\n */\nexport function durableThroughputProvider(): DataProvider {\n return {\n name: 'durable.throughput',\n async resolve(query, ctx: ExtensionContext) {\n const entries = await fetchEntries(ctx);\n const windowMs = Number((query as Record<string, unknown>)?.windowMs ?? 24 * 60 * 60 * 1000);\n const now = Date.now();\n const { current, previous } = splitWindows(entries, windowMs, now);\n\n const countCompleted = (es: StorageEntry[]) =>\n es.filter((e) => ((e.content ?? {}) as { event?: string }).event === 'run.completed')\n .length;\n\n const windowHours = windowMs / (60 * 60 * 1000);\n const value = countCompleted(current) / windowHours;\n const prevValue = countCompleted(previous) / windowHours;\n const delta = previous.length > 0 ? value - prevValue : undefined;\n\n // Spark: 8 sub-buckets of current window.\n const sparkBuckets = 8;\n const bucketSize = windowMs / sparkBuckets;\n const bucketHours = bucketSize / (60 * 60 * 1000);\n const sparkStart = now - windowMs;\n const spark = Array.from({ length: sparkBuckets }, (_, i) => {\n const bStart = sparkStart + i * bucketSize;\n const bEnd = bStart + bucketSize;\n const bEntries = current.filter((e) => {\n const t = e.createdAt ? +new Date(e.createdAt) : 0;\n return t > bStart && t <= bEnd;\n });\n return countCompleted(bEntries) / bucketHours;\n });\n\n return { value, delta, spark };\n },\n };\n}\n\n/** Color palette for the state breakdown pie segments. */\nconst STATE_BREAKDOWN_PALETTE = ['#34d399', '#fbbf24', '#f59e0b', '#f87171', '#38bdf8', '#a78bfa'];\nconst STATE_BREAKDOWN_STATUSES: RunStatus[] = [\n 'running',\n 'pending',\n 'cancelling',\n 'completed',\n 'failed',\n 'dead',\n];\n\n/**\n * Counts from `STATE_STORE.listRuns({ status })` for each of the statuses and returns pie\n * segments with the standard palette: `{ segments: Array<{ label, value, color }> }`.\n */\nexport function durableStateBreakdownProvider(): DataProvider {\n return {\n name: 'durable.stateBreakdown',\n async resolve(_query, ctx: ExtensionContext) {\n const store = ctx.moduleRef.get(STATE_STORE_CANONICAL, { strict: false }) as StateStore;\n const counts = await Promise.all(\n STATE_BREAKDOWN_STATUSES.map((status) =>\n store.listRuns({ status, limit: STATE_CAP }).then((runs) => runs.length),\n ),\n );\n const segments = STATE_BREAKDOWN_STATUSES.map((label, i) => ({\n label,\n value: counts[i],\n color: STATE_BREAKDOWN_PALETTE[i],\n }));\n return { segments };\n },\n };\n}\n\n// NOTE: durableRetryHotspotsProvider is NOT implemented.\n// StateStore only exposes `listCheckpoints(runId: string)` (per-run lookup), with no cross-run\n// query by step `attempts`. There is no API to enumerate all step checkpoints across runs without\n// first listing every run — which would be O(N*M) and impractical. Task 13 should omit that panel.\n"],"mappings":"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","names":["WorkflowEngine","SpanStatusCode","context","trace","DurableTelescopeWatcher","type","roots","Map","register","ctx","engine","moduleRef","get","WorkflowEngine","strict","tracer","trace","getTracer","subscribe","event","record","content","workflow","runId","seq","name","kind","output","error","durationMs","tags","set","startSpan","attributes","root","base","setSpan","context","active","step","with","end","delete","message","setStatus","undefined","code","SpanStatusCode","ERROR","push","defineTelescopeExtension","durableDashboard","opts","runHref","windowMs","recentFailuresWindowMs","id","label","panels","sections","title","cols","kind","data","provider","max","format","thresholds","warn","bad","direction","query","metric","spark","status","limit","columns","key","link","href","series","style","markers","STATE_STORE_CANONICAL","WorkflowEngine","TELESCOPE_STORAGE","percentile","sorted","p","length","idx","Math","min","ceil","max","fetchEntries","ctx","storage","moduleRef","get","TELESCOPE_STORAGE","strict","page","type","limit","data","splitWindows","entries","windowMs","now","start","prevStart","current","filter","e","t","createdAt","Date","previous","successRateOf","completed","failed","c","content","event","total","STATE_CAP","durableStateProvider","name","resolve","query","store","STATE_STORE_CANONICAL","status","runs","listRuns","value","durableTimeseriesProvider","Number","failByWorkflow","Map","wf","workflow","set","metric","items","map","label","sort","a","b","durableRecentFailuresProvider","undefined","cutoff","dead","Promise","all","rows","r","updatedAt","slice","toISOString","replace","runId","id","error","message","durableWorkerHealthProvider","engine","WorkflowEngine","health","workerHealth","isStarved","g","depth","liveWorkers","group","queued","durableDurationProvider","startedAt","durs","durationMs","push","end","p50","p95","p99","at","bucketCount","size","buckets","Array","from","_","i","round","count","d","bi","floor","bucket","durableRunsOverTimeProvider","n","minT","span","bucketSize","bucketStart","done","row","durableSuccessRateProvider","prevRate","delta","sparkBuckets","sparkStart","spark","bStart","bEnd","bEntries","durableThroughputProvider","countCompleted","es","windowHours","prevValue","bucketHours","STATE_BREAKDOWN_PALETTE","STATE_BREAKDOWN_STATUSES","durableStateBreakdownProvider","_query","counts","then","segments","color","durableTelescopeExtension","opts","defineTelescopeExtension","name","watchers","DurableTelescopeWatcher","entryTypes","id","label","dot","dashboards","durableDashboard","dataProviders","durableStateProvider","durableTimeseriesProvider","durableRecentFailuresProvider","durableWorkerHealthProvider","durableDurationProvider","durableRunsOverTimeProvider","durableSuccessRateProvider","durableThroughputProvider","durableStateBreakdownProvider"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dudousxd/nestjs-durable-telescope",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Telescope watcher for nestjs-durable — workflow runs and steps as Telescope entries",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Davide Carvalho",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"@opentelemetry/api": "1.9.0",
|
|
28
28
|
"typescript": "5.9.3",
|
|
29
29
|
"tsup": "8.3.5",
|
|
30
|
-
"@dudousxd/nestjs-durable-core": "^0.
|
|
30
|
+
"@dudousxd/nestjs-durable-core": "^0.35.0"
|
|
31
31
|
},
|
|
32
32
|
"module": "./dist/index.js",
|
|
33
33
|
"exports": {
|