@dudousxd/nestjs-durable-telescope 0.9.2 → 0.10.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 +315 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -2
- package/dist/index.d.ts +25 -2
- package/dist/index.js +313 -8
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,9 @@ __export(index_exports, {
|
|
|
26
26
|
durableDashboard: () => durableDashboard,
|
|
27
27
|
durableDurationProvider: () => durableDurationProvider,
|
|
28
28
|
durableExecutionScope: () => durableExecutionScope,
|
|
29
|
+
durableFailedTriggersProvider: () => durableFailedTriggersProvider,
|
|
30
|
+
durableLifecycleTriggersProvider: () => durableLifecycleTriggersProvider,
|
|
31
|
+
durableOwedLifecycleProvider: () => durableOwedLifecycleProvider,
|
|
29
32
|
durableRecentFailuresProvider: () => durableRecentFailuresProvider,
|
|
30
33
|
durableRunsOverTimeProvider: () => durableRunsOverTimeProvider,
|
|
31
34
|
durableStateBreakdownProvider: () => durableStateBreakdownProvider,
|
|
@@ -37,6 +40,7 @@ __export(index_exports, {
|
|
|
37
40
|
durableTraceContext: () => durableTraceContext,
|
|
38
41
|
durableWorkerHealthProvider: () => durableWorkerHealthProvider,
|
|
39
42
|
durableWorkerStatusProvider: () => durableWorkerStatusProvider,
|
|
43
|
+
runSpanContext: () => runSpanContext,
|
|
40
44
|
runTraceContext: () => runTraceContext,
|
|
41
45
|
runTraceId: () => runTraceId
|
|
42
46
|
});
|
|
@@ -82,14 +86,18 @@ function runIds(runId) {
|
|
|
82
86
|
}
|
|
83
87
|
__name(runIds, "runIds");
|
|
84
88
|
function runTraceContext(runId, parent = import_api.context.active()) {
|
|
85
|
-
return import_api.trace.setSpanContext(parent,
|
|
89
|
+
return import_api.trace.setSpanContext(parent, runSpanContext(runId));
|
|
90
|
+
}
|
|
91
|
+
__name(runTraceContext, "runTraceContext");
|
|
92
|
+
function runSpanContext(runId) {
|
|
93
|
+
return {
|
|
86
94
|
traceId: runTraceId(runId),
|
|
87
95
|
spanId: runSpanId(runId),
|
|
88
96
|
traceFlags: import_api.TraceFlags.SAMPLED,
|
|
89
97
|
isRemote: true
|
|
90
|
-
}
|
|
98
|
+
};
|
|
91
99
|
}
|
|
92
|
-
__name(
|
|
100
|
+
__name(runSpanContext, "runSpanContext");
|
|
93
101
|
|
|
94
102
|
// src/durable-execution-scope.ts
|
|
95
103
|
var activeScope = new import_node_async_hooks.AsyncLocalStorage();
|
|
@@ -176,10 +184,14 @@ var RUN_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
|
176
184
|
"run.started",
|
|
177
185
|
"run.completed",
|
|
178
186
|
"run.failed",
|
|
179
|
-
"run.suspended"
|
|
187
|
+
"run.suspended",
|
|
188
|
+
"run.triggered",
|
|
189
|
+
"run.trigger_failed"
|
|
180
190
|
]);
|
|
181
191
|
|
|
182
192
|
// src/durable-telescope.watcher.ts
|
|
193
|
+
var REMOTE_EVENT = Symbol.for("@dudousxd/nestjs-durable:remote-event");
|
|
194
|
+
var isRemote = /* @__PURE__ */ __name((event) => event[REMOTE_EVENT] === true, "isRemote");
|
|
183
195
|
var DurableTelescopeWatcher = class {
|
|
184
196
|
static {
|
|
185
197
|
__name(this, "DurableTelescopeWatcher");
|
|
@@ -196,7 +208,9 @@ var DurableTelescopeWatcher = class {
|
|
|
196
208
|
if (typeof engine?.subscribe !== "function") return;
|
|
197
209
|
const tracer = import_api3.trace.getTracer("@dudousxd/nestjs-durable-telescope");
|
|
198
210
|
engine.subscribe((event) => {
|
|
211
|
+
if (isRemote(event)) return;
|
|
199
212
|
const family = runEventFamily(event.type);
|
|
213
|
+
const source = lifecycleSourceOfEvent(event);
|
|
200
214
|
const record = /* @__PURE__ */ __name(() => ctx.record({
|
|
201
215
|
type: this.type,
|
|
202
216
|
...family !== void 0 ? {
|
|
@@ -220,7 +234,13 @@ var DurableTelescopeWatcher = class {
|
|
|
220
234
|
output: event.output,
|
|
221
235
|
error: event.error,
|
|
222
236
|
durationMs: event.durationMs,
|
|
223
|
-
reason: event.reason
|
|
237
|
+
reason: event.reason,
|
|
238
|
+
triggeredRunId: event.triggeredRunId,
|
|
239
|
+
...source ? {
|
|
240
|
+
lifecycleSourceRunId: source.runId,
|
|
241
|
+
lifecycleKind: source.kind,
|
|
242
|
+
lifecycleSourceTraceId: runTraceId(source.runId)
|
|
243
|
+
} : {}
|
|
224
244
|
},
|
|
225
245
|
tags: this.tags(event)
|
|
226
246
|
}), "record");
|
|
@@ -244,6 +264,21 @@ var DurableTelescopeWatcher = class {
|
|
|
244
264
|
}
|
|
245
265
|
import_api3.context.with(import_api3.trace.setSpan(base, step), record);
|
|
246
266
|
step.end();
|
|
267
|
+
} else if (source) {
|
|
268
|
+
const started = tracer.startSpan(`run ${event.workflow ?? event.runId}`, {
|
|
269
|
+
attributes: {
|
|
270
|
+
"durable.run_id": event.runId,
|
|
271
|
+
"durable.lifecycle.source_run_id": source.runId,
|
|
272
|
+
"durable.lifecycle.kind": source.kind
|
|
273
|
+
},
|
|
274
|
+
links: [
|
|
275
|
+
{
|
|
276
|
+
context: runSpanContext(source.runId)
|
|
277
|
+
}
|
|
278
|
+
]
|
|
279
|
+
}, base);
|
|
280
|
+
import_api3.context.with(import_api3.trace.setSpan(base, started), record);
|
|
281
|
+
started.end();
|
|
247
282
|
} else {
|
|
248
283
|
import_api3.context.with(base, record);
|
|
249
284
|
}
|
|
@@ -274,9 +309,28 @@ var DurableTelescopeWatcher = class {
|
|
|
274
309
|
tags.push(event.type === "run.routed" ? "routed" : "unrouted");
|
|
275
310
|
tags.push(`onError:${event.error?.code ?? "*"}`);
|
|
276
311
|
}
|
|
312
|
+
if (event.type === "run.triggered") {
|
|
313
|
+
tags.push("triggered");
|
|
314
|
+
if (event.triggeredRunId) tags.push(`run:${event.triggeredRunId}`);
|
|
315
|
+
}
|
|
316
|
+
if (event.type === "run.trigger_failed") tags.push("trigger_failed", `subscriber:${event.name}`);
|
|
317
|
+
const source = lifecycleSourceOfEvent(event);
|
|
318
|
+
if (source) {
|
|
319
|
+
tags.push("lifecycle", `source:${source.runId}`);
|
|
320
|
+
const traceId = (0, import_nestjs_durable_core.traceIdOfTraceparent)(event.sourceTraceparent);
|
|
321
|
+
if (traceId) tags.push(`sourceTrace:${traceId}`);
|
|
322
|
+
}
|
|
277
323
|
return tags;
|
|
278
324
|
}
|
|
279
325
|
};
|
|
326
|
+
function lifecycleSourceOfEvent(event) {
|
|
327
|
+
if (event.type !== "run.started" || !event.workflow) return void 0;
|
|
328
|
+
return (0, import_nestjs_durable_core.lifecycleSourceOf)({
|
|
329
|
+
id: event.runId,
|
|
330
|
+
workflow: event.workflow
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
__name(lifecycleSourceOfEvent, "lifecycleSourceOfEvent");
|
|
280
334
|
|
|
281
335
|
// src/durable-telescope.extension.ts
|
|
282
336
|
var import_nestjs_telescope2 = require("@dudousxd/nestjs-telescope");
|
|
@@ -398,6 +452,66 @@ function durableDashboard(opts = {}) {
|
|
|
398
452
|
}
|
|
399
453
|
]
|
|
400
454
|
},
|
|
455
|
+
{
|
|
456
|
+
kind: "table",
|
|
457
|
+
title: "Failed triggers",
|
|
458
|
+
data: {
|
|
459
|
+
provider: "durable.failedTriggers"
|
|
460
|
+
},
|
|
461
|
+
columns: [
|
|
462
|
+
{
|
|
463
|
+
key: "at",
|
|
464
|
+
label: "Time"
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
key: "workflow",
|
|
468
|
+
label: "Source workflow"
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
key: "runId",
|
|
472
|
+
label: "Source run",
|
|
473
|
+
link: {
|
|
474
|
+
href: runHref
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
key: "subscriber",
|
|
479
|
+
label: "Subscriber"
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
key: "error",
|
|
483
|
+
label: "Error"
|
|
484
|
+
}
|
|
485
|
+
]
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
kind: "table",
|
|
489
|
+
title: "Owed lifecycle events",
|
|
490
|
+
data: {
|
|
491
|
+
provider: "durable.owedLifecycle"
|
|
492
|
+
},
|
|
493
|
+
columns: [
|
|
494
|
+
{
|
|
495
|
+
key: "settledAt",
|
|
496
|
+
label: "Settled"
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
key: "workflow",
|
|
500
|
+
label: "Workflow"
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
key: "runId",
|
|
504
|
+
label: "Run",
|
|
505
|
+
link: {
|
|
506
|
+
href: runHref
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
key: "owed",
|
|
511
|
+
label: "Owed to"
|
|
512
|
+
}
|
|
513
|
+
]
|
|
514
|
+
},
|
|
401
515
|
{
|
|
402
516
|
kind: "table",
|
|
403
517
|
// Lists ALL worker groups (starved ones sorted first); the Status column
|
|
@@ -425,6 +539,52 @@ function durableDashboard(opts = {}) {
|
|
|
425
539
|
label: "Status"
|
|
426
540
|
}
|
|
427
541
|
]
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
kind: "stat",
|
|
545
|
+
title: "Owed runs",
|
|
546
|
+
data: {
|
|
547
|
+
provider: "durable.owedLifecycle",
|
|
548
|
+
query: {
|
|
549
|
+
metric: "count"
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
spark: false,
|
|
553
|
+
thresholds: {
|
|
554
|
+
warn: 1,
|
|
555
|
+
bad: 50,
|
|
556
|
+
direction: "up-bad"
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
]
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
// Workflows started when another one settles (`run.completed:<wf>` / `run.failed:<wf>`).
|
|
563
|
+
title: "Lifecycle",
|
|
564
|
+
cols: 2,
|
|
565
|
+
panels: [
|
|
566
|
+
{
|
|
567
|
+
kind: "timeseries",
|
|
568
|
+
title: "Lifecycle triggers over time",
|
|
569
|
+
data: {
|
|
570
|
+
provider: "durable.lifecycleTriggers"
|
|
571
|
+
},
|
|
572
|
+
series: [
|
|
573
|
+
"triggered",
|
|
574
|
+
"failed"
|
|
575
|
+
],
|
|
576
|
+
style: "stacked"
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
kind: "topN",
|
|
580
|
+
title: "Triggered by subscriber",
|
|
581
|
+
data: {
|
|
582
|
+
provider: "durable.lifecycleTriggers",
|
|
583
|
+
query: {
|
|
584
|
+
metric: "bySubscriber"
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
limit: 10
|
|
428
588
|
}
|
|
429
589
|
]
|
|
430
590
|
},
|
|
@@ -1073,6 +1233,148 @@ function durableStateBreakdownProvider() {
|
|
|
1073
1233
|
};
|
|
1074
1234
|
}
|
|
1075
1235
|
__name(durableStateBreakdownProvider, "durableStateBreakdownProvider");
|
|
1236
|
+
function stamp(at) {
|
|
1237
|
+
return at ? `${new Date(at).toISOString().replace("T", " ").slice(0, 16)}Z` : "";
|
|
1238
|
+
}
|
|
1239
|
+
__name(stamp, "stamp");
|
|
1240
|
+
async function triggerFailures(ctx, limit) {
|
|
1241
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1242
|
+
const out = [];
|
|
1243
|
+
for (const entry of await fetchRunEntries(ctx, [
|
|
1244
|
+
"run.trigger_failed"
|
|
1245
|
+
], limit)) {
|
|
1246
|
+
const c = entry.content ?? {};
|
|
1247
|
+
if (c.event !== "run.trigger_failed") continue;
|
|
1248
|
+
const key = `${c.runId}\0${c.name}`;
|
|
1249
|
+
if (seen.has(key)) continue;
|
|
1250
|
+
seen.add(key);
|
|
1251
|
+
out.push(entry);
|
|
1252
|
+
}
|
|
1253
|
+
return out;
|
|
1254
|
+
}
|
|
1255
|
+
__name(triggerFailures, "triggerFailures");
|
|
1256
|
+
function durableFailedTriggersProvider() {
|
|
1257
|
+
return {
|
|
1258
|
+
name: "durable.failedTriggers",
|
|
1259
|
+
async resolve(query, ctx) {
|
|
1260
|
+
const limit = Math.min(200, Math.max(10, Number(query?.limit ?? 50)));
|
|
1261
|
+
const windowMs = query?.windowMs === void 0 ? 24 * 60 * 60 * 1e3 : Number(query.windowMs);
|
|
1262
|
+
const cutoff = windowMs > 0 ? Date.now() - windowMs : 0;
|
|
1263
|
+
const rows = (await triggerFailures(ctx, RUN_EVENTS_LIMIT)).filter((entry) => entryTime(entry) >= cutoff).slice(0, limit).map((entry) => {
|
|
1264
|
+
const c = entry.content ?? {};
|
|
1265
|
+
return {
|
|
1266
|
+
at: stamp(entry.createdAt),
|
|
1267
|
+
workflow: c.workflow ?? "",
|
|
1268
|
+
runId: c.runId ?? "",
|
|
1269
|
+
subscriber: c.name ?? "",
|
|
1270
|
+
error: c.error?.message ?? ""
|
|
1271
|
+
};
|
|
1272
|
+
});
|
|
1273
|
+
return {
|
|
1274
|
+
rows
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
__name(durableFailedTriggersProvider, "durableFailedTriggersProvider");
|
|
1280
|
+
var LIFECYCLE_GRACE_MS = 3e4;
|
|
1281
|
+
function durableOwedLifecycleProvider() {
|
|
1282
|
+
return {
|
|
1283
|
+
name: "durable.owedLifecycle",
|
|
1284
|
+
async resolve(query, ctx) {
|
|
1285
|
+
const store = ctx.moduleRef.get(import_nestjs_durable_core2.STATE_STORE_CANONICAL, {
|
|
1286
|
+
strict: false
|
|
1287
|
+
});
|
|
1288
|
+
const limit = Math.min(200, Math.max(10, Number(query?.limit ?? 50)));
|
|
1289
|
+
const cutoff = Date.now() - LIFECYCLE_GRACE_MS;
|
|
1290
|
+
const runs = await store.listRuns({
|
|
1291
|
+
tag: import_nestjs_durable_core2.LIFECYCLE_PENDING_TAG,
|
|
1292
|
+
statuses: [
|
|
1293
|
+
"completed",
|
|
1294
|
+
"failed",
|
|
1295
|
+
"cancelled",
|
|
1296
|
+
"dead"
|
|
1297
|
+
],
|
|
1298
|
+
limit: 1e3
|
|
1299
|
+
});
|
|
1300
|
+
const owed = runs.filter((run) => +new Date(run.updatedAt) <= cutoff);
|
|
1301
|
+
if (query?.metric === "count") return {
|
|
1302
|
+
value: owed.length
|
|
1303
|
+
};
|
|
1304
|
+
const rows = owed.sort((a, b) => +new Date(b.updatedAt) - +new Date(a.updatedAt)).slice(0, limit).map((run) => ({
|
|
1305
|
+
settledAt: stamp(run.updatedAt),
|
|
1306
|
+
workflow: run.workflow,
|
|
1307
|
+
runId: run.id,
|
|
1308
|
+
owed: (0, import_nestjs_durable_core2.owedSubscribersOf)(run.tags).join(", ") || "every subscriber"
|
|
1309
|
+
}));
|
|
1310
|
+
return {
|
|
1311
|
+
rows
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
__name(durableOwedLifecycleProvider, "durableOwedLifecycleProvider");
|
|
1317
|
+
function durableLifecycleTriggersProvider() {
|
|
1318
|
+
return {
|
|
1319
|
+
name: "durable.lifecycleTriggers",
|
|
1320
|
+
async resolve(query, ctx) {
|
|
1321
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1322
|
+
const entries = (await fetchRunEntries(ctx, [
|
|
1323
|
+
"run.triggered",
|
|
1324
|
+
"run.trigger_failed"
|
|
1325
|
+
])).filter((entry) => {
|
|
1326
|
+
const c = entry.content ?? {};
|
|
1327
|
+
if (c.event !== "run.triggered" && c.event !== "run.trigger_failed") return false;
|
|
1328
|
+
const key = `${c.event}\0${c.runId}\0${c.name}`;
|
|
1329
|
+
if (seen.has(key)) return false;
|
|
1330
|
+
seen.add(key);
|
|
1331
|
+
return true;
|
|
1332
|
+
});
|
|
1333
|
+
if (query?.metric === "bySubscriber") {
|
|
1334
|
+
const limit = Math.max(1, Number(query?.limit ?? 10));
|
|
1335
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1336
|
+
for (const entry of entries) {
|
|
1337
|
+
const c = entry.content ?? {};
|
|
1338
|
+
if (c.event !== "run.triggered") continue;
|
|
1339
|
+
const label = `${c.workflow ?? "unknown"} \u2192 ${c.name ?? "unknown"}`;
|
|
1340
|
+
counts.set(label, (counts.get(label) ?? 0) + 1);
|
|
1341
|
+
}
|
|
1342
|
+
const items = [
|
|
1343
|
+
...counts
|
|
1344
|
+
].map(([label, value]) => ({
|
|
1345
|
+
label,
|
|
1346
|
+
value
|
|
1347
|
+
})).sort((a, b) => b.value - a.value || a.label.localeCompare(b.label)).slice(0, limit);
|
|
1348
|
+
return {
|
|
1349
|
+
items
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
const n = Math.max(1, Number(query?.buckets ?? 24));
|
|
1353
|
+
const now = Date.now();
|
|
1354
|
+
let minT = now;
|
|
1355
|
+
for (const entry of entries) if (entry.createdAt) minT = Math.min(minT, entryTime(entry));
|
|
1356
|
+
const bucketSize = Math.max(now - minT, 1) / n;
|
|
1357
|
+
const rows = Array.from({
|
|
1358
|
+
length: n
|
|
1359
|
+
}, (_, i) => ({
|
|
1360
|
+
label: new Date(minT + i * bucketSize).toISOString().slice(11, 16),
|
|
1361
|
+
triggered: 0,
|
|
1362
|
+
failed: 0
|
|
1363
|
+
}));
|
|
1364
|
+
for (const entry of entries) {
|
|
1365
|
+
const index = Math.floor((entryTime(entry) - minT) / bucketSize);
|
|
1366
|
+
const row = rows[Math.min(n - 1, Math.max(0, index))];
|
|
1367
|
+
if (!row) continue;
|
|
1368
|
+
if (entry.content.event === "run.triggered") row.triggered += 1;
|
|
1369
|
+
else row.failed += 1;
|
|
1370
|
+
}
|
|
1371
|
+
return {
|
|
1372
|
+
rows
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
__name(durableLifecycleTriggersProvider, "durableLifecycleTriggersProvider");
|
|
1076
1378
|
|
|
1077
1379
|
// src/durable-telescope.extension.ts
|
|
1078
1380
|
function durableTelescopeExtension(opts = {}) {
|
|
@@ -1101,7 +1403,10 @@ function durableTelescopeExtension(opts = {}) {
|
|
|
1101
1403
|
durableRunsOverTimeProvider(),
|
|
1102
1404
|
durableSuccessRateProvider(),
|
|
1103
1405
|
durableThroughputProvider(),
|
|
1104
|
-
durableStateBreakdownProvider()
|
|
1406
|
+
durableStateBreakdownProvider(),
|
|
1407
|
+
durableFailedTriggersProvider(),
|
|
1408
|
+
durableOwedLifecycleProvider(),
|
|
1409
|
+
durableLifecycleTriggersProvider()
|
|
1105
1410
|
], "dataProviders")
|
|
1106
1411
|
});
|
|
1107
1412
|
}
|
|
@@ -1113,6 +1418,9 @@ __name(durableTelescopeExtension, "durableTelescopeExtension");
|
|
|
1113
1418
|
durableDashboard,
|
|
1114
1419
|
durableDurationProvider,
|
|
1115
1420
|
durableExecutionScope,
|
|
1421
|
+
durableFailedTriggersProvider,
|
|
1422
|
+
durableLifecycleTriggersProvider,
|
|
1423
|
+
durableOwedLifecycleProvider,
|
|
1116
1424
|
durableRecentFailuresProvider,
|
|
1117
1425
|
durableRunsOverTimeProvider,
|
|
1118
1426
|
durableStateBreakdownProvider,
|
|
@@ -1124,6 +1432,7 @@ __name(durableTelescopeExtension, "durableTelescopeExtension");
|
|
|
1124
1432
|
durableTraceContext,
|
|
1125
1433
|
durableWorkerHealthProvider,
|
|
1126
1434
|
durableWorkerStatusProvider,
|
|
1435
|
+
runSpanContext,
|
|
1127
1436
|
runTraceContext,
|
|
1128
1437
|
runTraceId
|
|
1129
1438
|
});
|