@dudousxd/nestjs-durable-telescope 0.7.2 → 0.8.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 +176 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +165 -8
- package/dist/index.d.ts +165 -8
- package/dist/index.js +172 -49
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -22,8 +22,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
24
|
DurableTelescopeWatcher: () => DurableTelescopeWatcher,
|
|
25
|
+
currentDurableScope: () => currentDurableScope,
|
|
25
26
|
durableDashboard: () => durableDashboard,
|
|
26
27
|
durableDurationProvider: () => durableDurationProvider,
|
|
28
|
+
durableExecutionScope: () => durableExecutionScope,
|
|
27
29
|
durableRecentFailuresProvider: () => durableRecentFailuresProvider,
|
|
28
30
|
durableRunsOverTimeProvider: () => durableRunsOverTimeProvider,
|
|
29
31
|
durableStateBreakdownProvider: () => durableStateBreakdownProvider,
|
|
@@ -32,30 +34,146 @@ __export(index_exports, {
|
|
|
32
34
|
durableTelescopeExtension: () => durableTelescopeExtension,
|
|
33
35
|
durableThroughputProvider: () => durableThroughputProvider,
|
|
34
36
|
durableTimeseriesProvider: () => durableTimeseriesProvider,
|
|
37
|
+
durableTraceContext: () => durableTraceContext,
|
|
35
38
|
durableWorkerHealthProvider: () => durableWorkerHealthProvider,
|
|
36
|
-
durableWorkerStatusProvider: () => durableWorkerStatusProvider
|
|
39
|
+
durableWorkerStatusProvider: () => durableWorkerStatusProvider,
|
|
40
|
+
runTraceContext: () => runTraceContext,
|
|
41
|
+
runTraceId: () => runTraceId
|
|
37
42
|
});
|
|
38
43
|
module.exports = __toCommonJS(index_exports);
|
|
39
44
|
|
|
40
45
|
// src/durable-telescope.watcher.ts
|
|
41
46
|
var import_nestjs_durable_core = require("@dudousxd/nestjs-durable-core");
|
|
47
|
+
var import_api3 = require("@opentelemetry/api");
|
|
48
|
+
|
|
49
|
+
// src/durable-execution-scope.ts
|
|
50
|
+
var import_node_async_hooks = require("async_hooks");
|
|
51
|
+
var import_node_crypto2 = require("crypto");
|
|
52
|
+
var import_api2 = require("@opentelemetry/api");
|
|
53
|
+
|
|
54
|
+
// src/durable-run-trace.ts
|
|
55
|
+
var import_node_crypto = require("crypto");
|
|
42
56
|
var import_api = require("@opentelemetry/api");
|
|
57
|
+
function runTraceId(runId) {
|
|
58
|
+
return (0, import_node_crypto.createHash)("sha256").update(`durable:run:${runId}`).digest("hex").slice(0, 32);
|
|
59
|
+
}
|
|
60
|
+
__name(runTraceId, "runTraceId");
|
|
61
|
+
function runSpanId(runId) {
|
|
62
|
+
return (0, import_node_crypto.createHash)("sha256").update(`durable:run-span:${runId}`).digest("hex").slice(0, 16);
|
|
63
|
+
}
|
|
64
|
+
__name(runSpanId, "runSpanId");
|
|
65
|
+
function runTraceContext(runId, parent = import_api.context.active()) {
|
|
66
|
+
return import_api.trace.setSpanContext(parent, {
|
|
67
|
+
traceId: runTraceId(runId),
|
|
68
|
+
spanId: runSpanId(runId),
|
|
69
|
+
traceFlags: import_api.TraceFlags.SAMPLED,
|
|
70
|
+
isRemote: true
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
__name(runTraceContext, "runTraceContext");
|
|
74
|
+
|
|
75
|
+
// src/durable-execution-scope.ts
|
|
76
|
+
var activeScope = new import_node_async_hooks.AsyncLocalStorage();
|
|
77
|
+
function currentDurableScope() {
|
|
78
|
+
return activeScope.getStore();
|
|
79
|
+
}
|
|
80
|
+
__name(currentDurableScope, "currentDurableScope");
|
|
81
|
+
var DURABLE_BATCH_ORIGIN = "queue";
|
|
82
|
+
function spanName(execution) {
|
|
83
|
+
if (execution.unit === "step") return `step ${execution.name ?? "?"}`;
|
|
84
|
+
return `workflow ${execution.workflow ?? "?"}`;
|
|
85
|
+
}
|
|
86
|
+
__name(spanName, "spanName");
|
|
87
|
+
function spanAttributes(execution) {
|
|
88
|
+
const attributes = {
|
|
89
|
+
"durable.run_id": execution.runId
|
|
90
|
+
};
|
|
91
|
+
if (execution.workflow !== void 0) attributes["durable.workflow"] = execution.workflow;
|
|
92
|
+
if (execution.name !== void 0) attributes["durable.step.name"] = execution.name;
|
|
93
|
+
if (execution.seq !== void 0) attributes["durable.step.seq"] = execution.seq;
|
|
94
|
+
if (execution.attempt !== void 0) attributes["durable.step.attempt"] = execution.attempt;
|
|
95
|
+
return attributes;
|
|
96
|
+
}
|
|
97
|
+
__name(spanAttributes, "spanAttributes");
|
|
98
|
+
function durableExecutionScope(ctx) {
|
|
99
|
+
const tracer = import_api2.trace.getTracer("@dudousxd/nestjs-durable-telescope");
|
|
100
|
+
const owner = {};
|
|
101
|
+
return (execution, next) => {
|
|
102
|
+
const open = activeScope.getStore();
|
|
103
|
+
if (open?.owner === owner && open.runId === execution.runId) return next();
|
|
104
|
+
if (ctx.config.enabled === false) return next();
|
|
105
|
+
try {
|
|
106
|
+
const parent = runTraceContext(execution.runId);
|
|
107
|
+
const span = tracer.startSpan(spanName(execution), {
|
|
108
|
+
attributes: spanAttributes(execution)
|
|
109
|
+
}, parent);
|
|
110
|
+
const spanContext = span.spanContext();
|
|
111
|
+
const scope = {
|
|
112
|
+
runId: execution.runId,
|
|
113
|
+
traceId: runTraceId(execution.runId),
|
|
114
|
+
spanId: span.isRecording() ? spanContext.spanId : (0, import_node_crypto2.randomBytes)(8).toString("hex"),
|
|
115
|
+
owner
|
|
116
|
+
};
|
|
117
|
+
return import_api2.context.with(import_api2.trace.setSpan(parent, span), () => activeScope.run(scope, () => ctx.runInBatch(DURABLE_BATCH_ORIGIN, next).then((value) => {
|
|
118
|
+
span.end();
|
|
119
|
+
return value;
|
|
120
|
+
}, (error) => {
|
|
121
|
+
span.setStatus({
|
|
122
|
+
code: import_api2.SpanStatusCode.ERROR,
|
|
123
|
+
...error instanceof Error ? {
|
|
124
|
+
message: error.message
|
|
125
|
+
} : {}
|
|
126
|
+
});
|
|
127
|
+
span.end();
|
|
128
|
+
throw error;
|
|
129
|
+
})));
|
|
130
|
+
} catch {
|
|
131
|
+
return next();
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
__name(durableExecutionScope, "durableExecutionScope");
|
|
136
|
+
function durableTraceContext(delegate) {
|
|
137
|
+
return {
|
|
138
|
+
current() {
|
|
139
|
+
const fromDelegate = delegate?.current() ?? null;
|
|
140
|
+
if (fromDelegate) return fromDelegate;
|
|
141
|
+
const scope = activeScope.getStore();
|
|
142
|
+
return scope ? {
|
|
143
|
+
traceId: scope.traceId,
|
|
144
|
+
spanId: scope.spanId
|
|
145
|
+
} : null;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
__name(durableTraceContext, "durableTraceContext");
|
|
150
|
+
|
|
151
|
+
// src/durable-telescope.watcher.ts
|
|
43
152
|
var DurableTelescopeWatcher = class {
|
|
44
153
|
static {
|
|
45
154
|
__name(this, "DurableTelescopeWatcher");
|
|
46
155
|
}
|
|
47
156
|
type = "durable";
|
|
48
|
-
/**
|
|
49
|
-
|
|
157
|
+
/** Removes the execution wrapper from the engine's process-level registry. */
|
|
158
|
+
disposeScope;
|
|
50
159
|
register(ctx) {
|
|
160
|
+
this.disposeScope?.();
|
|
161
|
+
this.disposeScope = (0, import_nestjs_durable_core.useDurableExecution)(durableExecutionScope(ctx));
|
|
51
162
|
const engine = ctx.moduleRef.get(import_nestjs_durable_core.WorkflowEngine, {
|
|
52
163
|
strict: false
|
|
53
164
|
});
|
|
54
165
|
if (typeof engine?.subscribe !== "function") return;
|
|
55
|
-
const tracer =
|
|
166
|
+
const tracer = import_api3.trace.getTracer("@dudousxd/nestjs-durable-telescope");
|
|
56
167
|
engine.subscribe((event) => {
|
|
57
168
|
const record = /* @__PURE__ */ __name(() => ctx.record({
|
|
58
169
|
type: this.type,
|
|
170
|
+
// Stated explicitly rather than left to the ambient span, and the Recorder honours an
|
|
171
|
+
// explicit `traceId` above everything else. A lifecycle event is very often emitted
|
|
172
|
+
// OUTSIDE any execution scope — a remote step's result lands in a transport callback, a
|
|
173
|
+
// recovery sweep emits for a run this process never executed — and those entries are
|
|
174
|
+
// exactly the ones an operator follows into the trace. We know the run, so we know the
|
|
175
|
+
// trace; there is no reason to let it depend on where the emit happened to land.
|
|
176
|
+
traceId: runTraceId(event.runId),
|
|
59
177
|
content: {
|
|
60
178
|
event: event.type,
|
|
61
179
|
workflow: event.workflow,
|
|
@@ -69,16 +187,7 @@ var DurableTelescopeWatcher = class {
|
|
|
69
187
|
},
|
|
70
188
|
tags: this.tags(event)
|
|
71
189
|
}), "record");
|
|
72
|
-
|
|
73
|
-
this.roots.set(event.runId, tracer.startSpan(`workflow ${event.workflow ?? "?"}`, {
|
|
74
|
-
attributes: {
|
|
75
|
-
"durable.run_id": event.runId,
|
|
76
|
-
"durable.workflow": event.workflow
|
|
77
|
-
}
|
|
78
|
-
}));
|
|
79
|
-
}
|
|
80
|
-
const root = this.roots.get(event.runId);
|
|
81
|
-
const base = root ? import_api.trace.setSpan(import_api.context.active(), root) : import_api.context.active();
|
|
190
|
+
const base = runTraceContext(event.runId);
|
|
82
191
|
if (event.name != null && event.seq != null) {
|
|
83
192
|
const step = tracer.startSpan(`step ${event.name}`, {
|
|
84
193
|
attributes: {
|
|
@@ -87,27 +196,32 @@ var DurableTelescopeWatcher = class {
|
|
|
87
196
|
"durable.step.kind": event.kind
|
|
88
197
|
}
|
|
89
198
|
}, base);
|
|
90
|
-
|
|
199
|
+
if (event.type === "step.failed") {
|
|
200
|
+
const message = event.error?.message;
|
|
201
|
+
step.setStatus(message !== void 0 ? {
|
|
202
|
+
code: import_api3.SpanStatusCode.ERROR,
|
|
203
|
+
message
|
|
204
|
+
} : {
|
|
205
|
+
code: import_api3.SpanStatusCode.ERROR
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
import_api3.context.with(import_api3.trace.setSpan(base, step), record);
|
|
91
209
|
step.end();
|
|
92
210
|
} else {
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
if (event.type === "run.completed" || event.type === "run.suspended") {
|
|
96
|
-
root?.end();
|
|
97
|
-
this.roots.delete(event.runId);
|
|
98
|
-
} else if (event.type === "run.failed") {
|
|
99
|
-
const message = event.error?.message;
|
|
100
|
-
root?.setStatus(message !== void 0 ? {
|
|
101
|
-
code: import_api.SpanStatusCode.ERROR,
|
|
102
|
-
message
|
|
103
|
-
} : {
|
|
104
|
-
code: import_api.SpanStatusCode.ERROR
|
|
105
|
-
});
|
|
106
|
-
root?.end();
|
|
107
|
-
this.roots.delete(event.runId);
|
|
211
|
+
import_api3.context.with(base, record);
|
|
108
212
|
}
|
|
109
213
|
});
|
|
110
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Detach the execution wrapper. The `Watcher` SPI has no teardown hook, so nothing calls this in a
|
|
217
|
+
* running app — where the wrapper should live exactly as long as the process does. It exists so a
|
|
218
|
+
* test that registers a watcher does not leave a wrapper behind in the process-level registry for
|
|
219
|
+
* the next test to trip over.
|
|
220
|
+
*/
|
|
221
|
+
unregister() {
|
|
222
|
+
this.disposeScope?.();
|
|
223
|
+
this.disposeScope = void 0;
|
|
224
|
+
}
|
|
111
225
|
tags(event) {
|
|
112
226
|
const tags = [
|
|
113
227
|
"durable",
|
|
@@ -284,7 +398,10 @@ function durableDashboard(opts = {}) {
|
|
|
284
398
|
// One row per LIVE worker (flattened from each group's heartbeats), exposing the live
|
|
285
399
|
// WorkerStatus the heartbeat carries: fixed vs. adaptive limit, in-flight saturation,
|
|
286
400
|
// RAM%/CPU%, throughput, p95, and why the adaptive controller last moved. A worker from
|
|
287
|
-
// an older SDK with no status still lists,
|
|
401
|
+
// an older SDK with no status still lists, reading 'n/a' — distinct from the '—' of a
|
|
402
|
+
// worker that reports fine but has nothing to measure yet, which is what an idle
|
|
403
|
+
// deployment looks like (throughput and p95 come off a rolling window of completions,
|
|
404
|
+
// so they are empty until a step finishes inside it). The
|
|
288
405
|
// adaptive min–max range rides the In-flight column's row value (`minMax` key) for those
|
|
289
406
|
// that want it; the visible columns stay readable.
|
|
290
407
|
title: "Workers",
|
|
@@ -598,17 +715,23 @@ function relAgo(atMs) {
|
|
|
598
715
|
return `${Math.round(h / 24)}d ago`;
|
|
599
716
|
}
|
|
600
717
|
__name(relAgo, "relAgo");
|
|
601
|
-
|
|
602
|
-
|
|
718
|
+
var NOT_REPORTED = "n/a";
|
|
719
|
+
var NOT_YET_MEASURED = "\u2014";
|
|
720
|
+
function fmtLastAdjust(status) {
|
|
721
|
+
if (!status) return NOT_REPORTED;
|
|
722
|
+
const a = status.lastAdjust;
|
|
723
|
+
if (!a) return NOT_YET_MEASURED;
|
|
603
724
|
return `${a.reason} ${a.from}\u2192${a.to} ${relAgo(a.at)}`;
|
|
604
725
|
}
|
|
605
726
|
__name(fmtLastAdjust, "fmtLastAdjust");
|
|
606
|
-
function fmtPct(n) {
|
|
607
|
-
|
|
727
|
+
function fmtPct(n, status) {
|
|
728
|
+
if (typeof n === "number") return Math.round(n);
|
|
729
|
+
return status ? NOT_YET_MEASURED : NOT_REPORTED;
|
|
608
730
|
}
|
|
609
731
|
__name(fmtPct, "fmtPct");
|
|
610
|
-
function fmtNum(n) {
|
|
611
|
-
|
|
732
|
+
function fmtNum(n, status) {
|
|
733
|
+
if (typeof n === "number") return Math.round(n);
|
|
734
|
+
return status ? NOT_YET_MEASURED : NOT_REPORTED;
|
|
612
735
|
}
|
|
613
736
|
__name(fmtNum, "fmtNum");
|
|
614
737
|
function durableWorkerStatusProvider() {
|
|
@@ -622,11 +745,11 @@ function durableWorkerStatusProvider() {
|
|
|
622
745
|
const rows = health.slice().sort((a, b) => Number(b.depth > 0) - Number(a.depth > 0) || a.group.localeCompare(b.group)).flatMap((group) => group.liveWorkers.slice().sort((a, b) => a.instanceId.localeCompare(b.instanceId)).map((worker) => {
|
|
623
746
|
const status = worker.status;
|
|
624
747
|
const concurrency = status?.concurrency;
|
|
625
|
-
const mode = concurrency?.mode ??
|
|
626
|
-
const limit = concurrency?.limit ??
|
|
627
|
-
const inFlight = status?.inFlight ??
|
|
628
|
-
const minMax = concurrency?.mode === "adaptive" ? `${concurrency.min ?? 1}\u2013${concurrency.max ?? 32}` :
|
|
629
|
-
const saturation = concurrency && status ? `${status.inFlight}/${concurrency.limit}` :
|
|
748
|
+
const mode = concurrency?.mode ?? NOT_REPORTED;
|
|
749
|
+
const limit = concurrency?.limit ?? NOT_REPORTED;
|
|
750
|
+
const inFlight = status?.inFlight ?? NOT_REPORTED;
|
|
751
|
+
const minMax = concurrency?.mode === "adaptive" ? `${concurrency.min ?? 1}\u2013${concurrency.max ?? 32}` : NOT_REPORTED;
|
|
752
|
+
const saturation = concurrency && status ? `${status.inFlight}/${concurrency.limit}` : NOT_REPORTED;
|
|
630
753
|
return {
|
|
631
754
|
group: group.group,
|
|
632
755
|
instanceId: worker.instanceId,
|
|
@@ -636,11 +759,11 @@ function durableWorkerStatusProvider() {
|
|
|
636
759
|
inFlight,
|
|
637
760
|
saturation,
|
|
638
761
|
queued: group.depth,
|
|
639
|
-
rssPct: fmtPct(status?.rssPct),
|
|
640
|
-
cpuPct: fmtPct(status?.cpuPct),
|
|
641
|
-
throughputPerMin: fmtNum(status?.throughputPerMin),
|
|
642
|
-
p95Ms: fmtNum(status?.p95Ms),
|
|
643
|
-
lastAdjust: fmtLastAdjust(status
|
|
762
|
+
rssPct: fmtPct(status?.rssPct, status),
|
|
763
|
+
cpuPct: fmtPct(status?.cpuPct, status),
|
|
764
|
+
throughputPerMin: fmtNum(status?.throughputPerMin, status),
|
|
765
|
+
p95Ms: fmtNum(status?.p95Ms, status),
|
|
766
|
+
lastAdjust: fmtLastAdjust(status)
|
|
644
767
|
};
|
|
645
768
|
}));
|
|
646
769
|
return {
|
|
@@ -906,8 +1029,10 @@ __name(durableTelescopeExtension, "durableTelescopeExtension");
|
|
|
906
1029
|
// Annotate the CommonJS export names for ESM import in node:
|
|
907
1030
|
0 && (module.exports = {
|
|
908
1031
|
DurableTelescopeWatcher,
|
|
1032
|
+
currentDurableScope,
|
|
909
1033
|
durableDashboard,
|
|
910
1034
|
durableDurationProvider,
|
|
1035
|
+
durableExecutionScope,
|
|
911
1036
|
durableRecentFailuresProvider,
|
|
912
1037
|
durableRunsOverTimeProvider,
|
|
913
1038
|
durableStateBreakdownProvider,
|
|
@@ -916,7 +1041,10 @@ __name(durableTelescopeExtension, "durableTelescopeExtension");
|
|
|
916
1041
|
durableTelescopeExtension,
|
|
917
1042
|
durableThroughputProvider,
|
|
918
1043
|
durableTimeseriesProvider,
|
|
1044
|
+
durableTraceContext,
|
|
919
1045
|
durableWorkerHealthProvider,
|
|
920
|
-
durableWorkerStatusProvider
|
|
1046
|
+
durableWorkerStatusProvider,
|
|
1047
|
+
runTraceContext,
|
|
1048
|
+
runTraceId
|
|
921
1049
|
});
|
|
922
1050
|
//# sourceMappingURL=index.cjs.map
|