@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.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,10 +139,14 @@ 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
|
|
148
|
+
var REMOTE_EVENT = Symbol.for("@dudousxd/nestjs-durable:remote-event");
|
|
149
|
+
var isRemote = /* @__PURE__ */ __name((event) => event[REMOTE_EVENT] === true, "isRemote");
|
|
142
150
|
var DurableTelescopeWatcher = class {
|
|
143
151
|
static {
|
|
144
152
|
__name(this, "DurableTelescopeWatcher");
|
|
@@ -155,7 +163,9 @@ var DurableTelescopeWatcher = class {
|
|
|
155
163
|
if (typeof engine?.subscribe !== "function") return;
|
|
156
164
|
const tracer = trace3.getTracer("@dudousxd/nestjs-durable-telescope");
|
|
157
165
|
engine.subscribe((event) => {
|
|
166
|
+
if (isRemote(event)) return;
|
|
158
167
|
const family = runEventFamily(event.type);
|
|
168
|
+
const source = lifecycleSourceOfEvent(event);
|
|
159
169
|
const record = /* @__PURE__ */ __name(() => ctx.record({
|
|
160
170
|
type: this.type,
|
|
161
171
|
...family !== void 0 ? {
|
|
@@ -179,7 +189,13 @@ var DurableTelescopeWatcher = class {
|
|
|
179
189
|
output: event.output,
|
|
180
190
|
error: event.error,
|
|
181
191
|
durationMs: event.durationMs,
|
|
182
|
-
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
|
+
} : {}
|
|
183
199
|
},
|
|
184
200
|
tags: this.tags(event)
|
|
185
201
|
}), "record");
|
|
@@ -203,6 +219,21 @@ var DurableTelescopeWatcher = class {
|
|
|
203
219
|
}
|
|
204
220
|
context3.with(trace3.setSpan(base, step), record);
|
|
205
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();
|
|
206
237
|
} else {
|
|
207
238
|
context3.with(base, record);
|
|
208
239
|
}
|
|
@@ -233,9 +264,28 @@ var DurableTelescopeWatcher = class {
|
|
|
233
264
|
tags.push(event.type === "run.routed" ? "routed" : "unrouted");
|
|
234
265
|
tags.push(`onError:${event.error?.code ?? "*"}`);
|
|
235
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
|
+
}
|
|
236
278
|
return tags;
|
|
237
279
|
}
|
|
238
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");
|
|
239
289
|
|
|
240
290
|
// src/durable-telescope.extension.ts
|
|
241
291
|
import { defineTelescopeExtension } from "@dudousxd/nestjs-telescope";
|
|
@@ -357,6 +407,66 @@ function durableDashboard(opts = {}) {
|
|
|
357
407
|
}
|
|
358
408
|
]
|
|
359
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
|
+
},
|
|
360
470
|
{
|
|
361
471
|
kind: "table",
|
|
362
472
|
// Lists ALL worker groups (starved ones sorted first); the Status column
|
|
@@ -384,6 +494,52 @@ function durableDashboard(opts = {}) {
|
|
|
384
494
|
label: "Status"
|
|
385
495
|
}
|
|
386
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
|
|
387
543
|
}
|
|
388
544
|
]
|
|
389
545
|
},
|
|
@@ -502,7 +658,7 @@ function durableDashboard(opts = {}) {
|
|
|
502
658
|
__name(durableDashboard, "durableDashboard");
|
|
503
659
|
|
|
504
660
|
// src/durable-data-providers.ts
|
|
505
|
-
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";
|
|
506
662
|
import { TELESCOPE_STORAGE } from "@dudousxd/nestjs-telescope";
|
|
507
663
|
function percentile(sorted, p) {
|
|
508
664
|
if (sorted.length === 0) return 0;
|
|
@@ -1032,6 +1188,148 @@ function durableStateBreakdownProvider() {
|
|
|
1032
1188
|
};
|
|
1033
1189
|
}
|
|
1034
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");
|
|
1035
1333
|
|
|
1036
1334
|
// src/durable-telescope.extension.ts
|
|
1037
1335
|
function durableTelescopeExtension(opts = {}) {
|
|
@@ -1060,7 +1358,10 @@ function durableTelescopeExtension(opts = {}) {
|
|
|
1060
1358
|
durableRunsOverTimeProvider(),
|
|
1061
1359
|
durableSuccessRateProvider(),
|
|
1062
1360
|
durableThroughputProvider(),
|
|
1063
|
-
durableStateBreakdownProvider()
|
|
1361
|
+
durableStateBreakdownProvider(),
|
|
1362
|
+
durableFailedTriggersProvider(),
|
|
1363
|
+
durableOwedLifecycleProvider(),
|
|
1364
|
+
durableLifecycleTriggersProvider()
|
|
1064
1365
|
], "dataProviders")
|
|
1065
1366
|
});
|
|
1066
1367
|
}
|
|
@@ -1071,6 +1372,9 @@ export {
|
|
|
1071
1372
|
durableDashboard,
|
|
1072
1373
|
durableDurationProvider,
|
|
1073
1374
|
durableExecutionScope,
|
|
1375
|
+
durableFailedTriggersProvider,
|
|
1376
|
+
durableLifecycleTriggersProvider,
|
|
1377
|
+
durableOwedLifecycleProvider,
|
|
1074
1378
|
durableRecentFailuresProvider,
|
|
1075
1379
|
durableRunsOverTimeProvider,
|
|
1076
1380
|
durableStateBreakdownProvider,
|
|
@@ -1082,6 +1386,7 @@ export {
|
|
|
1082
1386
|
durableTraceContext,
|
|
1083
1387
|
durableWorkerHealthProvider,
|
|
1084
1388
|
durableWorkerStatusProvider,
|
|
1389
|
+
runSpanContext,
|
|
1085
1390
|
runTraceContext,
|
|
1086
1391
|
runTraceId
|
|
1087
1392
|
};
|