@dudousxd/nestjs-durable-telescope 0.9.3 → 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 +312 -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 +310 -8
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
3
|
|
|
4
4
|
// src/durable-telescope.watcher.ts
|
|
5
|
-
import { WorkflowEngine, parentRunIdOf, useDurableExecution } from "@dudousxd/nestjs-durable-core";
|
|
5
|
+
import { WorkflowEngine, lifecycleSourceOf, parentRunIdOf, traceIdOfTraceparent, useDurableExecution } from "@dudousxd/nestjs-durable-core";
|
|
6
6
|
import { SpanStatusCode as SpanStatusCode2, context as context3, trace as trace3 } from "@opentelemetry/api";
|
|
7
7
|
|
|
8
8
|
// src/durable-execution-scope.ts
|
|
@@ -41,14 +41,18 @@ function runIds(runId) {
|
|
|
41
41
|
}
|
|
42
42
|
__name(runIds, "runIds");
|
|
43
43
|
function runTraceContext(runId, parent = context.active()) {
|
|
44
|
-
return trace.setSpanContext(parent,
|
|
44
|
+
return trace.setSpanContext(parent, runSpanContext(runId));
|
|
45
|
+
}
|
|
46
|
+
__name(runTraceContext, "runTraceContext");
|
|
47
|
+
function runSpanContext(runId) {
|
|
48
|
+
return {
|
|
45
49
|
traceId: runTraceId(runId),
|
|
46
50
|
spanId: runSpanId(runId),
|
|
47
51
|
traceFlags: TraceFlags.SAMPLED,
|
|
48
52
|
isRemote: true
|
|
49
|
-
}
|
|
53
|
+
};
|
|
50
54
|
}
|
|
51
|
-
__name(
|
|
55
|
+
__name(runSpanContext, "runSpanContext");
|
|
52
56
|
|
|
53
57
|
// src/durable-execution-scope.ts
|
|
54
58
|
var activeScope = new AsyncLocalStorage();
|
|
@@ -135,7 +139,9 @@ var RUN_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
|
135
139
|
"run.started",
|
|
136
140
|
"run.completed",
|
|
137
141
|
"run.failed",
|
|
138
|
-
"run.suspended"
|
|
142
|
+
"run.suspended",
|
|
143
|
+
"run.triggered",
|
|
144
|
+
"run.trigger_failed"
|
|
139
145
|
]);
|
|
140
146
|
|
|
141
147
|
// src/durable-telescope.watcher.ts
|
|
@@ -159,6 +165,7 @@ var DurableTelescopeWatcher = class {
|
|
|
159
165
|
engine.subscribe((event) => {
|
|
160
166
|
if (isRemote(event)) return;
|
|
161
167
|
const family = runEventFamily(event.type);
|
|
168
|
+
const source = lifecycleSourceOfEvent(event);
|
|
162
169
|
const record = /* @__PURE__ */ __name(() => ctx.record({
|
|
163
170
|
type: this.type,
|
|
164
171
|
...family !== void 0 ? {
|
|
@@ -182,7 +189,13 @@ var DurableTelescopeWatcher = class {
|
|
|
182
189
|
output: event.output,
|
|
183
190
|
error: event.error,
|
|
184
191
|
durationMs: event.durationMs,
|
|
185
|
-
reason: event.reason
|
|
192
|
+
reason: event.reason,
|
|
193
|
+
triggeredRunId: event.triggeredRunId,
|
|
194
|
+
...source ? {
|
|
195
|
+
lifecycleSourceRunId: source.runId,
|
|
196
|
+
lifecycleKind: source.kind,
|
|
197
|
+
lifecycleSourceTraceId: runTraceId(source.runId)
|
|
198
|
+
} : {}
|
|
186
199
|
},
|
|
187
200
|
tags: this.tags(event)
|
|
188
201
|
}), "record");
|
|
@@ -206,6 +219,21 @@ var DurableTelescopeWatcher = class {
|
|
|
206
219
|
}
|
|
207
220
|
context3.with(trace3.setSpan(base, step), record);
|
|
208
221
|
step.end();
|
|
222
|
+
} else if (source) {
|
|
223
|
+
const started = tracer.startSpan(`run ${event.workflow ?? event.runId}`, {
|
|
224
|
+
attributes: {
|
|
225
|
+
"durable.run_id": event.runId,
|
|
226
|
+
"durable.lifecycle.source_run_id": source.runId,
|
|
227
|
+
"durable.lifecycle.kind": source.kind
|
|
228
|
+
},
|
|
229
|
+
links: [
|
|
230
|
+
{
|
|
231
|
+
context: runSpanContext(source.runId)
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
}, base);
|
|
235
|
+
context3.with(trace3.setSpan(base, started), record);
|
|
236
|
+
started.end();
|
|
209
237
|
} else {
|
|
210
238
|
context3.with(base, record);
|
|
211
239
|
}
|
|
@@ -236,9 +264,28 @@ var DurableTelescopeWatcher = class {
|
|
|
236
264
|
tags.push(event.type === "run.routed" ? "routed" : "unrouted");
|
|
237
265
|
tags.push(`onError:${event.error?.code ?? "*"}`);
|
|
238
266
|
}
|
|
267
|
+
if (event.type === "run.triggered") {
|
|
268
|
+
tags.push("triggered");
|
|
269
|
+
if (event.triggeredRunId) tags.push(`run:${event.triggeredRunId}`);
|
|
270
|
+
}
|
|
271
|
+
if (event.type === "run.trigger_failed") tags.push("trigger_failed", `subscriber:${event.name}`);
|
|
272
|
+
const source = lifecycleSourceOfEvent(event);
|
|
273
|
+
if (source) {
|
|
274
|
+
tags.push("lifecycle", `source:${source.runId}`);
|
|
275
|
+
const traceId = traceIdOfTraceparent(event.sourceTraceparent);
|
|
276
|
+
if (traceId) tags.push(`sourceTrace:${traceId}`);
|
|
277
|
+
}
|
|
239
278
|
return tags;
|
|
240
279
|
}
|
|
241
280
|
};
|
|
281
|
+
function lifecycleSourceOfEvent(event) {
|
|
282
|
+
if (event.type !== "run.started" || !event.workflow) return void 0;
|
|
283
|
+
return lifecycleSourceOf({
|
|
284
|
+
id: event.runId,
|
|
285
|
+
workflow: event.workflow
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
__name(lifecycleSourceOfEvent, "lifecycleSourceOfEvent");
|
|
242
289
|
|
|
243
290
|
// src/durable-telescope.extension.ts
|
|
244
291
|
import { defineTelescopeExtension } from "@dudousxd/nestjs-telescope";
|
|
@@ -360,6 +407,66 @@ function durableDashboard(opts = {}) {
|
|
|
360
407
|
}
|
|
361
408
|
]
|
|
362
409
|
},
|
|
410
|
+
{
|
|
411
|
+
kind: "table",
|
|
412
|
+
title: "Failed triggers",
|
|
413
|
+
data: {
|
|
414
|
+
provider: "durable.failedTriggers"
|
|
415
|
+
},
|
|
416
|
+
columns: [
|
|
417
|
+
{
|
|
418
|
+
key: "at",
|
|
419
|
+
label: "Time"
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
key: "workflow",
|
|
423
|
+
label: "Source workflow"
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
key: "runId",
|
|
427
|
+
label: "Source run",
|
|
428
|
+
link: {
|
|
429
|
+
href: runHref
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
key: "subscriber",
|
|
434
|
+
label: "Subscriber"
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
key: "error",
|
|
438
|
+
label: "Error"
|
|
439
|
+
}
|
|
440
|
+
]
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
kind: "table",
|
|
444
|
+
title: "Owed lifecycle events",
|
|
445
|
+
data: {
|
|
446
|
+
provider: "durable.owedLifecycle"
|
|
447
|
+
},
|
|
448
|
+
columns: [
|
|
449
|
+
{
|
|
450
|
+
key: "settledAt",
|
|
451
|
+
label: "Settled"
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
key: "workflow",
|
|
455
|
+
label: "Workflow"
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
key: "runId",
|
|
459
|
+
label: "Run",
|
|
460
|
+
link: {
|
|
461
|
+
href: runHref
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
key: "owed",
|
|
466
|
+
label: "Owed to"
|
|
467
|
+
}
|
|
468
|
+
]
|
|
469
|
+
},
|
|
363
470
|
{
|
|
364
471
|
kind: "table",
|
|
365
472
|
// Lists ALL worker groups (starved ones sorted first); the Status column
|
|
@@ -387,6 +494,52 @@ function durableDashboard(opts = {}) {
|
|
|
387
494
|
label: "Status"
|
|
388
495
|
}
|
|
389
496
|
]
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
kind: "stat",
|
|
500
|
+
title: "Owed runs",
|
|
501
|
+
data: {
|
|
502
|
+
provider: "durable.owedLifecycle",
|
|
503
|
+
query: {
|
|
504
|
+
metric: "count"
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
spark: false,
|
|
508
|
+
thresholds: {
|
|
509
|
+
warn: 1,
|
|
510
|
+
bad: 50,
|
|
511
|
+
direction: "up-bad"
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
]
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
// Workflows started when another one settles (`run.completed:<wf>` / `run.failed:<wf>`).
|
|
518
|
+
title: "Lifecycle",
|
|
519
|
+
cols: 2,
|
|
520
|
+
panels: [
|
|
521
|
+
{
|
|
522
|
+
kind: "timeseries",
|
|
523
|
+
title: "Lifecycle triggers over time",
|
|
524
|
+
data: {
|
|
525
|
+
provider: "durable.lifecycleTriggers"
|
|
526
|
+
},
|
|
527
|
+
series: [
|
|
528
|
+
"triggered",
|
|
529
|
+
"failed"
|
|
530
|
+
],
|
|
531
|
+
style: "stacked"
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
kind: "topN",
|
|
535
|
+
title: "Triggered by subscriber",
|
|
536
|
+
data: {
|
|
537
|
+
provider: "durable.lifecycleTriggers",
|
|
538
|
+
query: {
|
|
539
|
+
metric: "bySubscriber"
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
limit: 10
|
|
390
543
|
}
|
|
391
544
|
]
|
|
392
545
|
},
|
|
@@ -505,7 +658,7 @@ function durableDashboard(opts = {}) {
|
|
|
505
658
|
__name(durableDashboard, "durableDashboard");
|
|
506
659
|
|
|
507
660
|
// src/durable-data-providers.ts
|
|
508
|
-
import { STATE_STORE_CANONICAL, WorkflowEngine as WorkflowEngine2 } from "@dudousxd/nestjs-durable-core";
|
|
661
|
+
import { LIFECYCLE_PENDING_TAG, STATE_STORE_CANONICAL, WorkflowEngine as WorkflowEngine2, owedSubscribersOf } from "@dudousxd/nestjs-durable-core";
|
|
509
662
|
import { TELESCOPE_STORAGE } from "@dudousxd/nestjs-telescope";
|
|
510
663
|
function percentile(sorted, p) {
|
|
511
664
|
if (sorted.length === 0) return 0;
|
|
@@ -1035,6 +1188,148 @@ function durableStateBreakdownProvider() {
|
|
|
1035
1188
|
};
|
|
1036
1189
|
}
|
|
1037
1190
|
__name(durableStateBreakdownProvider, "durableStateBreakdownProvider");
|
|
1191
|
+
function stamp(at) {
|
|
1192
|
+
return at ? `${new Date(at).toISOString().replace("T", " ").slice(0, 16)}Z` : "";
|
|
1193
|
+
}
|
|
1194
|
+
__name(stamp, "stamp");
|
|
1195
|
+
async function triggerFailures(ctx, limit) {
|
|
1196
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1197
|
+
const out = [];
|
|
1198
|
+
for (const entry of await fetchRunEntries(ctx, [
|
|
1199
|
+
"run.trigger_failed"
|
|
1200
|
+
], limit)) {
|
|
1201
|
+
const c = entry.content ?? {};
|
|
1202
|
+
if (c.event !== "run.trigger_failed") continue;
|
|
1203
|
+
const key = `${c.runId}\0${c.name}`;
|
|
1204
|
+
if (seen.has(key)) continue;
|
|
1205
|
+
seen.add(key);
|
|
1206
|
+
out.push(entry);
|
|
1207
|
+
}
|
|
1208
|
+
return out;
|
|
1209
|
+
}
|
|
1210
|
+
__name(triggerFailures, "triggerFailures");
|
|
1211
|
+
function durableFailedTriggersProvider() {
|
|
1212
|
+
return {
|
|
1213
|
+
name: "durable.failedTriggers",
|
|
1214
|
+
async resolve(query, ctx) {
|
|
1215
|
+
const limit = Math.min(200, Math.max(10, Number(query?.limit ?? 50)));
|
|
1216
|
+
const windowMs = query?.windowMs === void 0 ? 24 * 60 * 60 * 1e3 : Number(query.windowMs);
|
|
1217
|
+
const cutoff = windowMs > 0 ? Date.now() - windowMs : 0;
|
|
1218
|
+
const rows = (await triggerFailures(ctx, RUN_EVENTS_LIMIT)).filter((entry) => entryTime(entry) >= cutoff).slice(0, limit).map((entry) => {
|
|
1219
|
+
const c = entry.content ?? {};
|
|
1220
|
+
return {
|
|
1221
|
+
at: stamp(entry.createdAt),
|
|
1222
|
+
workflow: c.workflow ?? "",
|
|
1223
|
+
runId: c.runId ?? "",
|
|
1224
|
+
subscriber: c.name ?? "",
|
|
1225
|
+
error: c.error?.message ?? ""
|
|
1226
|
+
};
|
|
1227
|
+
});
|
|
1228
|
+
return {
|
|
1229
|
+
rows
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
__name(durableFailedTriggersProvider, "durableFailedTriggersProvider");
|
|
1235
|
+
var LIFECYCLE_GRACE_MS = 3e4;
|
|
1236
|
+
function durableOwedLifecycleProvider() {
|
|
1237
|
+
return {
|
|
1238
|
+
name: "durable.owedLifecycle",
|
|
1239
|
+
async resolve(query, ctx) {
|
|
1240
|
+
const store = ctx.moduleRef.get(STATE_STORE_CANONICAL, {
|
|
1241
|
+
strict: false
|
|
1242
|
+
});
|
|
1243
|
+
const limit = Math.min(200, Math.max(10, Number(query?.limit ?? 50)));
|
|
1244
|
+
const cutoff = Date.now() - LIFECYCLE_GRACE_MS;
|
|
1245
|
+
const runs = await store.listRuns({
|
|
1246
|
+
tag: LIFECYCLE_PENDING_TAG,
|
|
1247
|
+
statuses: [
|
|
1248
|
+
"completed",
|
|
1249
|
+
"failed",
|
|
1250
|
+
"cancelled",
|
|
1251
|
+
"dead"
|
|
1252
|
+
],
|
|
1253
|
+
limit: 1e3
|
|
1254
|
+
});
|
|
1255
|
+
const owed = runs.filter((run) => +new Date(run.updatedAt) <= cutoff);
|
|
1256
|
+
if (query?.metric === "count") return {
|
|
1257
|
+
value: owed.length
|
|
1258
|
+
};
|
|
1259
|
+
const rows = owed.sort((a, b) => +new Date(b.updatedAt) - +new Date(a.updatedAt)).slice(0, limit).map((run) => ({
|
|
1260
|
+
settledAt: stamp(run.updatedAt),
|
|
1261
|
+
workflow: run.workflow,
|
|
1262
|
+
runId: run.id,
|
|
1263
|
+
owed: owedSubscribersOf(run.tags).join(", ") || "every subscriber"
|
|
1264
|
+
}));
|
|
1265
|
+
return {
|
|
1266
|
+
rows
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
__name(durableOwedLifecycleProvider, "durableOwedLifecycleProvider");
|
|
1272
|
+
function durableLifecycleTriggersProvider() {
|
|
1273
|
+
return {
|
|
1274
|
+
name: "durable.lifecycleTriggers",
|
|
1275
|
+
async resolve(query, ctx) {
|
|
1276
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1277
|
+
const entries = (await fetchRunEntries(ctx, [
|
|
1278
|
+
"run.triggered",
|
|
1279
|
+
"run.trigger_failed"
|
|
1280
|
+
])).filter((entry) => {
|
|
1281
|
+
const c = entry.content ?? {};
|
|
1282
|
+
if (c.event !== "run.triggered" && c.event !== "run.trigger_failed") return false;
|
|
1283
|
+
const key = `${c.event}\0${c.runId}\0${c.name}`;
|
|
1284
|
+
if (seen.has(key)) return false;
|
|
1285
|
+
seen.add(key);
|
|
1286
|
+
return true;
|
|
1287
|
+
});
|
|
1288
|
+
if (query?.metric === "bySubscriber") {
|
|
1289
|
+
const limit = Math.max(1, Number(query?.limit ?? 10));
|
|
1290
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1291
|
+
for (const entry of entries) {
|
|
1292
|
+
const c = entry.content ?? {};
|
|
1293
|
+
if (c.event !== "run.triggered") continue;
|
|
1294
|
+
const label = `${c.workflow ?? "unknown"} \u2192 ${c.name ?? "unknown"}`;
|
|
1295
|
+
counts.set(label, (counts.get(label) ?? 0) + 1);
|
|
1296
|
+
}
|
|
1297
|
+
const items = [
|
|
1298
|
+
...counts
|
|
1299
|
+
].map(([label, value]) => ({
|
|
1300
|
+
label,
|
|
1301
|
+
value
|
|
1302
|
+
})).sort((a, b) => b.value - a.value || a.label.localeCompare(b.label)).slice(0, limit);
|
|
1303
|
+
return {
|
|
1304
|
+
items
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
const n = Math.max(1, Number(query?.buckets ?? 24));
|
|
1308
|
+
const now = Date.now();
|
|
1309
|
+
let minT = now;
|
|
1310
|
+
for (const entry of entries) if (entry.createdAt) minT = Math.min(minT, entryTime(entry));
|
|
1311
|
+
const bucketSize = Math.max(now - minT, 1) / n;
|
|
1312
|
+
const rows = Array.from({
|
|
1313
|
+
length: n
|
|
1314
|
+
}, (_, i) => ({
|
|
1315
|
+
label: new Date(minT + i * bucketSize).toISOString().slice(11, 16),
|
|
1316
|
+
triggered: 0,
|
|
1317
|
+
failed: 0
|
|
1318
|
+
}));
|
|
1319
|
+
for (const entry of entries) {
|
|
1320
|
+
const index = Math.floor((entryTime(entry) - minT) / bucketSize);
|
|
1321
|
+
const row = rows[Math.min(n - 1, Math.max(0, index))];
|
|
1322
|
+
if (!row) continue;
|
|
1323
|
+
if (entry.content.event === "run.triggered") row.triggered += 1;
|
|
1324
|
+
else row.failed += 1;
|
|
1325
|
+
}
|
|
1326
|
+
return {
|
|
1327
|
+
rows
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
__name(durableLifecycleTriggersProvider, "durableLifecycleTriggersProvider");
|
|
1038
1333
|
|
|
1039
1334
|
// src/durable-telescope.extension.ts
|
|
1040
1335
|
function durableTelescopeExtension(opts = {}) {
|
|
@@ -1063,7 +1358,10 @@ function durableTelescopeExtension(opts = {}) {
|
|
|
1063
1358
|
durableRunsOverTimeProvider(),
|
|
1064
1359
|
durableSuccessRateProvider(),
|
|
1065
1360
|
durableThroughputProvider(),
|
|
1066
|
-
durableStateBreakdownProvider()
|
|
1361
|
+
durableStateBreakdownProvider(),
|
|
1362
|
+
durableFailedTriggersProvider(),
|
|
1363
|
+
durableOwedLifecycleProvider(),
|
|
1364
|
+
durableLifecycleTriggersProvider()
|
|
1067
1365
|
], "dataProviders")
|
|
1068
1366
|
});
|
|
1069
1367
|
}
|
|
@@ -1074,6 +1372,9 @@ export {
|
|
|
1074
1372
|
durableDashboard,
|
|
1075
1373
|
durableDurationProvider,
|
|
1076
1374
|
durableExecutionScope,
|
|
1375
|
+
durableFailedTriggersProvider,
|
|
1376
|
+
durableLifecycleTriggersProvider,
|
|
1377
|
+
durableOwedLifecycleProvider,
|
|
1077
1378
|
durableRecentFailuresProvider,
|
|
1078
1379
|
durableRunsOverTimeProvider,
|
|
1079
1380
|
durableStateBreakdownProvider,
|
|
@@ -1085,6 +1386,7 @@ export {
|
|
|
1085
1386
|
durableTraceContext,
|
|
1086
1387
|
durableWorkerHealthProvider,
|
|
1087
1388
|
durableWorkerStatusProvider,
|
|
1389
|
+
runSpanContext,
|
|
1088
1390
|
runTraceContext,
|
|
1089
1391
|
runTraceId
|
|
1090
1392
|
};
|