temporal-explorer 0.0.0-mvp
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/LICENSE +21 -0
- package/README.md +149 -0
- package/dist/api/index.js +4322 -0
- package/dist/cli/index.js +5292 -0
- package/dist/schemas/index.js +612 -0
- package/package.json +108 -0
- package/packages/schemas/json-schema/temporal-analysis.v1.schema.json +372 -0
- package/packages/schemas/json-schema/temporal-overlay.v1.schema.json +246 -0
- package/packages/schemas/json-schema/temporal-trace.v1.schema.json +607 -0
|
@@ -0,0 +1,4322 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/analyzer/src/index.ts
|
|
3
|
+
import { basename, resolve as resolve3 } from "path";
|
|
4
|
+
import { Project } from "ts-morph";
|
|
5
|
+
|
|
6
|
+
// packages/analyzer/src/configuration.ts
|
|
7
|
+
var severityOverrides = new Set(["error", "warning", "info", "off"]);
|
|
8
|
+
function assertStringArray(value, label) {
|
|
9
|
+
if (value !== undefined && (!Array.isArray(value) || value.some((entry) => typeof entry !== "string"))) {
|
|
10
|
+
throw new Error(`temporal-explorer.config.ts: ${label} must be an array of strings.`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function assertDiagnosticOverrides(value) {
|
|
14
|
+
if (value === undefined) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
18
|
+
throw new Error("temporal-explorer.config.ts: diagnostics must map diagnostic codes to severities.");
|
|
19
|
+
}
|
|
20
|
+
for (const [code, severity] of Object.entries(value)) {
|
|
21
|
+
if (typeof severity !== "string" || !severityOverrides.has(severity)) {
|
|
22
|
+
throw new Error(`temporal-explorer.config.ts: diagnostics.${code} must be error, warning, info, or off.`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function defineConfig(configuration) {
|
|
27
|
+
if (typeof configuration !== "object" || configuration === null) {
|
|
28
|
+
throw new Error("temporal-explorer.config.ts must export a configuration object.");
|
|
29
|
+
}
|
|
30
|
+
assertStringArray(configuration.include, "include");
|
|
31
|
+
assertStringArray(configuration.exclude, "exclude");
|
|
32
|
+
assertStringArray(configuration.temporal?.workflowGlobs, "temporal.workflowGlobs");
|
|
33
|
+
assertStringArray(configuration.temporal?.workerGlobs, "temporal.workerGlobs");
|
|
34
|
+
assertStringArray(configuration.temporal?.clientGlobs, "temporal.clientGlobs");
|
|
35
|
+
assertDiagnosticOverrides(configuration.diagnostics);
|
|
36
|
+
return configuration;
|
|
37
|
+
}
|
|
38
|
+
async function loadConfiguration(configPath) {
|
|
39
|
+
if (!await Bun.file(configPath).exists()) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const module = await import(configPath);
|
|
43
|
+
if (!module.default || typeof module.default !== "object") {
|
|
44
|
+
throw new Error(`${configPath} must default-export defineConfig(...).`);
|
|
45
|
+
}
|
|
46
|
+
return defineConfig(module.default);
|
|
47
|
+
}
|
|
48
|
+
function applySeverityOverrides(diagnostics, overrides) {
|
|
49
|
+
if (!overrides) {
|
|
50
|
+
return diagnostics;
|
|
51
|
+
}
|
|
52
|
+
const applied = [];
|
|
53
|
+
for (const diagnostic of diagnostics) {
|
|
54
|
+
const override = overrides[diagnostic.code];
|
|
55
|
+
if (override === "off") {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
applied.push(override ? { ...diagnostic, severity: override } : diagnostic);
|
|
59
|
+
}
|
|
60
|
+
return applied;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// packages/analyzer/src/package-metadata.ts
|
|
64
|
+
import { resolve } from "path";
|
|
65
|
+
function isRecord(value) {
|
|
66
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
67
|
+
}
|
|
68
|
+
function readStringRecord(value) {
|
|
69
|
+
if (!isRecord(value)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const entries = Object.entries(value).filter((entry) => typeof entry[1] === "string");
|
|
73
|
+
return Object.fromEntries(entries);
|
|
74
|
+
}
|
|
75
|
+
async function readPackageJson(root) {
|
|
76
|
+
const packagePath = resolve(root, "package.json");
|
|
77
|
+
const file = Bun.file(packagePath);
|
|
78
|
+
if (!await file.exists()) {
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
const value = await file.json();
|
|
82
|
+
if (!isRecord(value)) {
|
|
83
|
+
return {};
|
|
84
|
+
}
|
|
85
|
+
const metadata = {};
|
|
86
|
+
const dependencies = readStringRecord(value["dependencies"]);
|
|
87
|
+
const devDependencies = readStringRecord(value["devDependencies"]);
|
|
88
|
+
if (typeof value["packageManager"] === "string") {
|
|
89
|
+
metadata.packageManager = value["packageManager"];
|
|
90
|
+
}
|
|
91
|
+
if (dependencies) {
|
|
92
|
+
metadata.dependencies = dependencies;
|
|
93
|
+
}
|
|
94
|
+
if (devDependencies) {
|
|
95
|
+
metadata.devDependencies = devDependencies;
|
|
96
|
+
}
|
|
97
|
+
return metadata;
|
|
98
|
+
}
|
|
99
|
+
function getPackageManager(packageJson) {
|
|
100
|
+
const packageManager = packageJson.packageManager?.split("@")[0];
|
|
101
|
+
if (packageManager === "bun" || packageManager === "npm" || packageManager === "pnpm" || packageManager === "yarn") {
|
|
102
|
+
return packageManager;
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// packages/analyzer/src/paths.ts
|
|
108
|
+
import { relative, resolve as resolve2 } from "path";
|
|
109
|
+
function toProjectPath(root, path) {
|
|
110
|
+
return relative(root, path).split("\\").join("/");
|
|
111
|
+
}
|
|
112
|
+
function createSourceLocation(root, sourceFile, node, symbolName) {
|
|
113
|
+
const startOffset = node.getStart();
|
|
114
|
+
const endOffset = node.getEnd();
|
|
115
|
+
const start = sourceFile.getLineAndColumnAtPos(startOffset);
|
|
116
|
+
const end = sourceFile.getLineAndColumnAtPos(endOffset);
|
|
117
|
+
const baseLocation = {
|
|
118
|
+
path: toProjectPath(root, sourceFile.getFilePath()),
|
|
119
|
+
pathKind: "project-relative",
|
|
120
|
+
start: {
|
|
121
|
+
line: start.line,
|
|
122
|
+
column: start.column,
|
|
123
|
+
offset: startOffset
|
|
124
|
+
},
|
|
125
|
+
end: {
|
|
126
|
+
line: end.line,
|
|
127
|
+
column: end.column,
|
|
128
|
+
offset: endOffset
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
return symbolName ? { ...baseLocation, symbolName } : baseLocation;
|
|
132
|
+
}
|
|
133
|
+
async function hashFile(path) {
|
|
134
|
+
const file = Bun.file(path);
|
|
135
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
136
|
+
hasher.update(await file.arrayBuffer());
|
|
137
|
+
return hasher.digest("hex");
|
|
138
|
+
}
|
|
139
|
+
async function createSourceFileHashes(root, files) {
|
|
140
|
+
const entries = await Promise.all(files.map(async (file) => [toProjectPath(root, file), await hashFile(file)]));
|
|
141
|
+
return Object.fromEntries(entries.toSorted(([left], [right]) => left.localeCompare(right)));
|
|
142
|
+
}
|
|
143
|
+
async function discoverFiles(root, globs) {
|
|
144
|
+
const discovered = new Set;
|
|
145
|
+
for (const pattern of globs) {
|
|
146
|
+
const glob = new Bun.Glob(pattern);
|
|
147
|
+
for await (const relativePath of glob.scan({ cwd: root, onlyFiles: true })) {
|
|
148
|
+
if (!relativePath.endsWith(".test.ts") && !relativePath.endsWith(".spec.ts")) {
|
|
149
|
+
discovered.add(resolve2(root, relativePath));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return [...discovered].toSorted((left, right) => left.localeCompare(right));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// packages/analyzer/src/workflow-analysis.ts
|
|
157
|
+
import {
|
|
158
|
+
Node as Node8,
|
|
159
|
+
SyntaxKind as SyntaxKind7
|
|
160
|
+
} from "ts-morph";
|
|
161
|
+
|
|
162
|
+
// packages/analyzer/src/determinism-diagnostics.ts
|
|
163
|
+
import { SyntaxKind as SyntaxKind4 } from "ts-morph";
|
|
164
|
+
|
|
165
|
+
// packages/analyzer/src/workflow-messages.ts
|
|
166
|
+
import {
|
|
167
|
+
Node as Node2,
|
|
168
|
+
SyntaxKind as SyntaxKind2
|
|
169
|
+
} from "ts-morph";
|
|
170
|
+
|
|
171
|
+
// packages/analyzer/src/symbols.ts
|
|
172
|
+
import {
|
|
173
|
+
Node,
|
|
174
|
+
SyntaxKind
|
|
175
|
+
} from "ts-morph";
|
|
176
|
+
var temporalWorkflowModule = "@temporalio/workflow";
|
|
177
|
+
var temporalWorkerModule = "@temporalio/worker";
|
|
178
|
+
function isWorkflowModuleCall(call, importedName) {
|
|
179
|
+
const expression = call.getExpression();
|
|
180
|
+
return Node.isIdentifier(expression) && isNamedImportFrom(expression, temporalWorkflowModule, importedName);
|
|
181
|
+
}
|
|
182
|
+
function isNamedImportFrom(identifier, moduleSpecifier, importedName) {
|
|
183
|
+
const symbol = identifier.getSymbol();
|
|
184
|
+
return symbol?.getDeclarations().some((declaration) => {
|
|
185
|
+
if (!Node.isImportSpecifier(declaration)) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
const importDeclaration = declaration.getImportDeclaration();
|
|
189
|
+
const exportedName = declaration.compilerNode.propertyName?.getText() ?? declaration.getNameNode().getText();
|
|
190
|
+
return importDeclaration.getModuleSpecifierValue() === moduleSpecifier && exportedName === importedName;
|
|
191
|
+
}) ?? false;
|
|
192
|
+
}
|
|
193
|
+
function isProxyActivitiesCall(call) {
|
|
194
|
+
const expression = call.getExpression();
|
|
195
|
+
return Node.isIdentifier(expression) && isNamedImportFrom(expression, temporalWorkflowModule, "proxyActivities");
|
|
196
|
+
}
|
|
197
|
+
function unwrapCasts(node) {
|
|
198
|
+
let current = node;
|
|
199
|
+
while (Node.isAsExpression(current) || Node.isParenthesizedExpression(current)) {
|
|
200
|
+
current = current.getExpression();
|
|
201
|
+
}
|
|
202
|
+
return current;
|
|
203
|
+
}
|
|
204
|
+
function findActivityProxyVariables(sourceFile) {
|
|
205
|
+
const proxyVariables = new Set;
|
|
206
|
+
for (let pass = 0;pass < 2; pass += 1) {
|
|
207
|
+
for (const declaration of sourceFile.getDescendantsOfKind(SyntaxKind.VariableDeclaration)) {
|
|
208
|
+
const nameNode = declaration.getNameNode();
|
|
209
|
+
const initializer = declaration.getInitializer();
|
|
210
|
+
if (!initializer || !Node.isIdentifier(nameNode)) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const unwrapped = unwrapCasts(initializer);
|
|
214
|
+
if (Node.isCallExpression(unwrapped) && isProxyActivitiesCall(unwrapped)) {
|
|
215
|
+
proxyVariables.add(nameNode.getText());
|
|
216
|
+
} else if (Node.isIdentifier(unwrapped) && proxyVariables.has(unwrapped.getText())) {
|
|
217
|
+
proxyVariables.add(nameNode.getText());
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return proxyVariables;
|
|
222
|
+
}
|
|
223
|
+
function isProxySourceExpression(node, proxyVariables) {
|
|
224
|
+
const unwrapped = unwrapCasts(node);
|
|
225
|
+
return Node.isCallExpression(unwrapped) && isProxyActivitiesCall(unwrapped) || Node.isIdentifier(unwrapped) && proxyVariables.has(unwrapped.getText());
|
|
226
|
+
}
|
|
227
|
+
function collectBindingElements(pattern, bindings) {
|
|
228
|
+
for (const element of pattern.getElements()) {
|
|
229
|
+
const localNameNode = element.getNameNode();
|
|
230
|
+
if (Node.isIdentifier(localNameNode)) {
|
|
231
|
+
const activityName = element.getPropertyNameNode()?.getText() ?? localNameNode.getText();
|
|
232
|
+
bindings.set(localNameNode.getText(), activityName);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function findDestructuredActivityBindings(sourceFile, proxyVariables) {
|
|
237
|
+
const bindings = new Map;
|
|
238
|
+
for (const declaration of sourceFile.getDescendantsOfKind(SyntaxKind.VariableDeclaration)) {
|
|
239
|
+
const nameNode = declaration.getNameNode();
|
|
240
|
+
const initializer = declaration.getInitializer();
|
|
241
|
+
if (initializer && Node.isObjectBindingPattern(nameNode) && isProxySourceExpression(initializer, proxyVariables)) {
|
|
242
|
+
collectBindingElements(nameNode, bindings);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return bindings;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// packages/analyzer/src/type-shapes.ts
|
|
249
|
+
function createTypeShape(id, display, source, displayName) {
|
|
250
|
+
return {
|
|
251
|
+
id,
|
|
252
|
+
display,
|
|
253
|
+
...displayName ? { displayName } : {},
|
|
254
|
+
kind: display === "void" ? "primitive" : "external",
|
|
255
|
+
...source ? { source } : {},
|
|
256
|
+
confidence: display === "unknown" ? "unknown" : "exact"
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// packages/analyzer/src/tuple-shapes.ts
|
|
261
|
+
var nestingOpeners = new Set(["<", "[", "{", "("]);
|
|
262
|
+
var nestingClosers = new Set([">", "]", "}", ")"]);
|
|
263
|
+
function splitTopLevel(contents) {
|
|
264
|
+
const parts = [];
|
|
265
|
+
let depth = 0;
|
|
266
|
+
let current = "";
|
|
267
|
+
for (const character of contents) {
|
|
268
|
+
if (nestingOpeners.has(character)) {
|
|
269
|
+
depth += 1;
|
|
270
|
+
} else if (nestingClosers.has(character)) {
|
|
271
|
+
depth -= 1;
|
|
272
|
+
}
|
|
273
|
+
if (character === "," && depth === 0) {
|
|
274
|
+
parts.push(current.trim());
|
|
275
|
+
current = "";
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
current += character;
|
|
279
|
+
}
|
|
280
|
+
if (current.trim()) {
|
|
281
|
+
parts.push(current.trim());
|
|
282
|
+
}
|
|
283
|
+
return parts;
|
|
284
|
+
}
|
|
285
|
+
function extractTupleShapes(idPrefix, tupleText) {
|
|
286
|
+
if (!tupleText) {
|
|
287
|
+
return [];
|
|
288
|
+
}
|
|
289
|
+
const tupleContents = tupleText.replace(/^\[/u, "").replace(/\]$/u, "").trim();
|
|
290
|
+
if (!tupleContents) {
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
return splitTopLevel(tupleContents).map((display, index) => createTypeShape(`${idPrefix}:arg:${index}`, display));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// packages/analyzer/src/workflow-messages.ts
|
|
297
|
+
function readMessageName(call) {
|
|
298
|
+
const firstArgument = call.getArguments()[0];
|
|
299
|
+
if (firstArgument && Node2.isStringLiteral(firstArgument)) {
|
|
300
|
+
return { name: firstArgument.getLiteralValue(), literal: true };
|
|
301
|
+
}
|
|
302
|
+
return { name: firstArgument?.getText() || "unknownMessage", literal: false };
|
|
303
|
+
}
|
|
304
|
+
function getMessageKind(initializer) {
|
|
305
|
+
if (isWorkflowModuleCall(initializer, "defineQuery")) {
|
|
306
|
+
return "query";
|
|
307
|
+
}
|
|
308
|
+
return isWorkflowModuleCall(initializer, "defineUpdate") ? "update" : undefined;
|
|
309
|
+
}
|
|
310
|
+
function createDeclaredMessage(root, sourceFile, variableName, kind, initializer) {
|
|
311
|
+
const { name, literal } = readMessageName(initializer);
|
|
312
|
+
const typeArguments = initializer.getTypeArguments();
|
|
313
|
+
const resultText = typeArguments[0]?.getText();
|
|
314
|
+
return {
|
|
315
|
+
variableName,
|
|
316
|
+
kind,
|
|
317
|
+
name,
|
|
318
|
+
source: createSourceLocation(root, sourceFile, initializer, name),
|
|
319
|
+
args: extractTupleShapes(`${kind}:${name}`, typeArguments[1]?.getText()),
|
|
320
|
+
...resultText ? { result: createTypeShape(`${kind}:${name}:result`, resultText) } : {},
|
|
321
|
+
confidence: literal ? "exact" : "dynamic"
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function readMessageDeclaration(root, sourceFile, declaration) {
|
|
325
|
+
const initializer = declaration.getInitializer();
|
|
326
|
+
const nameNode = declaration.getNameNode();
|
|
327
|
+
if (!initializer || !Node2.isCallExpression(initializer) || !Node2.isIdentifier(nameNode)) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const kind = getMessageKind(initializer);
|
|
331
|
+
if (!kind) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
return createDeclaredMessage(root, sourceFile, nameNode.getText(), kind, initializer);
|
|
335
|
+
}
|
|
336
|
+
function findMessageDeclarations(root, sourceFile) {
|
|
337
|
+
const declarations = new Map;
|
|
338
|
+
for (const declaration of sourceFile.getDescendantsOfKind(SyntaxKind2.VariableDeclaration)) {
|
|
339
|
+
const declared = readMessageDeclaration(root, sourceFile, declaration);
|
|
340
|
+
if (declared) {
|
|
341
|
+
declarations.set(declared.variableName, declared);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return declarations;
|
|
345
|
+
}
|
|
346
|
+
function readValidatorNode(call) {
|
|
347
|
+
const options = call.getArguments()[2];
|
|
348
|
+
if (!options || !Node2.isObjectLiteralExpression(options)) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const property = options.getProperty("validator");
|
|
352
|
+
if (property && Node2.isPropertyAssignment(property)) {
|
|
353
|
+
return property.getInitializer();
|
|
354
|
+
}
|
|
355
|
+
return property;
|
|
356
|
+
}
|
|
357
|
+
function findMessageRegistrations(functionDeclaration, declaredMessages) {
|
|
358
|
+
const registrations = [];
|
|
359
|
+
for (const call of functionDeclaration.getDescendantsOfKind(SyntaxKind2.CallExpression)) {
|
|
360
|
+
if (!isWorkflowModuleCall(call, "setHandler")) {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
const [handleArgument, handlerArgument] = call.getArguments();
|
|
364
|
+
if (!handleArgument || !Node2.isIdentifier(handleArgument)) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
const declared = declaredMessages.get(handleArgument.getText());
|
|
368
|
+
if (!declared) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
registrations.push({
|
|
372
|
+
declared,
|
|
373
|
+
registration: call,
|
|
374
|
+
handler: handlerArgument,
|
|
375
|
+
validator: readValidatorNode(call)
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
return registrations;
|
|
379
|
+
}
|
|
380
|
+
function createQueryDefinition(root, workflowName, registration) {
|
|
381
|
+
const { declared } = registration;
|
|
382
|
+
return {
|
|
383
|
+
id: `query:${workflowName}:${declared.name}`,
|
|
384
|
+
name: declared.name,
|
|
385
|
+
source: declared.source,
|
|
386
|
+
args: declared.args,
|
|
387
|
+
...declared.result ? { result: declared.result } : {},
|
|
388
|
+
handlerSource: createSourceLocation(root, registration.registration.getSourceFile(), registration.registration, declared.name),
|
|
389
|
+
confidence: declared.confidence
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function createUpdateDefinition(root, workflowName, registration) {
|
|
393
|
+
const { declared } = registration;
|
|
394
|
+
const sourceFile = registration.registration.getSourceFile();
|
|
395
|
+
return {
|
|
396
|
+
id: `update:${workflowName}:${declared.name}`,
|
|
397
|
+
name: declared.name,
|
|
398
|
+
source: declared.source,
|
|
399
|
+
args: declared.args,
|
|
400
|
+
...declared.result ? { result: declared.result } : {},
|
|
401
|
+
handlerSource: createSourceLocation(root, sourceFile, registration.registration, declared.name),
|
|
402
|
+
...registration.validator ? {
|
|
403
|
+
validatorSource: createSourceLocation(root, sourceFile, registration.validator)
|
|
404
|
+
} : {},
|
|
405
|
+
confidence: declared.confidence
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// packages/analyzer/src/workflow-signals.ts
|
|
410
|
+
import {
|
|
411
|
+
Node as Node3,
|
|
412
|
+
SyntaxKind as SyntaxKind3
|
|
413
|
+
} from "ts-morph";
|
|
414
|
+
function extractSignalPayloadShapes(signalName, typeArgumentText) {
|
|
415
|
+
if (!typeArgumentText) {
|
|
416
|
+
return [];
|
|
417
|
+
}
|
|
418
|
+
const tupleContents = typeArgumentText.replace(/^\[/u, "").replace(/\]$/u, "").trim();
|
|
419
|
+
if (!tupleContents) {
|
|
420
|
+
return [];
|
|
421
|
+
}
|
|
422
|
+
return splitTopLevel2(tupleContents).map((display, index) => createTypeShape(`signal:${signalName}:arg:${index}`, display));
|
|
423
|
+
}
|
|
424
|
+
var nestingOpeners2 = new Set(["<", "[", "{", "("]);
|
|
425
|
+
var nestingClosers2 = new Set([">", "]", "}", ")"]);
|
|
426
|
+
function splitTopLevel2(contents) {
|
|
427
|
+
const parts = [];
|
|
428
|
+
let depth = 0;
|
|
429
|
+
let current = "";
|
|
430
|
+
for (const character of contents) {
|
|
431
|
+
if (nestingOpeners2.has(character)) {
|
|
432
|
+
depth += 1;
|
|
433
|
+
} else if (nestingClosers2.has(character)) {
|
|
434
|
+
depth -= 1;
|
|
435
|
+
}
|
|
436
|
+
if (character === "," && depth === 0) {
|
|
437
|
+
parts.push(current.trim());
|
|
438
|
+
current = "";
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
current += character;
|
|
442
|
+
}
|
|
443
|
+
if (current.trim()) {
|
|
444
|
+
parts.push(current.trim());
|
|
445
|
+
}
|
|
446
|
+
return parts;
|
|
447
|
+
}
|
|
448
|
+
function getDefineSignalCall(declaration) {
|
|
449
|
+
const initializer = declaration.getInitializer();
|
|
450
|
+
if (!initializer || !Node3.isCallExpression(initializer)) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
return isWorkflowModuleCall(initializer, "defineSignal") ? initializer : undefined;
|
|
454
|
+
}
|
|
455
|
+
function readSignalName(call) {
|
|
456
|
+
const firstArgument = call.getArguments()[0];
|
|
457
|
+
if (firstArgument && Node3.isStringLiteral(firstArgument)) {
|
|
458
|
+
return { name: firstArgument.getLiteralValue(), literal: true };
|
|
459
|
+
}
|
|
460
|
+
return { name: firstArgument?.getText() || "unknownSignal", literal: false };
|
|
461
|
+
}
|
|
462
|
+
function readSignalDeclaration(root, sourceFile, declaration) {
|
|
463
|
+
const call = getDefineSignalCall(declaration);
|
|
464
|
+
const nameNode = declaration.getNameNode();
|
|
465
|
+
if (!call || !Node3.isIdentifier(nameNode)) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const { name, literal } = readSignalName(call);
|
|
469
|
+
return {
|
|
470
|
+
variableName: nameNode.getText(),
|
|
471
|
+
name,
|
|
472
|
+
source: createSourceLocation(root, sourceFile, call, name),
|
|
473
|
+
args: extractSignalPayloadShapes(name, call.getTypeArguments()[0]?.getText()),
|
|
474
|
+
confidence: literal ? "exact" : "dynamic"
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
function findSignalDeclarations(root, sourceFile) {
|
|
478
|
+
const declarations = new Map;
|
|
479
|
+
for (const declaration of sourceFile.getDescendantsOfKind(SyntaxKind3.VariableDeclaration)) {
|
|
480
|
+
const declared = readSignalDeclaration(root, sourceFile, declaration);
|
|
481
|
+
if (declared) {
|
|
482
|
+
declarations.set(declared.variableName, declared);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return declarations;
|
|
486
|
+
}
|
|
487
|
+
function findSignalRegistrations(root, workflowName, functionDeclaration, declaredSignals) {
|
|
488
|
+
const registrations = [];
|
|
489
|
+
const sourceFile = functionDeclaration.getSourceFile();
|
|
490
|
+
for (const call of functionDeclaration.getDescendantsOfKind(SyntaxKind3.CallExpression)) {
|
|
491
|
+
if (!isWorkflowModuleCall(call, "setHandler")) {
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
const [handleArgument] = call.getArguments();
|
|
495
|
+
if (!handleArgument || !Node3.isIdentifier(handleArgument)) {
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
const declared = declaredSignals.get(handleArgument.getText());
|
|
499
|
+
if (!declared) {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
registrations.push({
|
|
503
|
+
id: `signal:${workflowName}:${declared.name}`,
|
|
504
|
+
name: declared.name,
|
|
505
|
+
source: declared.source,
|
|
506
|
+
args: declared.args,
|
|
507
|
+
handlerSource: createSourceLocation(root, sourceFile, call, declared.name),
|
|
508
|
+
confidence: declared.confidence
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
return registrations.toSorted((left, right) => left.name.localeCompare(right.name));
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// packages/analyzer/src/determinism-diagnostics.ts
|
|
515
|
+
var unsafeBareModules = new Set([
|
|
516
|
+
"fs",
|
|
517
|
+
"path",
|
|
518
|
+
"os",
|
|
519
|
+
"http",
|
|
520
|
+
"https",
|
|
521
|
+
"net",
|
|
522
|
+
"dns",
|
|
523
|
+
"crypto",
|
|
524
|
+
"child_process",
|
|
525
|
+
"worker_threads",
|
|
526
|
+
"cluster"
|
|
527
|
+
]);
|
|
528
|
+
var nondeterministicCalls = new Map([
|
|
529
|
+
["Date.now", "Date.now()"],
|
|
530
|
+
["Math.random", "Math.random()"]
|
|
531
|
+
]);
|
|
532
|
+
function createDeterminismDiagnostic(root, code, message, node, severity = "error") {
|
|
533
|
+
return {
|
|
534
|
+
code,
|
|
535
|
+
category: "determinism",
|
|
536
|
+
severity,
|
|
537
|
+
message,
|
|
538
|
+
source: createSourceLocation(root, node.getSourceFile(), node),
|
|
539
|
+
confidence: "exact"
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function findUnsafeImportDiagnostics(root, sourceFile) {
|
|
543
|
+
const diagnostics = [];
|
|
544
|
+
for (const importDeclaration of sourceFile.getImportDeclarations()) {
|
|
545
|
+
const specifier = importDeclaration.getModuleSpecifierValue();
|
|
546
|
+
if (specifier.startsWith("node:") || unsafeBareModules.has(specifier)) {
|
|
547
|
+
diagnostics.push(createDeterminismDiagnostic(root, "TEA_UNSAFE_WORKFLOW_IMPORT", `Workflow file imports ${specifier}, which is not available in the deterministic Workflow sandbox.`, importDeclaration));
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return diagnostics;
|
|
551
|
+
}
|
|
552
|
+
function findNondeterministicApiDiagnostics(root, functionDeclaration) {
|
|
553
|
+
const diagnostics = [];
|
|
554
|
+
for (const call of functionDeclaration.getDescendantsOfKind(SyntaxKind4.CallExpression)) {
|
|
555
|
+
const expressionText = call.getExpression().getText();
|
|
556
|
+
const label = nondeterministicCalls.get(expressionText);
|
|
557
|
+
if (label) {
|
|
558
|
+
diagnostics.push(createDeterminismDiagnostic(root, "TEA_NONDETERMINISTIC_API", `Potential nondeterministic API inside Workflow: ${label}`, call));
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
for (const newExpression of functionDeclaration.getDescendantsOfKind(SyntaxKind4.NewExpression)) {
|
|
562
|
+
if (newExpression.getExpression().getText() === "Date" && newExpression.getArguments().length === 0) {
|
|
563
|
+
diagnostics.push(createDeterminismDiagnostic(root, "TEA_NONDETERMINISTIC_API", "Potential nondeterministic API inside Workflow: new Date()", newExpression));
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return diagnostics;
|
|
567
|
+
}
|
|
568
|
+
function findDuplicateMessageDiagnostics(root, sourceFile) {
|
|
569
|
+
const diagnostics = [];
|
|
570
|
+
const byName = new Map;
|
|
571
|
+
const declarations = [
|
|
572
|
+
...[...findSignalDeclarations(root, sourceFile).values()].map((declared) => ({
|
|
573
|
+
name: declared.name,
|
|
574
|
+
kind: "Signal",
|
|
575
|
+
source: declared.source
|
|
576
|
+
})),
|
|
577
|
+
...[...findMessageDeclarations(root, sourceFile).values()].map((declared) => ({
|
|
578
|
+
name: declared.name,
|
|
579
|
+
kind: declared.kind === "query" ? "Query" : "Update",
|
|
580
|
+
source: declared.source
|
|
581
|
+
}))
|
|
582
|
+
];
|
|
583
|
+
for (const declared of declarations) {
|
|
584
|
+
byName.set(declared.name, (byName.get(declared.name) ?? 0) + 1);
|
|
585
|
+
}
|
|
586
|
+
for (const declared of declarations) {
|
|
587
|
+
if ((byName.get(declared.name) ?? 0) > 1) {
|
|
588
|
+
diagnostics.push({
|
|
589
|
+
code: "TEA_DUPLICATE_MESSAGE_NAME",
|
|
590
|
+
category: "discovery",
|
|
591
|
+
severity: "error",
|
|
592
|
+
message: `${declared.kind} name "${declared.name}" is defined more than once in this file.`,
|
|
593
|
+
source: declared.source,
|
|
594
|
+
confidence: "exact"
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return diagnostics;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// packages/analyzer/src/handler-diagnostics.ts
|
|
602
|
+
import { Node as Node5, SyntaxKind as SyntaxKind5 } from "ts-morph";
|
|
603
|
+
var commandApiNames = [
|
|
604
|
+
"sleep",
|
|
605
|
+
"condition",
|
|
606
|
+
"startChild",
|
|
607
|
+
"executeChild",
|
|
608
|
+
"continueAsNew",
|
|
609
|
+
"getExternalWorkflowHandle"
|
|
610
|
+
];
|
|
611
|
+
var assignmentOperators = new Set([
|
|
612
|
+
SyntaxKind5.EqualsToken,
|
|
613
|
+
SyntaxKind5.PlusEqualsToken,
|
|
614
|
+
SyntaxKind5.MinusEqualsToken,
|
|
615
|
+
SyntaxKind5.AsteriskEqualsToken,
|
|
616
|
+
SyntaxKind5.SlashEqualsToken,
|
|
617
|
+
SyntaxKind5.PercentEqualsToken,
|
|
618
|
+
SyntaxKind5.AmpersandAmpersandEqualsToken,
|
|
619
|
+
SyntaxKind5.BarBarEqualsToken,
|
|
620
|
+
SyntaxKind5.QuestionQuestionEqualsToken
|
|
621
|
+
]);
|
|
622
|
+
function isFunctionLike(node) {
|
|
623
|
+
return Node5.isArrowFunction(node) || Node5.isFunctionExpression(node) || Node5.isFunctionDeclaration(node);
|
|
624
|
+
}
|
|
625
|
+
function isDeclaredInside(identifier, container) {
|
|
626
|
+
const symbol = identifier.getSymbol();
|
|
627
|
+
return symbol?.getDeclarations().some((declaration) => declaration.getSourceFile() === container.getSourceFile() && declaration.getStart() >= container.getStart() && declaration.getEnd() <= container.getEnd()) ?? false;
|
|
628
|
+
}
|
|
629
|
+
function createDiagnostic(root, code, message, node) {
|
|
630
|
+
return {
|
|
631
|
+
code,
|
|
632
|
+
category: "determinism",
|
|
633
|
+
severity: "error",
|
|
634
|
+
message,
|
|
635
|
+
source: createSourceLocation(root, node.getSourceFile(), node),
|
|
636
|
+
confidence: "exact"
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
function getAssignmentTarget(target) {
|
|
640
|
+
if (Node5.isIdentifier(target)) {
|
|
641
|
+
return target;
|
|
642
|
+
}
|
|
643
|
+
return Node5.isPropertyAccessExpression(target) ? target.getExpression() : undefined;
|
|
644
|
+
}
|
|
645
|
+
function isOuterStateMutation(target, handler) {
|
|
646
|
+
return Boolean(target && Node5.isIdentifier(target) && !isDeclaredInside(target, handler));
|
|
647
|
+
}
|
|
648
|
+
function createMutationDiagnostic(root, queryName, expression) {
|
|
649
|
+
return createDiagnostic(root, "TEA_QUERY_STATE_MUTATION", `Query handler ${queryName} mutates Workflow state: ${expression.getText()}`, expression);
|
|
650
|
+
}
|
|
651
|
+
function findAssignmentMutations(root, queryName, handler, diagnostics) {
|
|
652
|
+
for (const binary of handler.getDescendantsOfKind(SyntaxKind5.BinaryExpression)) {
|
|
653
|
+
if (!assignmentOperators.has(binary.getOperatorToken().getKind())) {
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
if (isOuterStateMutation(getAssignmentTarget(binary.getLeft()), handler)) {
|
|
657
|
+
diagnostics.push(createMutationDiagnostic(root, queryName, binary));
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
function findIncrementMutations(root, queryName, handler, diagnostics) {
|
|
662
|
+
const unaryExpressions = [
|
|
663
|
+
...handler.getDescendantsOfKind(SyntaxKind5.PostfixUnaryExpression),
|
|
664
|
+
...handler.getDescendantsOfKind(SyntaxKind5.PrefixUnaryExpression)
|
|
665
|
+
];
|
|
666
|
+
for (const unary of unaryExpressions) {
|
|
667
|
+
const operator = unary.getOperatorToken();
|
|
668
|
+
if (operator !== SyntaxKind5.PlusPlusToken && operator !== SyntaxKind5.MinusMinusToken) {
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
if (isOuterStateMutation(unary.getOperand(), handler)) {
|
|
672
|
+
diagnostics.push(createMutationDiagnostic(root, queryName, unary));
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function findMutationDiagnostics(root, queryName, handler, diagnostics) {
|
|
677
|
+
findAssignmentMutations(root, queryName, handler, diagnostics);
|
|
678
|
+
findIncrementMutations(root, queryName, handler, diagnostics);
|
|
679
|
+
}
|
|
680
|
+
function findCommandDiagnostics(root, queryName, handler, proxyVariables, diagnostics) {
|
|
681
|
+
for (const call of handler.getDescendantsOfKind(SyntaxKind5.CallExpression)) {
|
|
682
|
+
const expression = call.getExpression();
|
|
683
|
+
if (Node5.isPropertyAccessExpression(expression)) {
|
|
684
|
+
const receiver = expression.getExpression();
|
|
685
|
+
if (Node5.isIdentifier(receiver) && proxyVariables.has(receiver.getText())) {
|
|
686
|
+
diagnostics.push(createDiagnostic(root, "TEA_QUERY_COMMAND_IN_HANDLER", `Query handler ${queryName} calls Activity ${expression.getName()}.`, call));
|
|
687
|
+
}
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
for (const apiName of commandApiNames) {
|
|
691
|
+
if (isWorkflowModuleCall(call, apiName)) {
|
|
692
|
+
diagnostics.push(createDiagnostic(root, "TEA_QUERY_COMMAND_IN_HANDLER", `Query handler ${queryName} uses ${apiName}, which is not allowed in Queries.`, call));
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
function analyzeQueryHandler(root, queryName, handler, proxyVariables) {
|
|
698
|
+
if (!handler || !isFunctionLike(handler)) {
|
|
699
|
+
return [];
|
|
700
|
+
}
|
|
701
|
+
const diagnostics = [];
|
|
702
|
+
if ((Node5.isArrowFunction(handler) || Node5.isFunctionExpression(handler)) && handler.isAsync()) {
|
|
703
|
+
diagnostics.push(createDiagnostic(root, "TEA_QUERY_ASYNC_HANDLER", `Query handler ${queryName} may not be async.`, handler));
|
|
704
|
+
}
|
|
705
|
+
findMutationDiagnostics(root, queryName, handler, diagnostics);
|
|
706
|
+
findCommandDiagnostics(root, queryName, handler, proxyVariables, diagnostics);
|
|
707
|
+
return diagnostics;
|
|
708
|
+
}
|
|
709
|
+
function analyzeUpdateValidator(root, updateName, validator) {
|
|
710
|
+
if (!validator) {
|
|
711
|
+
return [];
|
|
712
|
+
}
|
|
713
|
+
if ((Node5.isArrowFunction(validator) || Node5.isFunctionExpression(validator)) && validator.isAsync()) {
|
|
714
|
+
return [
|
|
715
|
+
createDiagnostic(root, "TEA_UPDATE_ASYNC_VALIDATOR", `Update validator for ${updateName} may not be async.`, validator)
|
|
716
|
+
];
|
|
717
|
+
}
|
|
718
|
+
return [];
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// packages/analyzer/src/type-imports.ts
|
|
722
|
+
function toModulePath(root, filePath) {
|
|
723
|
+
return toProjectPath(root, filePath).replace(/\.tsx?$/u, "");
|
|
724
|
+
}
|
|
725
|
+
function collectNamedTypeSources(root, sourceFile) {
|
|
726
|
+
const sources = [];
|
|
727
|
+
for (const importDeclaration of sourceFile.getImportDeclarations()) {
|
|
728
|
+
const moduleSourceFile = importDeclaration.getModuleSpecifierSourceFile();
|
|
729
|
+
if (!moduleSourceFile || moduleSourceFile.isInNodeModules()) {
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
const module = toModulePath(root, moduleSourceFile.getFilePath());
|
|
733
|
+
for (const namedImport of importDeclaration.getNamedImports()) {
|
|
734
|
+
sources.push({ name: namedImport.getName(), module });
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
const ownModule = toModulePath(root, sourceFile.getFilePath());
|
|
738
|
+
for (const typeAlias of sourceFile.getTypeAliases()) {
|
|
739
|
+
if (typeAlias.isExported()) {
|
|
740
|
+
sources.push({ name: typeAlias.getName(), module: ownModule });
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
for (const interfaceDeclaration of sourceFile.getInterfaces()) {
|
|
744
|
+
if (interfaceDeclaration.isExported()) {
|
|
745
|
+
sources.push({ name: interfaceDeclaration.getName(), module: ownModule });
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return sources;
|
|
749
|
+
}
|
|
750
|
+
function collectDisplayTexts(workflow, signals, queries, updates) {
|
|
751
|
+
const texts = [
|
|
752
|
+
...workflow.signature.args.map((arg) => arg.display),
|
|
753
|
+
workflow.signature.result.display
|
|
754
|
+
];
|
|
755
|
+
for (const message of [...signals, ...queries, ...updates]) {
|
|
756
|
+
texts.push(...message.args.map((arg) => arg.display));
|
|
757
|
+
}
|
|
758
|
+
for (const message of [...queries, ...updates]) {
|
|
759
|
+
if (message.result) {
|
|
760
|
+
texts.push(message.result.display);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return texts;
|
|
764
|
+
}
|
|
765
|
+
function collectTypeImportDependencies(root, sourceFile, workflow, signals, queries, updates) {
|
|
766
|
+
const displayTexts = collectDisplayTexts(workflow, signals, queries, updates);
|
|
767
|
+
const dependencies = new Map;
|
|
768
|
+
for (const candidate of collectNamedTypeSources(root, sourceFile)) {
|
|
769
|
+
const pattern = new RegExp(`\\b${candidate.name}\\b`, "u");
|
|
770
|
+
if (displayTexts.some((text) => pattern.test(text))) {
|
|
771
|
+
dependencies.set(candidate.name, {
|
|
772
|
+
kind: "type-import",
|
|
773
|
+
name: candidate.name,
|
|
774
|
+
module: candidate.module
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return [...dependencies.values()].toSorted((left, right) => left.name.localeCompare(right.name));
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// packages/analyzer/src/workflow-commands.ts
|
|
782
|
+
import {
|
|
783
|
+
Node as Node7,
|
|
784
|
+
SyntaxKind as SyntaxKind6
|
|
785
|
+
} from "ts-morph";
|
|
786
|
+
|
|
787
|
+
// packages/analyzer/src/command-factories.ts
|
|
788
|
+
import { Node as Node6 } from "ts-morph";
|
|
789
|
+
function commandSource(root, call, symbolName) {
|
|
790
|
+
return createSourceLocation(root, call.getSourceFile(), call, symbolName);
|
|
791
|
+
}
|
|
792
|
+
function createActivityCommand(root, workflowName, activityName, call, order) {
|
|
793
|
+
return {
|
|
794
|
+
id: `activity-call:${workflowName}:${activityName}:${order}`,
|
|
795
|
+
kind: "activity",
|
|
796
|
+
name: activityName,
|
|
797
|
+
source: commandSource(root, call, activityName),
|
|
798
|
+
confidence: "exact",
|
|
799
|
+
staticOrder: order
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
function createConditionCommands(root, workflowName, call, startOrder) {
|
|
803
|
+
const [predicate, timeout] = call.getArguments();
|
|
804
|
+
const commands = [
|
|
805
|
+
{
|
|
806
|
+
id: `condition:${workflowName}:${startOrder}`,
|
|
807
|
+
kind: "condition",
|
|
808
|
+
name: predicate?.getText() ?? "condition",
|
|
809
|
+
source: commandSource(root, call),
|
|
810
|
+
confidence: "exact",
|
|
811
|
+
staticOrder: startOrder
|
|
812
|
+
}
|
|
813
|
+
];
|
|
814
|
+
if (timeout) {
|
|
815
|
+
commands.push({
|
|
816
|
+
id: `timer:${workflowName}:${startOrder + 1}`,
|
|
817
|
+
kind: "timer",
|
|
818
|
+
name: timeout.getText(),
|
|
819
|
+
source: commandSource(root, call),
|
|
820
|
+
confidence: "exact",
|
|
821
|
+
staticOrder: startOrder + 1
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
return commands;
|
|
825
|
+
}
|
|
826
|
+
function createSleepCommand(root, workflowName, call, order) {
|
|
827
|
+
return {
|
|
828
|
+
id: `timer:${workflowName}:${order}`,
|
|
829
|
+
kind: "timer",
|
|
830
|
+
name: call.getArguments()[0]?.getText() ?? "sleep",
|
|
831
|
+
source: commandSource(root, call),
|
|
832
|
+
confidence: "exact",
|
|
833
|
+
staticOrder: order
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
function resolveChildWorkflowTarget(target) {
|
|
837
|
+
if (target && Node6.isStringLiteral(target)) {
|
|
838
|
+
return { name: target.getLiteralValue(), confidence: "exact" };
|
|
839
|
+
}
|
|
840
|
+
if (target && Node6.isIdentifier(target)) {
|
|
841
|
+
return { name: target.getText(), confidence: "exact" };
|
|
842
|
+
}
|
|
843
|
+
return { name: target?.getText() || "unknownChildWorkflow", confidence: "dynamic" };
|
|
844
|
+
}
|
|
845
|
+
function createChildWorkflowCommand(root, workflowName, call, order) {
|
|
846
|
+
const { name, confidence } = resolveChildWorkflowTarget(call.getArguments()[0]);
|
|
847
|
+
return {
|
|
848
|
+
id: `child-workflow:${workflowName}:${name}:${order}`,
|
|
849
|
+
kind: "child-workflow",
|
|
850
|
+
name,
|
|
851
|
+
source: commandSource(root, call, name),
|
|
852
|
+
confidence,
|
|
853
|
+
staticOrder: order
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
function createExternalSignalCommand(root, workflowName, signalName, confidence, call, order) {
|
|
857
|
+
return {
|
|
858
|
+
id: `external-workflow:${workflowName}:${signalName}:${order}`,
|
|
859
|
+
kind: "external-workflow",
|
|
860
|
+
name: signalName,
|
|
861
|
+
source: commandSource(root, call, signalName),
|
|
862
|
+
confidence,
|
|
863
|
+
staticOrder: order
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
function createContinueAsNewCommand(root, workflowName, call, order) {
|
|
867
|
+
const typeArgument = call.getTypeArguments()[0]?.getText();
|
|
868
|
+
const target = typeArgument?.startsWith("typeof ") ? typeArgument.slice("typeof ".length) : typeArgument;
|
|
869
|
+
return {
|
|
870
|
+
id: `continue-as-new:${workflowName}:${order}`,
|
|
871
|
+
kind: "continue-as-new",
|
|
872
|
+
name: target ?? "continueAsNew",
|
|
873
|
+
source: commandSource(root, call),
|
|
874
|
+
confidence: "exact",
|
|
875
|
+
staticOrder: order
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
function createPatchCommand(root, workflowName, call, order) {
|
|
879
|
+
const [patchArgument] = call.getArguments();
|
|
880
|
+
const literalId = patchArgument && Node6.isStringLiteral(patchArgument) ? patchArgument.getLiteralValue() : undefined;
|
|
881
|
+
return {
|
|
882
|
+
id: `patch:${workflowName}:${order}`,
|
|
883
|
+
kind: "patch",
|
|
884
|
+
name: literalId ?? (patchArgument?.getText() || "unknownPatch"),
|
|
885
|
+
source: commandSource(root, call),
|
|
886
|
+
confidence: literalId ? "exact" : "dynamic",
|
|
887
|
+
staticOrder: order
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
function createCancellationScopeCommand(root, workflowName, scopeKind, call, order) {
|
|
891
|
+
return {
|
|
892
|
+
id: `cancellation-scope:${workflowName}:${order}`,
|
|
893
|
+
kind: "cancellation-scope",
|
|
894
|
+
name: scopeKind,
|
|
895
|
+
source: commandSource(root, call),
|
|
896
|
+
confidence: "exact",
|
|
897
|
+
staticOrder: order
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
function createDynamicCommand(root, workflowName, expressionText, call, order) {
|
|
901
|
+
return {
|
|
902
|
+
id: `dynamic:${workflowName}:${order}`,
|
|
903
|
+
kind: "dynamic",
|
|
904
|
+
name: expressionText,
|
|
905
|
+
source: commandSource(root, call),
|
|
906
|
+
confidence: "dynamic",
|
|
907
|
+
staticOrder: order
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// packages/analyzer/src/workflow-commands.ts
|
|
912
|
+
function getProxyPropertyAccessExpression(call, proxyVariables) {
|
|
913
|
+
const expression = call.getExpression();
|
|
914
|
+
if (!Node7.isPropertyAccessExpression(expression)) {
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
const receiver = expression.getExpression();
|
|
918
|
+
return Node7.isIdentifier(receiver) && proxyVariables.has(receiver.getText()) ? expression : undefined;
|
|
919
|
+
}
|
|
920
|
+
function getDynamicProxyAccess(call, proxyVariables) {
|
|
921
|
+
const expression = call.getExpression();
|
|
922
|
+
if (!Node7.isElementAccessExpression(expression)) {
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
const receiver = expression.getExpression();
|
|
926
|
+
return Node7.isIdentifier(receiver) && proxyVariables.has(receiver.getText()) ? expression : undefined;
|
|
927
|
+
}
|
|
928
|
+
function createDynamicActivityDiagnostic(root, call, expression) {
|
|
929
|
+
return {
|
|
930
|
+
code: "TEA_DYNAMIC_ACTIVITY_CALL",
|
|
931
|
+
category: "control-flow",
|
|
932
|
+
severity: "warning",
|
|
933
|
+
message: `Dynamic Activity call could not be fully resolved: ${expression.getText()}`,
|
|
934
|
+
source: createSourceLocation(root, call.getSourceFile(), call),
|
|
935
|
+
confidence: "dynamic"
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
function findExternalHandleVariables(functionDeclaration) {
|
|
939
|
+
const handles = new Set;
|
|
940
|
+
for (const declaration of functionDeclaration.getDescendantsOfKind(SyntaxKind6.VariableDeclaration)) {
|
|
941
|
+
const initializer = declaration.getInitializer();
|
|
942
|
+
const nameNode = declaration.getNameNode();
|
|
943
|
+
if (initializer && Node7.isCallExpression(initializer) && isWorkflowModuleCall(initializer, "getExternalWorkflowHandle") && Node7.isIdentifier(nameNode)) {
|
|
944
|
+
handles.add(nameNode.getText());
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
return handles;
|
|
948
|
+
}
|
|
949
|
+
function getCancellationScopeKind(call) {
|
|
950
|
+
const expression = call.getExpression();
|
|
951
|
+
if (!Node7.isPropertyAccessExpression(expression)) {
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
const receiver = expression.getExpression();
|
|
955
|
+
if (!Node7.isIdentifier(receiver) || !isNamedImportFrom(receiver, temporalWorkflowModule, "CancellationScope")) {
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
return expression.getName();
|
|
959
|
+
}
|
|
960
|
+
function isExternalHandleSignalCall(call, externalHandles) {
|
|
961
|
+
const expression = call.getExpression();
|
|
962
|
+
if (!Node7.isPropertyAccessExpression(expression) || expression.getName() !== "signal") {
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
const receiver = expression.getExpression();
|
|
966
|
+
return Node7.isIdentifier(receiver) && externalHandles.has(receiver.getText());
|
|
967
|
+
}
|
|
968
|
+
function resolveSignalArgumentName(signalArgument, signalNamesByVariable) {
|
|
969
|
+
if (signalArgument && Node7.isStringLiteral(signalArgument)) {
|
|
970
|
+
return { name: signalArgument.getLiteralValue(), confidence: "exact" };
|
|
971
|
+
}
|
|
972
|
+
if (signalArgument && Node7.isIdentifier(signalArgument)) {
|
|
973
|
+
const declared = signalNamesByVariable.get(signalArgument.getText());
|
|
974
|
+
if (declared) {
|
|
975
|
+
return { name: declared, confidence: "exact" };
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
return { name: signalArgument?.getText() || "unknownSignal", confidence: "dynamic" };
|
|
979
|
+
}
|
|
980
|
+
function getExternalSignalName(call, externalHandles, signalNamesByVariable) {
|
|
981
|
+
if (!isExternalHandleSignalCall(call, externalHandles)) {
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
return resolveSignalArgumentName(call.getArguments()[0], signalNamesByVariable);
|
|
985
|
+
}
|
|
986
|
+
function getDestructuredActivityName(call, destructuredActivities) {
|
|
987
|
+
const expression = call.getExpression();
|
|
988
|
+
return Node7.isIdentifier(expression) ? destructuredActivities.get(expression.getText()) : undefined;
|
|
989
|
+
}
|
|
990
|
+
function collectCall(call, context) {
|
|
991
|
+
const { root, workflowName, commands } = context;
|
|
992
|
+
const proxyAccess = getProxyPropertyAccessExpression(call, context.proxyVariables);
|
|
993
|
+
if (proxyAccess) {
|
|
994
|
+
commands.push(createActivityCommand(root, workflowName, proxyAccess.getName(), call, commands.length));
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
const destructuredActivity = getDestructuredActivityName(call, context.destructuredActivities);
|
|
998
|
+
if (destructuredActivity) {
|
|
999
|
+
commands.push(createActivityCommand(root, workflowName, destructuredActivity, call, commands.length));
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
const dynamicAccess = getDynamicProxyAccess(call, context.proxyVariables);
|
|
1003
|
+
if (dynamicAccess) {
|
|
1004
|
+
commands.push(createDynamicCommand(root, workflowName, dynamicAccess.getText(), call, commands.length));
|
|
1005
|
+
context.diagnostics.push(createDynamicActivityDiagnostic(root, call, dynamicAccess));
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
const scopeKind = getCancellationScopeKind(call);
|
|
1009
|
+
if (scopeKind) {
|
|
1010
|
+
commands.push(createCancellationScopeCommand(root, workflowName, scopeKind, call, commands.length));
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
const externalSignal = getExternalSignalName(call, context.externalHandles, context.signalNamesByVariable);
|
|
1014
|
+
if (externalSignal) {
|
|
1015
|
+
commands.push(createExternalSignalCommand(root, workflowName, externalSignal.name, externalSignal.confidence, call, commands.length));
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
collectWorkflowModuleCall(call, context);
|
|
1019
|
+
}
|
|
1020
|
+
function collectWorkflowModuleCall(call, context) {
|
|
1021
|
+
const { root, workflowName, commands } = context;
|
|
1022
|
+
if (isWorkflowModuleCall(call, "condition")) {
|
|
1023
|
+
commands.push(...createConditionCommands(root, workflowName, call, commands.length));
|
|
1024
|
+
} else if (isWorkflowModuleCall(call, "sleep")) {
|
|
1025
|
+
commands.push(createSleepCommand(root, workflowName, call, commands.length));
|
|
1026
|
+
} else if (isWorkflowModuleCall(call, "startChild") || isWorkflowModuleCall(call, "executeChild")) {
|
|
1027
|
+
commands.push(createChildWorkflowCommand(root, workflowName, call, commands.length));
|
|
1028
|
+
} else if (isWorkflowModuleCall(call, "continueAsNew")) {
|
|
1029
|
+
commands.push(createContinueAsNewCommand(root, workflowName, call, commands.length));
|
|
1030
|
+
} else if (isWorkflowModuleCall(call, "patched") || isWorkflowModuleCall(call, "deprecatePatch")) {
|
|
1031
|
+
commands.push(createPatchCommand(root, workflowName, call, commands.length));
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
function collectTemporalCommands(root, workflowName, functionDeclaration, proxyVariables) {
|
|
1035
|
+
const signalNamesByVariable = new Map;
|
|
1036
|
+
for (const [variableName, declared] of findSignalDeclarations(root, functionDeclaration.getSourceFile())) {
|
|
1037
|
+
signalNamesByVariable.set(variableName, declared.name);
|
|
1038
|
+
}
|
|
1039
|
+
const context = {
|
|
1040
|
+
root,
|
|
1041
|
+
workflowName,
|
|
1042
|
+
proxyVariables,
|
|
1043
|
+
destructuredActivities: findDestructuredActivityBindings(functionDeclaration.getSourceFile(), proxyVariables),
|
|
1044
|
+
externalHandles: findExternalHandleVariables(functionDeclaration),
|
|
1045
|
+
signalNamesByVariable,
|
|
1046
|
+
commands: [],
|
|
1047
|
+
diagnostics: []
|
|
1048
|
+
};
|
|
1049
|
+
for (const call of functionDeclaration.getDescendantsOfKind(SyntaxKind6.CallExpression)) {
|
|
1050
|
+
collectCall(call, context);
|
|
1051
|
+
}
|
|
1052
|
+
return { commands: context.commands, diagnostics: context.diagnostics };
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// packages/analyzer/src/workflow-analysis.ts
|
|
1056
|
+
function findNamespaceImportSource(sourceFile) {
|
|
1057
|
+
for (const importDeclaration of sourceFile.getImportDeclarations()) {
|
|
1058
|
+
if (!importDeclaration.getNamespaceImport()) {
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
const moduleSourceFile = importDeclaration.getModuleSpecifierSourceFile();
|
|
1062
|
+
if (moduleSourceFile)
|
|
1063
|
+
return moduleSourceFile;
|
|
1064
|
+
}
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
function findActivityImplementationSource(activitySourceFile, activityName, root) {
|
|
1068
|
+
const declaration = activitySourceFile?.getFunction(activityName);
|
|
1069
|
+
if (!declaration || !activitySourceFile) {
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
return createSourceLocation(root, activitySourceFile, declaration, activityName);
|
|
1073
|
+
}
|
|
1074
|
+
function getFunctionReturnDisplay(functionDeclaration) {
|
|
1075
|
+
return functionDeclaration.getReturnTypeNode()?.getText() ?? functionDeclaration.getReturnType().getText(functionDeclaration);
|
|
1076
|
+
}
|
|
1077
|
+
function createWorkflowSignature(root, functionDeclaration) {
|
|
1078
|
+
const sourceFile = functionDeclaration.getSourceFile();
|
|
1079
|
+
const args = functionDeclaration.getParameters().map((parameter, index) => {
|
|
1080
|
+
const name = parameter.getName();
|
|
1081
|
+
const display = parameter.getTypeNode()?.getText() ?? parameter.getType().getText(parameter);
|
|
1082
|
+
return createTypeShape(`${functionDeclaration.getName() ?? "anonymous"}:arg:${index}:${name}`, display, createSourceLocation(root, sourceFile, parameter, name), name);
|
|
1083
|
+
});
|
|
1084
|
+
return {
|
|
1085
|
+
args,
|
|
1086
|
+
result: createTypeShape(`${functionDeclaration.getName() ?? "anonymous"}:result`, getFunctionReturnDisplay(functionDeclaration))
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
function analyzeMessageHandlers(root, registrations, proxyVariables) {
|
|
1090
|
+
return registrations.flatMap((registration) => registration.declared.kind === "query" ? analyzeQueryHandler(root, registration.declared.name, registration.handler, proxyVariables) : analyzeUpdateValidator(root, registration.declared.name, registration.validator));
|
|
1091
|
+
}
|
|
1092
|
+
function analyzeWorkflowFunction(root, functionDeclaration, proxyVariables) {
|
|
1093
|
+
const workflowName = functionDeclaration.getName() ?? "anonymousWorkflow";
|
|
1094
|
+
const collected = collectTemporalCommands(root, workflowName, functionDeclaration, proxyVariables);
|
|
1095
|
+
const signals = findSignalRegistrations(root, workflowName, functionDeclaration, findSignalDeclarations(root, functionDeclaration.getSourceFile()));
|
|
1096
|
+
const registrations = findMessageRegistrations(functionDeclaration, findMessageDeclarations(root, functionDeclaration.getSourceFile()));
|
|
1097
|
+
const queries = registrations.filter((registration) => registration.declared.kind === "query").map((registration) => createQueryDefinition(root, workflowName, registration)).toSorted((left, right) => left.name.localeCompare(right.name));
|
|
1098
|
+
const updates = registrations.filter((registration) => registration.declared.kind === "update").map((registration) => createUpdateDefinition(root, workflowName, registration)).toSorted((left, right) => left.name.localeCompare(right.name));
|
|
1099
|
+
const diagnostics = [
|
|
1100
|
+
...collected.diagnostics,
|
|
1101
|
+
...analyzeMessageHandlers(root, registrations, proxyVariables),
|
|
1102
|
+
...findNondeterministicApiDiagnostics(root, functionDeclaration)
|
|
1103
|
+
];
|
|
1104
|
+
const signature = createWorkflowSignature(root, functionDeclaration);
|
|
1105
|
+
return {
|
|
1106
|
+
id: `workflow:${workflowName}`,
|
|
1107
|
+
name: workflowName,
|
|
1108
|
+
source: createSourceLocation(root, functionDeclaration.getSourceFile(), functionDeclaration, workflowName),
|
|
1109
|
+
exported: functionDeclaration.isExported(),
|
|
1110
|
+
signature,
|
|
1111
|
+
messageSurface: {
|
|
1112
|
+
signals,
|
|
1113
|
+
queries,
|
|
1114
|
+
updates
|
|
1115
|
+
},
|
|
1116
|
+
state: {
|
|
1117
|
+
variables: []
|
|
1118
|
+
},
|
|
1119
|
+
body: {
|
|
1120
|
+
nodes: []
|
|
1121
|
+
},
|
|
1122
|
+
temporalCommands: collected.commands,
|
|
1123
|
+
dependencies: collectTypeImportDependencies(root, functionDeclaration.getSourceFile(), { signature }, signals, queries, updates),
|
|
1124
|
+
diagnostics
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
function analyzeActivities(root, workflow, activitySourceFile, diagnostics) {
|
|
1128
|
+
return workflow.temporalCommands.filter((command) => command.kind === "activity").map((command) => {
|
|
1129
|
+
const implementationSource = findActivityImplementationSource(activitySourceFile, command.name, root);
|
|
1130
|
+
if (!implementationSource) {
|
|
1131
|
+
diagnostics.push({
|
|
1132
|
+
code: "TEA_UNRESOLVED_ACTIVITY_IMPLEMENTATION",
|
|
1133
|
+
category: "discovery",
|
|
1134
|
+
severity: "warning",
|
|
1135
|
+
message: `Activity implementation for ${command.name} could not be resolved statically.`,
|
|
1136
|
+
source: command.source,
|
|
1137
|
+
confidence: "inferred"
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
return {
|
|
1141
|
+
id: `activity:${command.name}`,
|
|
1142
|
+
name: command.name,
|
|
1143
|
+
source: command.source,
|
|
1144
|
+
...implementationSource ? { implementationSource } : {},
|
|
1145
|
+
confidence: implementationSource ? "exact" : "inferred"
|
|
1146
|
+
};
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
function analyzeWorkerFiles(project, root) {
|
|
1150
|
+
const workers = [];
|
|
1151
|
+
for (const sourceFile of project.getSourceFiles()) {
|
|
1152
|
+
if (!sourceFile.getFilePath().includes("/worker")) {
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
for (const call of sourceFile.getDescendantsOfKind(SyntaxKind7.CallExpression)) {
|
|
1156
|
+
const expression = call.getExpression();
|
|
1157
|
+
const receiver = Node8.isPropertyAccessExpression(expression) ? expression.getExpression() : undefined;
|
|
1158
|
+
if (!Node8.isPropertyAccessExpression(expression) || expression.getName() !== "create" || !Node8.isIdentifier(receiver) || !isNamedImportFrom(receiver, temporalWorkerModule, "Worker")) {
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
1161
|
+
workers.push({
|
|
1162
|
+
id: `worker:${workers.length}`,
|
|
1163
|
+
source: createSourceLocation(root, sourceFile, call, "Worker.create"),
|
|
1164
|
+
confidence: "inferred"
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
return workers;
|
|
1169
|
+
}
|
|
1170
|
+
function analyzeWorkflowSourceFile(root, sourceFile) {
|
|
1171
|
+
const workflows = [];
|
|
1172
|
+
const activities = [];
|
|
1173
|
+
const diagnostics = [
|
|
1174
|
+
...findUnsafeImportDiagnostics(root, sourceFile),
|
|
1175
|
+
...findDuplicateMessageDiagnostics(root, sourceFile)
|
|
1176
|
+
];
|
|
1177
|
+
const proxyVariables = findActivityProxyVariables(sourceFile);
|
|
1178
|
+
const activitySourceFile = findNamespaceImportSource(sourceFile);
|
|
1179
|
+
for (const functionDeclaration of sourceFile.getFunctions()) {
|
|
1180
|
+
if (!functionDeclaration.isExported()) {
|
|
1181
|
+
continue;
|
|
1182
|
+
}
|
|
1183
|
+
const workflow = analyzeWorkflowFunction(root, functionDeclaration, proxyVariables);
|
|
1184
|
+
workflows.push(workflow);
|
|
1185
|
+
activities.push(...analyzeActivities(root, workflow, activitySourceFile, diagnostics));
|
|
1186
|
+
diagnostics.push(...workflow.diagnostics);
|
|
1187
|
+
}
|
|
1188
|
+
return { workflows, activities, diagnostics };
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// packages/analyzer/src/index.ts
|
|
1192
|
+
var workflowGlobs = ["src/**/workflows/**/*.ts", "src/**/*.workflow.ts", "src/**/workflows.ts"];
|
|
1193
|
+
var workerGlobs = ["src/**/worker*.ts", "src/**/workers/**/*.ts"];
|
|
1194
|
+
async function resolveWorkflowFiles(root, options, configuration) {
|
|
1195
|
+
if (options.workflowFiles) {
|
|
1196
|
+
return options.workflowFiles.map((file) => resolve3(root, file));
|
|
1197
|
+
}
|
|
1198
|
+
return await discoverFiles(root, configuration?.temporal?.workflowGlobs ?? workflowGlobs);
|
|
1199
|
+
}
|
|
1200
|
+
function resolveProjectPaths(options, configuration) {
|
|
1201
|
+
return {
|
|
1202
|
+
tsconfigName: options.tsconfig ?? configuration?.tsconfig ?? "tsconfig.json",
|
|
1203
|
+
outputName: options.outputDirectory ?? configuration?.output?.directory ?? ".temporal-explorer"
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
async function loadTemporalExplorerProject(options = {}) {
|
|
1207
|
+
const root = resolve3(options.root ?? process.cwd());
|
|
1208
|
+
const configuration = await loadConfiguration(resolve3(root, "temporal-explorer.config.ts"));
|
|
1209
|
+
const { tsconfigName, outputName } = resolveProjectPaths(options, configuration);
|
|
1210
|
+
return {
|
|
1211
|
+
root,
|
|
1212
|
+
tsconfig: resolve3(root, tsconfigName),
|
|
1213
|
+
workflowFiles: await resolveWorkflowFiles(root, options, configuration),
|
|
1214
|
+
outputDirectory: resolve3(root, outputName),
|
|
1215
|
+
...configuration ? { configuration } : {}
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
async function createProjectWithSources(root, tsconfig, workflowFiles) {
|
|
1219
|
+
const project = new Project({ tsConfigFilePath: tsconfig });
|
|
1220
|
+
for (const workflowFile of workflowFiles) {
|
|
1221
|
+
project.addSourceFileAtPathIfExists(workflowFile);
|
|
1222
|
+
}
|
|
1223
|
+
for (const workerFile of await discoverFiles(root, workerGlobs)) {
|
|
1224
|
+
project.addSourceFileAtPathIfExists(workerFile);
|
|
1225
|
+
}
|
|
1226
|
+
return project;
|
|
1227
|
+
}
|
|
1228
|
+
function resolveDeclaringSourceFiles(sourceFile) {
|
|
1229
|
+
const files = new Map([[sourceFile.getFilePath(), sourceFile]]);
|
|
1230
|
+
for (const exportDeclaration of sourceFile.getExportDeclarations()) {
|
|
1231
|
+
const target = exportDeclaration.getModuleSpecifierSourceFile();
|
|
1232
|
+
if (target) {
|
|
1233
|
+
files.set(target.getFilePath(), target);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
return [...files.values()];
|
|
1237
|
+
}
|
|
1238
|
+
function collectWorkflowAnalysis(project, root, workflowFiles) {
|
|
1239
|
+
const workflows = [];
|
|
1240
|
+
const activities = [];
|
|
1241
|
+
const diagnostics = [];
|
|
1242
|
+
const analyzedPaths = new Set;
|
|
1243
|
+
for (const workflowFile of workflowFiles) {
|
|
1244
|
+
for (const sourceFile of resolveDeclaringSourceFiles(project.getSourceFileOrThrow(workflowFile))) {
|
|
1245
|
+
if (analyzedPaths.has(sourceFile.getFilePath())) {
|
|
1246
|
+
continue;
|
|
1247
|
+
}
|
|
1248
|
+
analyzedPaths.add(sourceFile.getFilePath());
|
|
1249
|
+
const analysis = analyzeWorkflowSourceFile(root, sourceFile);
|
|
1250
|
+
workflows.push(...analysis.workflows);
|
|
1251
|
+
activities.push(...analysis.activities);
|
|
1252
|
+
diagnostics.push(...analysis.diagnostics);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
return { workflows, activities, diagnostics };
|
|
1256
|
+
}
|
|
1257
|
+
async function analyzeWorkflowFiles(options) {
|
|
1258
|
+
const root = resolve3(options.projectRoot);
|
|
1259
|
+
const workflowFiles = options.workflowFiles.map((file) => resolve3(root, file));
|
|
1260
|
+
const tsconfig = resolve3(root, options.tsconfig);
|
|
1261
|
+
const project = await createProjectWithSources(root, tsconfig, workflowFiles);
|
|
1262
|
+
const collected = collectWorkflowAnalysis(project, root, workflowFiles);
|
|
1263
|
+
const packageJson = await readPackageJson(root);
|
|
1264
|
+
const sourceFileHashes = await createSourceFileHashes(root, workflowFiles);
|
|
1265
|
+
const configHash = await hashFile(tsconfig);
|
|
1266
|
+
const temporalTypeScriptVersion = packageJson.dependencies?.["@temporalio/workflow"];
|
|
1267
|
+
const packageManager = getPackageManager(packageJson);
|
|
1268
|
+
const projectName = basename(root);
|
|
1269
|
+
return {
|
|
1270
|
+
schemaVersion: "temporal-analysis/v1",
|
|
1271
|
+
artifactId: `analysis:${projectName}`,
|
|
1272
|
+
metadata: {
|
|
1273
|
+
temporalExplorerVersion: "0.0.0-mvp",
|
|
1274
|
+
schemaVersion: "temporal-analysis/v1",
|
|
1275
|
+
inputs: {
|
|
1276
|
+
projectRoot: projectName,
|
|
1277
|
+
configHash,
|
|
1278
|
+
tsconfigHash: configHash,
|
|
1279
|
+
sourceFileHashes,
|
|
1280
|
+
temporalSdkVersions: temporalTypeScriptVersion ? {
|
|
1281
|
+
"@temporalio/workflow": temporalTypeScriptVersion
|
|
1282
|
+
} : {}
|
|
1283
|
+
}
|
|
1284
|
+
},
|
|
1285
|
+
project: {
|
|
1286
|
+
root: projectName,
|
|
1287
|
+
tsconfig: toProjectPath(root, tsconfig),
|
|
1288
|
+
...packageManager ? { packageManager } : {}
|
|
1289
|
+
},
|
|
1290
|
+
sdk: {
|
|
1291
|
+
...temporalTypeScriptVersion ? { temporalTypeScriptVersion } : {},
|
|
1292
|
+
detectedPackages: temporalTypeScriptVersion ? ["@temporalio/workflow"] : []
|
|
1293
|
+
},
|
|
1294
|
+
workers: analyzeWorkerFiles(project, root),
|
|
1295
|
+
workflows: collected.workflows,
|
|
1296
|
+
activities: collected.activities,
|
|
1297
|
+
clients: [],
|
|
1298
|
+
diagnostics: collected.diagnostics
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
async function analyzeProject(project) {
|
|
1302
|
+
return await analyzeWorkflowFiles({
|
|
1303
|
+
projectRoot: project.root,
|
|
1304
|
+
tsconfig: project.tsconfig,
|
|
1305
|
+
workflowFiles: project.workflowFiles,
|
|
1306
|
+
outputDirectory: project.outputDirectory
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// packages/history/src/index.ts
|
|
1311
|
+
import { createHash } from "crypto";
|
|
1312
|
+
import { basename as basename2, dirname, join, relative as relative2, resolve as resolve4 } from "path";
|
|
1313
|
+
|
|
1314
|
+
// packages/history/src/event-types.ts
|
|
1315
|
+
var eventTypes = {
|
|
1316
|
+
workflowExecutionStarted: 1,
|
|
1317
|
+
workflowExecutionCompleted: 2,
|
|
1318
|
+
workflowExecutionFailed: 3,
|
|
1319
|
+
workflowExecutionTimedOut: 4,
|
|
1320
|
+
workflowTaskScheduled: 5,
|
|
1321
|
+
workflowTaskStarted: 6,
|
|
1322
|
+
workflowTaskCompleted: 7,
|
|
1323
|
+
workflowTaskTimedOut: 8,
|
|
1324
|
+
workflowTaskFailed: 9,
|
|
1325
|
+
activityTaskScheduled: 10,
|
|
1326
|
+
activityTaskStarted: 11,
|
|
1327
|
+
activityTaskCompleted: 12,
|
|
1328
|
+
activityTaskFailed: 13,
|
|
1329
|
+
activityTaskTimedOut: 14,
|
|
1330
|
+
activityTaskCancelRequested: 15,
|
|
1331
|
+
activityTaskCanceled: 16,
|
|
1332
|
+
timerStarted: 17,
|
|
1333
|
+
timerFired: 18,
|
|
1334
|
+
timerCanceled: 19,
|
|
1335
|
+
workflowExecutionCancelRequested: 20,
|
|
1336
|
+
workflowExecutionCanceled: 21,
|
|
1337
|
+
requestCancelExternalWorkflowExecutionInitiated: 22,
|
|
1338
|
+
requestCancelExternalWorkflowExecutionFailed: 23,
|
|
1339
|
+
externalWorkflowExecutionCancelRequested: 24,
|
|
1340
|
+
markerRecorded: 25,
|
|
1341
|
+
workflowExecutionSignaled: 26,
|
|
1342
|
+
workflowExecutionTerminated: 27,
|
|
1343
|
+
workflowExecutionContinuedAsNew: 28,
|
|
1344
|
+
startChildWorkflowExecutionInitiated: 29,
|
|
1345
|
+
startChildWorkflowExecutionFailed: 30,
|
|
1346
|
+
childWorkflowExecutionStarted: 31,
|
|
1347
|
+
childWorkflowExecutionCompleted: 32,
|
|
1348
|
+
childWorkflowExecutionFailed: 33,
|
|
1349
|
+
childWorkflowExecutionCanceled: 34,
|
|
1350
|
+
childWorkflowExecutionTimedOut: 35,
|
|
1351
|
+
childWorkflowExecutionTerminated: 36,
|
|
1352
|
+
signalExternalWorkflowExecutionInitiated: 37,
|
|
1353
|
+
signalExternalWorkflowExecutionFailed: 38,
|
|
1354
|
+
externalWorkflowExecutionSignaled: 39,
|
|
1355
|
+
upsertWorkflowSearchAttributes: 40,
|
|
1356
|
+
workflowExecutionUpdateAccepted: 41,
|
|
1357
|
+
workflowExecutionUpdateRejected: 42,
|
|
1358
|
+
workflowExecutionUpdateCompleted: 43,
|
|
1359
|
+
workflowPropertiesModifiedExternally: 44,
|
|
1360
|
+
activityPropertiesModifiedExternally: 45,
|
|
1361
|
+
workflowPropertiesModified: 46,
|
|
1362
|
+
workflowExecutionUpdateAdmitted: 47
|
|
1363
|
+
};
|
|
1364
|
+
var eventTypeNames = new Map([
|
|
1365
|
+
[eventTypes.workflowExecutionStarted, "WorkflowExecutionStarted"],
|
|
1366
|
+
[eventTypes.workflowExecutionCompleted, "WorkflowExecutionCompleted"],
|
|
1367
|
+
[eventTypes.workflowExecutionFailed, "WorkflowExecutionFailed"],
|
|
1368
|
+
[eventTypes.workflowExecutionTimedOut, "WorkflowExecutionTimedOut"],
|
|
1369
|
+
[eventTypes.workflowTaskScheduled, "WorkflowTaskScheduled"],
|
|
1370
|
+
[eventTypes.workflowTaskStarted, "WorkflowTaskStarted"],
|
|
1371
|
+
[eventTypes.workflowTaskCompleted, "WorkflowTaskCompleted"],
|
|
1372
|
+
[eventTypes.workflowTaskTimedOut, "WorkflowTaskTimedOut"],
|
|
1373
|
+
[eventTypes.workflowTaskFailed, "WorkflowTaskFailed"],
|
|
1374
|
+
[eventTypes.activityTaskScheduled, "ActivityTaskScheduled"],
|
|
1375
|
+
[eventTypes.activityTaskStarted, "ActivityTaskStarted"],
|
|
1376
|
+
[eventTypes.activityTaskCompleted, "ActivityTaskCompleted"],
|
|
1377
|
+
[eventTypes.activityTaskFailed, "ActivityTaskFailed"],
|
|
1378
|
+
[eventTypes.activityTaskTimedOut, "ActivityTaskTimedOut"],
|
|
1379
|
+
[eventTypes.activityTaskCancelRequested, "ActivityTaskCancelRequested"],
|
|
1380
|
+
[eventTypes.activityTaskCanceled, "ActivityTaskCanceled"],
|
|
1381
|
+
[eventTypes.timerStarted, "TimerStarted"],
|
|
1382
|
+
[eventTypes.timerFired, "TimerFired"],
|
|
1383
|
+
[eventTypes.timerCanceled, "TimerCanceled"],
|
|
1384
|
+
[eventTypes.workflowExecutionCancelRequested, "WorkflowExecutionCancelRequested"],
|
|
1385
|
+
[eventTypes.workflowExecutionCanceled, "WorkflowExecutionCanceled"],
|
|
1386
|
+
[
|
|
1387
|
+
eventTypes.requestCancelExternalWorkflowExecutionInitiated,
|
|
1388
|
+
"RequestCancelExternalWorkflowExecutionInitiated"
|
|
1389
|
+
],
|
|
1390
|
+
[
|
|
1391
|
+
eventTypes.requestCancelExternalWorkflowExecutionFailed,
|
|
1392
|
+
"RequestCancelExternalWorkflowExecutionFailed"
|
|
1393
|
+
],
|
|
1394
|
+
[eventTypes.externalWorkflowExecutionCancelRequested, "ExternalWorkflowExecutionCancelRequested"],
|
|
1395
|
+
[eventTypes.markerRecorded, "MarkerRecorded"],
|
|
1396
|
+
[eventTypes.workflowExecutionSignaled, "WorkflowExecutionSignaled"],
|
|
1397
|
+
[eventTypes.workflowExecutionTerminated, "WorkflowExecutionTerminated"],
|
|
1398
|
+
[eventTypes.workflowExecutionContinuedAsNew, "WorkflowExecutionContinuedAsNew"],
|
|
1399
|
+
[eventTypes.startChildWorkflowExecutionInitiated, "StartChildWorkflowExecutionInitiated"],
|
|
1400
|
+
[eventTypes.startChildWorkflowExecutionFailed, "StartChildWorkflowExecutionFailed"],
|
|
1401
|
+
[eventTypes.childWorkflowExecutionStarted, "ChildWorkflowExecutionStarted"],
|
|
1402
|
+
[eventTypes.childWorkflowExecutionCompleted, "ChildWorkflowExecutionCompleted"],
|
|
1403
|
+
[eventTypes.childWorkflowExecutionFailed, "ChildWorkflowExecutionFailed"],
|
|
1404
|
+
[eventTypes.childWorkflowExecutionCanceled, "ChildWorkflowExecutionCanceled"],
|
|
1405
|
+
[eventTypes.childWorkflowExecutionTimedOut, "ChildWorkflowExecutionTimedOut"],
|
|
1406
|
+
[eventTypes.childWorkflowExecutionTerminated, "ChildWorkflowExecutionTerminated"],
|
|
1407
|
+
[eventTypes.signalExternalWorkflowExecutionInitiated, "SignalExternalWorkflowExecutionInitiated"],
|
|
1408
|
+
[eventTypes.signalExternalWorkflowExecutionFailed, "SignalExternalWorkflowExecutionFailed"],
|
|
1409
|
+
[eventTypes.externalWorkflowExecutionSignaled, "ExternalWorkflowExecutionSignaled"],
|
|
1410
|
+
[eventTypes.upsertWorkflowSearchAttributes, "UpsertWorkflowSearchAttributes"],
|
|
1411
|
+
[eventTypes.workflowExecutionUpdateAccepted, "WorkflowExecutionUpdateAccepted"],
|
|
1412
|
+
[eventTypes.workflowExecutionUpdateRejected, "WorkflowExecutionUpdateRejected"],
|
|
1413
|
+
[eventTypes.workflowExecutionUpdateCompleted, "WorkflowExecutionUpdateCompleted"],
|
|
1414
|
+
[eventTypes.workflowPropertiesModifiedExternally, "WorkflowPropertiesModifiedExternally"],
|
|
1415
|
+
[eventTypes.activityPropertiesModifiedExternally, "ActivityPropertiesModifiedExternally"],
|
|
1416
|
+
[eventTypes.workflowPropertiesModified, "WorkflowPropertiesModified"],
|
|
1417
|
+
[eventTypes.workflowExecutionUpdateAdmitted, "WorkflowExecutionUpdateAdmitted"]
|
|
1418
|
+
]);
|
|
1419
|
+
function getEventTypeName(eventType) {
|
|
1420
|
+
return eventTypeNames.get(eventType) ?? `UnknownEventType${eventType}`;
|
|
1421
|
+
}
|
|
1422
|
+
function isKnownEventType(eventType) {
|
|
1423
|
+
return eventTypeNames.has(eventType);
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// packages/history/src/history-json.ts
|
|
1427
|
+
function isRecord2(value) {
|
|
1428
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1429
|
+
}
|
|
1430
|
+
function readRecordField(value, field) {
|
|
1431
|
+
const candidate = value[field];
|
|
1432
|
+
return isRecord2(candidate) ? candidate : undefined;
|
|
1433
|
+
}
|
|
1434
|
+
function readArrayField(value, field) {
|
|
1435
|
+
const candidate = value[field];
|
|
1436
|
+
return Array.isArray(candidate) ? candidate : [];
|
|
1437
|
+
}
|
|
1438
|
+
function readStringField(value, field) {
|
|
1439
|
+
const candidate = value?.[field];
|
|
1440
|
+
return typeof candidate === "string" && candidate.length > 0 ? candidate : undefined;
|
|
1441
|
+
}
|
|
1442
|
+
function readPositiveIntegerField(value, field) {
|
|
1443
|
+
const candidate = value?.[field];
|
|
1444
|
+
if (typeof candidate === "number" && Number.isInteger(candidate) && candidate > 0) {
|
|
1445
|
+
return candidate;
|
|
1446
|
+
}
|
|
1447
|
+
if (typeof candidate === "string" && /^[1-9]\d*$/.test(candidate)) {
|
|
1448
|
+
return Number(candidate);
|
|
1449
|
+
}
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
function readHistoryEvent(value, index) {
|
|
1453
|
+
if (!isRecord2(value)) {
|
|
1454
|
+
throw new Error(`History event at index ${index} is not an object.`);
|
|
1455
|
+
}
|
|
1456
|
+
const eventId = readPositiveIntegerField(value, "eventId");
|
|
1457
|
+
const eventType = readPositiveIntegerField(value, "eventType");
|
|
1458
|
+
const eventTime = readStringField(value, "eventTime");
|
|
1459
|
+
if (!eventId) {
|
|
1460
|
+
throw new Error(`History event at index ${index} is missing a positive eventId.`);
|
|
1461
|
+
}
|
|
1462
|
+
if (!eventType) {
|
|
1463
|
+
throw new Error(`History event ${eventId} is missing a positive eventType.`);
|
|
1464
|
+
}
|
|
1465
|
+
if (!eventTime) {
|
|
1466
|
+
throw new Error(`History event ${eventId} is missing eventTime.`);
|
|
1467
|
+
}
|
|
1468
|
+
return {
|
|
1469
|
+
eventId,
|
|
1470
|
+
eventType,
|
|
1471
|
+
eventTypeName: getEventTypeName(eventType),
|
|
1472
|
+
eventTime,
|
|
1473
|
+
raw: value
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
function parseEventHistoryEvents(history) {
|
|
1477
|
+
const events = Array.isArray(history) ? history : isRecord2(history) ? readArrayField(history, "events") : [];
|
|
1478
|
+
if (events.length === 0) {
|
|
1479
|
+
throw new Error("Event History must contain at least one event.");
|
|
1480
|
+
}
|
|
1481
|
+
return events.map((event, index) => readHistoryEvent(event, index)).toSorted((left, right) => left.eventId - right.eventId);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
// packages/history/src/payloads.ts
|
|
1485
|
+
function decodeBase64(value) {
|
|
1486
|
+
return Buffer.from(value, "base64").toString("utf8");
|
|
1487
|
+
}
|
|
1488
|
+
function getPayloadEncoding(payload) {
|
|
1489
|
+
const metadata = readRecordField(payload, "metadata");
|
|
1490
|
+
const encoded = readStringField(metadata, "encoding");
|
|
1491
|
+
return encoded ? decodeBase64(encoded) : undefined;
|
|
1492
|
+
}
|
|
1493
|
+
var redactedPlaceholder = "[REDACTED]";
|
|
1494
|
+
function redactString(value, rules, state) {
|
|
1495
|
+
if (rules.patterns.some((pattern) => value.includes(pattern))) {
|
|
1496
|
+
state.redacted = true;
|
|
1497
|
+
return redactedPlaceholder;
|
|
1498
|
+
}
|
|
1499
|
+
return value;
|
|
1500
|
+
}
|
|
1501
|
+
function redactPreview(value, rules, state) {
|
|
1502
|
+
if (typeof value === "string") {
|
|
1503
|
+
return redactString(value, rules, state);
|
|
1504
|
+
}
|
|
1505
|
+
if (Array.isArray(value)) {
|
|
1506
|
+
return value.map((entry) => redactPreview(entry, rules, state));
|
|
1507
|
+
}
|
|
1508
|
+
if (isRecord2(value)) {
|
|
1509
|
+
const redacted = {};
|
|
1510
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1511
|
+
if (rules.keys.has(key)) {
|
|
1512
|
+
state.redacted = true;
|
|
1513
|
+
redacted[key] = redactedPlaceholder;
|
|
1514
|
+
} else {
|
|
1515
|
+
redacted[key] = redactPreview(entry, rules, state);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
return redacted;
|
|
1519
|
+
}
|
|
1520
|
+
return value;
|
|
1521
|
+
}
|
|
1522
|
+
function decodeJsonPayload(payload, configuration) {
|
|
1523
|
+
const data = readStringField(payload, "data");
|
|
1524
|
+
const maxPreviewBytes = configuration.maxPreviewBytes ?? 0;
|
|
1525
|
+
if (!data || getPayloadEncoding(payload) !== "json/plain") {
|
|
1526
|
+
return { decoded: false };
|
|
1527
|
+
}
|
|
1528
|
+
const decoded = decodeBase64(data);
|
|
1529
|
+
if (maxPreviewBytes <= 0 || Buffer.byteLength(decoded) > maxPreviewBytes) {
|
|
1530
|
+
return { decoded: false };
|
|
1531
|
+
}
|
|
1532
|
+
const rules = {
|
|
1533
|
+
keys: new Set(configuration.redactKeys ?? []),
|
|
1534
|
+
patterns: configuration.redactPatterns ?? []
|
|
1535
|
+
};
|
|
1536
|
+
const state = { redacted: false };
|
|
1537
|
+
const preview = redactPreview(JSON.parse(decoded), rules, state);
|
|
1538
|
+
return {
|
|
1539
|
+
decoded: true,
|
|
1540
|
+
preview,
|
|
1541
|
+
redacted: state.redacted
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
function createPayloadReferences(event, container, kind, configuration) {
|
|
1545
|
+
const payloads = container ? readArrayField(container, "payloads") : [];
|
|
1546
|
+
const shouldDecode = configuration.decodePayloads === true && configuration.redactPayloads !== true;
|
|
1547
|
+
return payloads.map((payload, index) => {
|
|
1548
|
+
const preview = shouldDecode && isRecord2(payload) ? decodeJsonPayload(payload, configuration) : { decoded: false };
|
|
1549
|
+
return {
|
|
1550
|
+
id: `payload:event-${event.eventId}:${kind}:${index}`,
|
|
1551
|
+
eventId: event.eventId,
|
|
1552
|
+
kind,
|
|
1553
|
+
decoded: preview.decoded,
|
|
1554
|
+
...preview.decoded ? { preview: preview.preview } : {},
|
|
1555
|
+
redacted: preview.decoded ? preview.redacted : true
|
|
1556
|
+
};
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
// packages/history/src/references.ts
|
|
1561
|
+
function createEventReference(event) {
|
|
1562
|
+
return {
|
|
1563
|
+
eventId: event.eventId,
|
|
1564
|
+
eventType: event.eventTypeName
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
function getDurationMs(startedAt, closedAt) {
|
|
1568
|
+
if (!closedAt) {
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
const duration = Date.parse(closedAt) - Date.parse(startedAt);
|
|
1572
|
+
return Number.isFinite(duration) && duration >= 0 ? duration : undefined;
|
|
1573
|
+
}
|
|
1574
|
+
function collectPayloadIds(payloads, event, container, kind, configuration) {
|
|
1575
|
+
const references = createPayloadReferences(event, container, kind, configuration);
|
|
1576
|
+
payloads.push(...references);
|
|
1577
|
+
return references.map((reference) => reference.id);
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
// packages/history/src/activity-operations.ts
|
|
1581
|
+
function createActivityOperations(events, payloads, configuration) {
|
|
1582
|
+
return collectActivities(events, payloads, configuration).map(createActivityOperation);
|
|
1583
|
+
}
|
|
1584
|
+
function collectActivities(events, payloads, configuration) {
|
|
1585
|
+
const activities = new Map;
|
|
1586
|
+
for (const event of events) {
|
|
1587
|
+
recordActivityEvent(event, activities, payloads, configuration);
|
|
1588
|
+
}
|
|
1589
|
+
return [...activities.values()].toSorted((left, right) => left.scheduled.eventId - right.scheduled.eventId);
|
|
1590
|
+
}
|
|
1591
|
+
function recordActivityEvent(event, activities, payloads, configuration) {
|
|
1592
|
+
if (event.eventType === eventTypes.activityTaskScheduled) {
|
|
1593
|
+
recordScheduledActivity(event, activities, payloads, configuration);
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
recordActivityProgress(event, activities, payloads, configuration);
|
|
1597
|
+
}
|
|
1598
|
+
function recordScheduledActivity(event, activities, payloads, configuration) {
|
|
1599
|
+
const attributes = readRecordField(event.raw, "activityTaskScheduledEventAttributes");
|
|
1600
|
+
const activityType = readStringField(readRecordField(attributes ?? {}, "activityType"), "name");
|
|
1601
|
+
const activityId = readStringField(attributes, "activityId") ?? String(event.eventId);
|
|
1602
|
+
const input = readRecordField(attributes ?? {}, "input");
|
|
1603
|
+
activities.set(event.eventId, {
|
|
1604
|
+
scheduled: event,
|
|
1605
|
+
activityId,
|
|
1606
|
+
activityType: activityType ?? "unknownActivity",
|
|
1607
|
+
payloadReferences: collectPayloadIds(payloads, event, input, "input", configuration),
|
|
1608
|
+
started: [],
|
|
1609
|
+
closed: []
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
function recordActivityProgress(event, activities, payloads, configuration) {
|
|
1613
|
+
const attributes = getActivityEventAttributes(event);
|
|
1614
|
+
const scheduledEventId = readPositiveIntegerField(attributes, "scheduledEventId");
|
|
1615
|
+
const activity = scheduledEventId ? activities.get(scheduledEventId) : undefined;
|
|
1616
|
+
if (!activity) {
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
if (event.eventType === eventTypes.activityTaskStarted) {
|
|
1620
|
+
activity.started.push(event);
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
const status = getActivityClosedStatus(event.eventType);
|
|
1624
|
+
if (!status) {
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
recordActivityClose(event, activity, attributes, status, payloads, configuration);
|
|
1628
|
+
}
|
|
1629
|
+
function recordActivityClose(event, activity, attributes, status, payloads, configuration) {
|
|
1630
|
+
const result = readRecordField(attributes ?? {}, status === "failed" ? "failure" : "result");
|
|
1631
|
+
const kind = status === "failed" ? "failure" : "result";
|
|
1632
|
+
activity.closed.push({ event, status });
|
|
1633
|
+
activity.payloadReferences.push(...collectPayloadIds(payloads, event, result, kind, configuration));
|
|
1634
|
+
}
|
|
1635
|
+
function getActivityEventAttributes(event) {
|
|
1636
|
+
if (event.eventType === eventTypes.activityTaskStarted) {
|
|
1637
|
+
return readRecordField(event.raw, "activityTaskStartedEventAttributes");
|
|
1638
|
+
}
|
|
1639
|
+
if (event.eventType === eventTypes.activityTaskCompleted) {
|
|
1640
|
+
return readRecordField(event.raw, "activityTaskCompletedEventAttributes");
|
|
1641
|
+
}
|
|
1642
|
+
if (event.eventType === eventTypes.activityTaskFailed) {
|
|
1643
|
+
return readRecordField(event.raw, "activityTaskFailedEventAttributes");
|
|
1644
|
+
}
|
|
1645
|
+
if (event.eventType === eventTypes.activityTaskTimedOut) {
|
|
1646
|
+
return readRecordField(event.raw, "activityTaskTimedOutEventAttributes");
|
|
1647
|
+
}
|
|
1648
|
+
if (event.eventType === eventTypes.activityTaskCanceled) {
|
|
1649
|
+
return readRecordField(event.raw, "activityTaskCanceledEventAttributes");
|
|
1650
|
+
}
|
|
1651
|
+
if (event.eventType === eventTypes.activityTaskCancelRequested) {
|
|
1652
|
+
return readRecordField(event.raw, "activityTaskCancelRequestedEventAttributes");
|
|
1653
|
+
}
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
function getActivityClosedStatus(eventType) {
|
|
1657
|
+
if (eventType === eventTypes.activityTaskCompleted) {
|
|
1658
|
+
return "completed";
|
|
1659
|
+
}
|
|
1660
|
+
if (eventType === eventTypes.activityTaskFailed) {
|
|
1661
|
+
return "failed";
|
|
1662
|
+
}
|
|
1663
|
+
if (eventType === eventTypes.activityTaskTimedOut) {
|
|
1664
|
+
return "timedOut";
|
|
1665
|
+
}
|
|
1666
|
+
if (eventType === eventTypes.activityTaskCanceled) {
|
|
1667
|
+
return "canceled";
|
|
1668
|
+
}
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
function createActivityOperation(activity) {
|
|
1672
|
+
const lastClosed = activity.closed.at(-1);
|
|
1673
|
+
const eventReferences = [
|
|
1674
|
+
activity.scheduled,
|
|
1675
|
+
...activity.started,
|
|
1676
|
+
...activity.closed.map((closed) => closed.event)
|
|
1677
|
+
].map(createEventReference);
|
|
1678
|
+
return {
|
|
1679
|
+
id: `activity:${activity.activityType}:${activity.scheduled.eventId}`,
|
|
1680
|
+
kind: "activity",
|
|
1681
|
+
activityType: activity.activityType,
|
|
1682
|
+
activityId: activity.activityId,
|
|
1683
|
+
status: lastClosed?.status ?? "pending",
|
|
1684
|
+
attempts: createActivityAttempts(activity),
|
|
1685
|
+
firstScheduledAt: activity.scheduled.eventTime,
|
|
1686
|
+
...lastClosed ? { closedAt: lastClosed.event.eventTime } : {},
|
|
1687
|
+
...lastClosed ? { durationMs: getDurationMs(activity.scheduled.eventTime, lastClosed.event.eventTime) } : {},
|
|
1688
|
+
eventReferences,
|
|
1689
|
+
payloadReferences: activity.payloadReferences
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
function readStartedAttemptNumber(started) {
|
|
1693
|
+
if (!started) {
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
const attributes = readRecordField(started.raw, "activityTaskStartedEventAttributes");
|
|
1697
|
+
return readPositiveIntegerField(attributes, "attempt");
|
|
1698
|
+
}
|
|
1699
|
+
function createActivityAttempts(activity) {
|
|
1700
|
+
if (activity.closed.length === 0) {
|
|
1701
|
+
return [
|
|
1702
|
+
{
|
|
1703
|
+
attempt: readStartedAttemptNumber(activity.started[0]) ?? 1,
|
|
1704
|
+
scheduledEventId: activity.scheduled.eventId,
|
|
1705
|
+
...activity.started[0] ? { startedEventId: activity.started[0].eventId } : {},
|
|
1706
|
+
status: "pending"
|
|
1707
|
+
}
|
|
1708
|
+
];
|
|
1709
|
+
}
|
|
1710
|
+
return activity.closed.map((closed, index) => ({
|
|
1711
|
+
attempt: readStartedAttemptNumber(activity.started[index]) ?? index + 1,
|
|
1712
|
+
scheduledEventId: activity.scheduled.eventId,
|
|
1713
|
+
...activity.started[index] ? { startedEventId: activity.started[index].eventId } : {},
|
|
1714
|
+
closedEventId: closed.event.eventId,
|
|
1715
|
+
status: closed.status
|
|
1716
|
+
}));
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
// packages/history/src/child-workflow-operations.ts
|
|
1720
|
+
var childProgressEvents = new Map([
|
|
1721
|
+
[
|
|
1722
|
+
eventTypes.startChildWorkflowExecutionFailed,
|
|
1723
|
+
{ attributesField: "startChildWorkflowExecutionFailedEventAttributes", status: "startFailed" }
|
|
1724
|
+
],
|
|
1725
|
+
[
|
|
1726
|
+
eventTypes.childWorkflowExecutionStarted,
|
|
1727
|
+
{ attributesField: "childWorkflowExecutionStartedEventAttributes", status: "started" }
|
|
1728
|
+
],
|
|
1729
|
+
[
|
|
1730
|
+
eventTypes.childWorkflowExecutionCompleted,
|
|
1731
|
+
{ attributesField: "childWorkflowExecutionCompletedEventAttributes", status: "completed" }
|
|
1732
|
+
],
|
|
1733
|
+
[
|
|
1734
|
+
eventTypes.childWorkflowExecutionFailed,
|
|
1735
|
+
{ attributesField: "childWorkflowExecutionFailedEventAttributes", status: "failed" }
|
|
1736
|
+
],
|
|
1737
|
+
[
|
|
1738
|
+
eventTypes.childWorkflowExecutionCanceled,
|
|
1739
|
+
{ attributesField: "childWorkflowExecutionCanceledEventAttributes", status: "canceled" }
|
|
1740
|
+
],
|
|
1741
|
+
[
|
|
1742
|
+
eventTypes.childWorkflowExecutionTimedOut,
|
|
1743
|
+
{ attributesField: "childWorkflowExecutionTimedOutEventAttributes", status: "timedOut" }
|
|
1744
|
+
],
|
|
1745
|
+
[
|
|
1746
|
+
eventTypes.childWorkflowExecutionTerminated,
|
|
1747
|
+
{ attributesField: "childWorkflowExecutionTerminatedEventAttributes", status: "terminated" }
|
|
1748
|
+
]
|
|
1749
|
+
]);
|
|
1750
|
+
function recordInitiatedChild(event, children, payloads, configuration) {
|
|
1751
|
+
const attributes = readRecordField(event.raw, "startChildWorkflowExecutionInitiatedEventAttributes");
|
|
1752
|
+
const workflowType = readStringField(readRecordField(attributes ?? {}, "workflowType"), "name") ?? "unknownChildWorkflow";
|
|
1753
|
+
children.set(event.eventId, {
|
|
1754
|
+
initiated: event,
|
|
1755
|
+
workflowType,
|
|
1756
|
+
childWorkflowId: readStringField(attributes, "workflowId") ?? String(event.eventId),
|
|
1757
|
+
payloadReferences: collectPayloadIds(payloads, event, readRecordField(attributes ?? {}, "input"), "input", configuration),
|
|
1758
|
+
progress: []
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
function recordChildClosePayloads(event, child, attributes, status, payloads, configuration) {
|
|
1762
|
+
if (status !== "completed" && status !== "failed") {
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1765
|
+
const field = status === "completed" ? "result" : "failure";
|
|
1766
|
+
child.payloadReferences.push(...collectPayloadIds(payloads, event, readRecordField(attributes ?? {}, field), field, configuration));
|
|
1767
|
+
}
|
|
1768
|
+
function recordChildProgress(event, children, payloads, configuration) {
|
|
1769
|
+
const progressKind = childProgressEvents.get(event.eventType);
|
|
1770
|
+
if (!progressKind) {
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
const attributes = readRecordField(event.raw, progressKind.attributesField);
|
|
1774
|
+
const initiatedEventId = readPositiveIntegerField(attributes, "initiatedEventId");
|
|
1775
|
+
const child = initiatedEventId ? children.get(initiatedEventId) : undefined;
|
|
1776
|
+
if (!child) {
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
child.progress.push({ event, status: progressKind.status });
|
|
1780
|
+
const runId = readStringField(readRecordField(attributes ?? {}, "workflowExecution"), "runId");
|
|
1781
|
+
if (runId && !child.childRunId) {
|
|
1782
|
+
child.childRunId = runId;
|
|
1783
|
+
}
|
|
1784
|
+
recordChildClosePayloads(event, child, attributes, progressKind.status, payloads, configuration);
|
|
1785
|
+
}
|
|
1786
|
+
function isClosedStatus(status) {
|
|
1787
|
+
return status !== "started";
|
|
1788
|
+
}
|
|
1789
|
+
function createChildWorkflowOperations(events, payloads, configuration) {
|
|
1790
|
+
const children = new Map;
|
|
1791
|
+
for (const event of events) {
|
|
1792
|
+
if (event.eventType === eventTypes.startChildWorkflowExecutionInitiated) {
|
|
1793
|
+
recordInitiatedChild(event, children, payloads, configuration);
|
|
1794
|
+
} else {
|
|
1795
|
+
recordChildProgress(event, children, payloads, configuration);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
return [...children.values()].toSorted((left, right) => left.initiated.eventId - right.initiated.eventId).map((child) => {
|
|
1799
|
+
const closed = child.progress.findLast((entry) => isClosedStatus(entry.status));
|
|
1800
|
+
return {
|
|
1801
|
+
id: `child-workflow:${child.workflowType}:${child.initiated.eventId}`,
|
|
1802
|
+
kind: "child-workflow",
|
|
1803
|
+
workflowType: child.workflowType,
|
|
1804
|
+
childWorkflowId: child.childWorkflowId,
|
|
1805
|
+
...child.childRunId ? { childRunId: child.childRunId } : {},
|
|
1806
|
+
status: closed?.status ?? child.progress.at(-1)?.status ?? "initiated",
|
|
1807
|
+
initiatedAt: child.initiated.eventTime,
|
|
1808
|
+
...closed ? { closedAt: closed.event.eventTime } : {},
|
|
1809
|
+
eventReferences: [
|
|
1810
|
+
createEventReference(child.initiated),
|
|
1811
|
+
...child.progress.map((entry) => createEventReference(entry.event))
|
|
1812
|
+
],
|
|
1813
|
+
payloadReferences: child.payloadReferences
|
|
1814
|
+
};
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
// packages/history/src/diagnostics.ts
|
|
1819
|
+
function createUnknownEventDiagnostics(events) {
|
|
1820
|
+
return events.filter((event) => !isKnownEventType(event.eventType)).map((event) => ({
|
|
1821
|
+
code: "TEH_UNKNOWN_EVENT_TYPE",
|
|
1822
|
+
category: "history",
|
|
1823
|
+
severity: "warning",
|
|
1824
|
+
message: `Unsupported Event History event type ${event.eventType} at event ${event.eventId}.`,
|
|
1825
|
+
confidence: "unknown"
|
|
1826
|
+
}));
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
// packages/history/src/external-signal-operations.ts
|
|
1830
|
+
function recordInitiatedExternalSignal(event, signals, payloads, configuration) {
|
|
1831
|
+
const attributes = readRecordField(event.raw, "signalExternalWorkflowExecutionInitiatedEventAttributes");
|
|
1832
|
+
const execution = readRecordField(attributes ?? {}, "workflowExecution");
|
|
1833
|
+
signals.set(event.eventId, {
|
|
1834
|
+
initiated: event,
|
|
1835
|
+
signalName: readStringField(attributes, "signalName") ?? "unknownSignal",
|
|
1836
|
+
targetWorkflowId: readStringField(execution, "workflowId") ?? "unknown-workflow-id",
|
|
1837
|
+
payloadReferences: collectPayloadIds(payloads, event, readRecordField(attributes ?? {}, "input"), "signal", configuration)
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
function recordExternalSignalClose(event, signals, status, attributesField) {
|
|
1841
|
+
const attributes = readRecordField(event.raw, attributesField);
|
|
1842
|
+
const initiatedEventId = readPositiveIntegerField(attributes, "initiatedEventId");
|
|
1843
|
+
const signal = initiatedEventId ? signals.get(initiatedEventId) : undefined;
|
|
1844
|
+
if (!signal) {
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
signal.closed = { event, status };
|
|
1848
|
+
const runId = readStringField(readRecordField(attributes ?? {}, "workflowExecution"), "runId");
|
|
1849
|
+
if (runId) {
|
|
1850
|
+
signal.targetRunId = runId;
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
function createExternalSignalOperations(events, payloads, configuration) {
|
|
1854
|
+
const signals = new Map;
|
|
1855
|
+
for (const event of events) {
|
|
1856
|
+
if (event.eventType === eventTypes.signalExternalWorkflowExecutionInitiated) {
|
|
1857
|
+
recordInitiatedExternalSignal(event, signals, payloads, configuration);
|
|
1858
|
+
} else if (event.eventType === eventTypes.externalWorkflowExecutionSignaled) {
|
|
1859
|
+
recordExternalSignalClose(event, signals, "signaled", "externalWorkflowExecutionSignaledEventAttributes");
|
|
1860
|
+
} else if (event.eventType === eventTypes.signalExternalWorkflowExecutionFailed) {
|
|
1861
|
+
recordExternalSignalClose(event, signals, "failed", "signalExternalWorkflowExecutionFailedEventAttributes");
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
return [...signals.values()].toSorted((left, right) => left.initiated.eventId - right.initiated.eventId).map((signal) => ({
|
|
1865
|
+
id: `external-signal:${signal.signalName}:${signal.initiated.eventId}`,
|
|
1866
|
+
kind: "external-signal",
|
|
1867
|
+
signalName: signal.signalName,
|
|
1868
|
+
targetWorkflowId: signal.targetWorkflowId,
|
|
1869
|
+
...signal.targetRunId ? { targetRunId: signal.targetRunId } : {},
|
|
1870
|
+
status: signal.closed?.status ?? "initiated",
|
|
1871
|
+
initiatedAt: signal.initiated.eventTime,
|
|
1872
|
+
...signal.closed ? { closedAt: signal.closed.event.eventTime } : {},
|
|
1873
|
+
eventReferences: [
|
|
1874
|
+
createEventReference(signal.initiated),
|
|
1875
|
+
...signal.closed ? [createEventReference(signal.closed.event)] : []
|
|
1876
|
+
],
|
|
1877
|
+
payloadReferences: signal.payloadReferences
|
|
1878
|
+
}));
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
// packages/history/src/marker-operations.ts
|
|
1882
|
+
function readPatchDetails(attributes) {
|
|
1883
|
+
const details = readRecordField(attributes ?? {}, "details");
|
|
1884
|
+
const patchData = readRecordField(details ?? {}, "patch-data");
|
|
1885
|
+
const [payload] = readArrayField(patchData ?? {}, "payloads");
|
|
1886
|
+
if (!isRecord2(payload)) {
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
const data = readStringField(payload, "data");
|
|
1890
|
+
if (!data) {
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
try {
|
|
1894
|
+
const decoded = JSON.parse(Buffer.from(data, "base64").toString("utf8"));
|
|
1895
|
+
if (isRecord2(decoded) && typeof decoded["id"] === "string") {
|
|
1896
|
+
return {
|
|
1897
|
+
patchId: decoded["id"],
|
|
1898
|
+
deprecated: decoded["deprecated"] === true
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
} catch {
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
function createMarkerOperations(events) {
|
|
1907
|
+
return events.filter((event) => event.eventType === eventTypes.markerRecorded).map((event) => {
|
|
1908
|
+
const attributes = readRecordField(event.raw, "markerRecordedEventAttributes");
|
|
1909
|
+
const markerName = readStringField(attributes, "markerName") ?? "unknownMarker";
|
|
1910
|
+
const patch = markerName === "core_patch" ? readPatchDetails(attributes) : undefined;
|
|
1911
|
+
return {
|
|
1912
|
+
id: `marker:${markerName}:${event.eventId}`,
|
|
1913
|
+
kind: "marker",
|
|
1914
|
+
markerName,
|
|
1915
|
+
...patch ? { patchId: patch.patchId, deprecated: patch.deprecated } : {},
|
|
1916
|
+
recordedAt: event.eventTime,
|
|
1917
|
+
eventReferences: [createEventReference(event)]
|
|
1918
|
+
};
|
|
1919
|
+
});
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
// packages/history/src/signal-operations.ts
|
|
1923
|
+
function createSignalOperations(events, payloads, configuration) {
|
|
1924
|
+
return events.filter((event) => event.eventType === eventTypes.workflowExecutionSignaled).map((event) => {
|
|
1925
|
+
const attributes = readRecordField(event.raw, "workflowExecutionSignaledEventAttributes");
|
|
1926
|
+
const signalName = readStringField(attributes, "signalName") ?? "unknownSignal";
|
|
1927
|
+
const input = readRecordField(attributes ?? {}, "input");
|
|
1928
|
+
return {
|
|
1929
|
+
id: `signal:${signalName}:${event.eventId}`,
|
|
1930
|
+
kind: "signal",
|
|
1931
|
+
signalName,
|
|
1932
|
+
receivedAt: event.eventTime,
|
|
1933
|
+
eventReferences: [createEventReference(event)],
|
|
1934
|
+
payloadReferences: collectPayloadIds(payloads, event, input, "signal", configuration)
|
|
1935
|
+
};
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
// packages/history/src/workflow-lifecycle.ts
|
|
1940
|
+
function createWorkflowLifecycle(events, payloads, configuration, provenance) {
|
|
1941
|
+
const workflowStartedEvent = getWorkflowStartedEvent(events);
|
|
1942
|
+
const workflowClosedEvent = findWorkflowClosedEvent(events);
|
|
1943
|
+
const workflowStartedAttributes = readRecordField(workflowStartedEvent.raw, "workflowExecutionStartedEventAttributes");
|
|
1944
|
+
const workflowType = getWorkflowType(workflowStartedAttributes, provenance);
|
|
1945
|
+
const runId = readStringField(workflowStartedAttributes, "originalExecutionRunId") ?? "unknown-run-id";
|
|
1946
|
+
const workflowId = provenance?.workflowId ?? `${workflowType}-${runId}`;
|
|
1947
|
+
const startPayloads = collectPayloadIds(payloads, workflowStartedEvent, readRecordField(workflowStartedAttributes ?? {}, "input"), "input", configuration);
|
|
1948
|
+
const closePayloads = collectWorkflowClosePayloads(payloads, workflowClosedEvent, configuration);
|
|
1949
|
+
return {
|
|
1950
|
+
workflowStartedEvent,
|
|
1951
|
+
...workflowClosedEvent ? { workflowClosedEvent } : {},
|
|
1952
|
+
workflowStartedAttributes,
|
|
1953
|
+
workflowType,
|
|
1954
|
+
workflowId,
|
|
1955
|
+
runId,
|
|
1956
|
+
operations: createWorkflowOperations(workflowStartedEvent, workflowClosedEvent, startPayloads, closePayloads)
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
var closedStatusByEventType = new Map([
|
|
1960
|
+
[eventTypes.workflowExecutionCompleted, "completed"],
|
|
1961
|
+
[eventTypes.workflowExecutionFailed, "failed"],
|
|
1962
|
+
[eventTypes.workflowExecutionCanceled, "canceled"],
|
|
1963
|
+
[eventTypes.workflowExecutionTerminated, "terminated"],
|
|
1964
|
+
[eventTypes.workflowExecutionContinuedAsNew, "continued-as-new"]
|
|
1965
|
+
]);
|
|
1966
|
+
function getWorkflowClosedStatus(eventType) {
|
|
1967
|
+
return closedStatusByEventType.get(eventType) ?? "timedOut";
|
|
1968
|
+
}
|
|
1969
|
+
function createWorkflowExecution(lifecycle) {
|
|
1970
|
+
const closedEvent = lifecycle.workflowClosedEvent;
|
|
1971
|
+
return {
|
|
1972
|
+
workflowType: lifecycle.workflowType,
|
|
1973
|
+
workflowId: lifecycle.workflowId,
|
|
1974
|
+
runId: lifecycle.runId,
|
|
1975
|
+
status: closedEvent ? getWorkflowClosedStatus(closedEvent.eventType) : "running",
|
|
1976
|
+
startedAt: lifecycle.workflowStartedEvent.eventTime,
|
|
1977
|
+
...closedEvent ? { closedAt: closedEvent.eventTime } : {},
|
|
1978
|
+
...closedEvent ? {
|
|
1979
|
+
durationMs: getDurationMs(lifecycle.workflowStartedEvent.eventTime, closedEvent.eventTime)
|
|
1980
|
+
} : {}
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
function getWorkflowStartedEvent(events) {
|
|
1984
|
+
const event = events.find((candidate) => candidate.eventType === eventTypes.workflowExecutionStarted);
|
|
1985
|
+
if (!event) {
|
|
1986
|
+
throw new Error("Event History is missing WorkflowExecutionStarted.");
|
|
1987
|
+
}
|
|
1988
|
+
return event;
|
|
1989
|
+
}
|
|
1990
|
+
function getWorkflowType(attributes, provenance) {
|
|
1991
|
+
return provenance?.workflowType ?? readStringField(readRecordField(attributes ?? {}, "workflowType"), "name") ?? "unknownWorkflow";
|
|
1992
|
+
}
|
|
1993
|
+
function findWorkflowClosedEvent(events) {
|
|
1994
|
+
const closedEventTypes = new Set([
|
|
1995
|
+
eventTypes.workflowExecutionCompleted,
|
|
1996
|
+
eventTypes.workflowExecutionFailed,
|
|
1997
|
+
eventTypes.workflowExecutionTimedOut,
|
|
1998
|
+
eventTypes.workflowExecutionCanceled,
|
|
1999
|
+
eventTypes.workflowExecutionTerminated,
|
|
2000
|
+
eventTypes.workflowExecutionContinuedAsNew
|
|
2001
|
+
]);
|
|
2002
|
+
return events.find((event) => closedEventTypes.has(event.eventType));
|
|
2003
|
+
}
|
|
2004
|
+
var closePayloadFields = new Map([
|
|
2005
|
+
[
|
|
2006
|
+
eventTypes.workflowExecutionCompleted,
|
|
2007
|
+
{
|
|
2008
|
+
attributesField: "workflowExecutionCompletedEventAttributes",
|
|
2009
|
+
container: "result",
|
|
2010
|
+
kind: "result"
|
|
2011
|
+
}
|
|
2012
|
+
],
|
|
2013
|
+
[
|
|
2014
|
+
eventTypes.workflowExecutionFailed,
|
|
2015
|
+
{
|
|
2016
|
+
attributesField: "workflowExecutionFailedEventAttributes",
|
|
2017
|
+
container: "failure",
|
|
2018
|
+
kind: "failure"
|
|
2019
|
+
}
|
|
2020
|
+
],
|
|
2021
|
+
[
|
|
2022
|
+
eventTypes.workflowExecutionCanceled,
|
|
2023
|
+
{
|
|
2024
|
+
attributesField: "workflowExecutionCanceledEventAttributes",
|
|
2025
|
+
container: "details",
|
|
2026
|
+
kind: "result"
|
|
2027
|
+
}
|
|
2028
|
+
],
|
|
2029
|
+
[
|
|
2030
|
+
eventTypes.workflowExecutionContinuedAsNew,
|
|
2031
|
+
{
|
|
2032
|
+
attributesField: "workflowExecutionContinuedAsNewEventAttributes",
|
|
2033
|
+
container: "input",
|
|
2034
|
+
kind: "input"
|
|
2035
|
+
}
|
|
2036
|
+
]
|
|
2037
|
+
]);
|
|
2038
|
+
function collectWorkflowClosePayloads(payloads, closed, configuration) {
|
|
2039
|
+
const fields = closed ? closePayloadFields.get(closed.eventType) : undefined;
|
|
2040
|
+
if (!closed || !fields) {
|
|
2041
|
+
return [];
|
|
2042
|
+
}
|
|
2043
|
+
const attributes = readRecordField(closed.raw, fields.attributesField);
|
|
2044
|
+
return collectPayloadIds(payloads, closed, readRecordField(attributes ?? {}, fields.container), fields.kind, configuration);
|
|
2045
|
+
}
|
|
2046
|
+
function createCancelRequestOperations(events) {
|
|
2047
|
+
return events.filter((event) => event.eventType === eventTypes.workflowExecutionCancelRequested).map((event) => ({
|
|
2048
|
+
id: `cancel-request:${event.eventId}`,
|
|
2049
|
+
kind: "cancel-request",
|
|
2050
|
+
requestedAt: event.eventTime,
|
|
2051
|
+
eventReferences: [createEventReference(event)]
|
|
2052
|
+
}));
|
|
2053
|
+
}
|
|
2054
|
+
function createContinueAsNewOperations(events, payloads, configuration) {
|
|
2055
|
+
return events.filter((event) => event.eventType === eventTypes.workflowExecutionContinuedAsNew).map((event) => {
|
|
2056
|
+
const attributes = readRecordField(event.raw, "workflowExecutionContinuedAsNewEventAttributes");
|
|
2057
|
+
const newRunId = readStringField(attributes, "newExecutionRunId");
|
|
2058
|
+
return {
|
|
2059
|
+
id: `continue-as-new:${event.eventId}`,
|
|
2060
|
+
kind: "continue-as-new",
|
|
2061
|
+
...newRunId ? { newRunId } : {},
|
|
2062
|
+
occurredAt: event.eventTime,
|
|
2063
|
+
eventReferences: [createEventReference(event)],
|
|
2064
|
+
payloadReferences: collectPayloadIds(payloads, event, readRecordField(attributes ?? {}, "input"), "input", configuration)
|
|
2065
|
+
};
|
|
2066
|
+
});
|
|
2067
|
+
}
|
|
2068
|
+
function createWorkflowOperations(started, closed, startPayloads, closePayloads) {
|
|
2069
|
+
const operations = [
|
|
2070
|
+
{
|
|
2071
|
+
id: "workflow:start",
|
|
2072
|
+
kind: "workflow-lifecycle",
|
|
2073
|
+
status: "started",
|
|
2074
|
+
eventReferences: [createEventReference(started)],
|
|
2075
|
+
payloadReferences: startPayloads
|
|
2076
|
+
}
|
|
2077
|
+
];
|
|
2078
|
+
if (closed) {
|
|
2079
|
+
const closedStatus = getWorkflowClosedStatus(closed.eventType);
|
|
2080
|
+
operations.push({
|
|
2081
|
+
id: `workflow:${closedStatus}`,
|
|
2082
|
+
kind: "workflow-lifecycle",
|
|
2083
|
+
status: closedStatus === "running" ? "completed" : closedStatus,
|
|
2084
|
+
eventReferences: [createEventReference(closed)],
|
|
2085
|
+
payloadReferences: closePayloads
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
2088
|
+
return operations;
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
// packages/history/src/timeline.ts
|
|
2092
|
+
function openCloseSpans(operation, openAt, openLabel, closedAt, closedLabel) {
|
|
2093
|
+
const opened = operation.eventReferences[0];
|
|
2094
|
+
const closed = operation.eventReferences.at(-1);
|
|
2095
|
+
const spans = [{ at: openAt, label: openLabel, reference: opened }];
|
|
2096
|
+
if (closedAt && closed && closed !== opened) {
|
|
2097
|
+
spans.push({ at: closedAt, label: closedLabel, reference: closed });
|
|
2098
|
+
}
|
|
2099
|
+
return spans;
|
|
2100
|
+
}
|
|
2101
|
+
function activitySpans(operation) {
|
|
2102
|
+
return openCloseSpans(operation, operation.firstScheduledAt, `${operation.activityType} scheduled`, operation.closedAt, `${operation.activityType} ${operation.status}`);
|
|
2103
|
+
}
|
|
2104
|
+
function pointSpan(operation, at, label) {
|
|
2105
|
+
return [{ at, label, reference: operation.eventReferences[0] }];
|
|
2106
|
+
}
|
|
2107
|
+
function markerLabel(operation) {
|
|
2108
|
+
if (!operation.patchId) {
|
|
2109
|
+
return `Marker ${operation.markerName}`;
|
|
2110
|
+
}
|
|
2111
|
+
return `Patch ${operation.patchId}${operation.deprecated ? " (deprecated)" : ""}`;
|
|
2112
|
+
}
|
|
2113
|
+
function pointOperationSpans(operation) {
|
|
2114
|
+
switch (operation.kind) {
|
|
2115
|
+
case "signal":
|
|
2116
|
+
return pointSpan(operation, operation.receivedAt, `Signal ${operation.signalName} received`);
|
|
2117
|
+
case "marker":
|
|
2118
|
+
return pointSpan(operation, operation.recordedAt, markerLabel(operation));
|
|
2119
|
+
case "continue-as-new":
|
|
2120
|
+
return pointSpan(operation, operation.occurredAt, "Continued as new");
|
|
2121
|
+
case "cancel-request":
|
|
2122
|
+
return pointSpan(operation, operation.requestedAt, "Cancellation requested");
|
|
2123
|
+
default:
|
|
2124
|
+
return [];
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
function operationSpans(operation) {
|
|
2128
|
+
switch (operation.kind) {
|
|
2129
|
+
case "activity":
|
|
2130
|
+
return activitySpans(operation);
|
|
2131
|
+
case "timer":
|
|
2132
|
+
return openCloseSpans(operation, operation.startedAt, `Timer ${operation.timerId} started`, operation.closedAt, `Timer ${operation.timerId} ${operation.status}`);
|
|
2133
|
+
case "update":
|
|
2134
|
+
return openCloseSpans(operation, operation.acceptedAt, `Update ${operation.updateName} accepted`, operation.closedAt, `Update ${operation.updateName} ${operation.status}`);
|
|
2135
|
+
case "child-workflow":
|
|
2136
|
+
return openCloseSpans(operation, operation.initiatedAt, `Child ${operation.workflowType} initiated`, operation.closedAt, `Child ${operation.workflowType} ${operation.status}`);
|
|
2137
|
+
case "external-signal":
|
|
2138
|
+
return openCloseSpans(operation, operation.initiatedAt, `External signal ${operation.signalName} initiated`, operation.closedAt, `External signal ${operation.signalName} ${operation.status}`);
|
|
2139
|
+
default:
|
|
2140
|
+
return pointOperationSpans(operation);
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
function createTimeline(started, closed, operations) {
|
|
2144
|
+
const entries = [
|
|
2145
|
+
{
|
|
2146
|
+
id: `timeline:${started.eventId}`,
|
|
2147
|
+
operationId: "workflow:start",
|
|
2148
|
+
at: started.eventTime,
|
|
2149
|
+
label: "Workflow started",
|
|
2150
|
+
eventIds: [started.eventId]
|
|
2151
|
+
}
|
|
2152
|
+
];
|
|
2153
|
+
for (const operation of operations) {
|
|
2154
|
+
for (const span of operationSpans(operation)) {
|
|
2155
|
+
if (span.reference) {
|
|
2156
|
+
entries.push({
|
|
2157
|
+
id: `timeline:${span.reference.eventId}`,
|
|
2158
|
+
operationId: operation.id,
|
|
2159
|
+
at: span.at,
|
|
2160
|
+
label: span.label,
|
|
2161
|
+
eventIds: [span.reference.eventId]
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
if (closed) {
|
|
2167
|
+
entries.push({
|
|
2168
|
+
id: `timeline:${closed.eventId}`,
|
|
2169
|
+
operationId: `workflow:${getWorkflowClosedStatus(closed.eventType)}`,
|
|
2170
|
+
at: closed.eventTime,
|
|
2171
|
+
label: "Workflow closed",
|
|
2172
|
+
eventIds: [closed.eventId]
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
return entries.toSorted((left, right) => Date.parse(left.at) - Date.parse(right.at));
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
// packages/history/src/timer-operations.ts
|
|
2179
|
+
function readDurationText(attributes) {
|
|
2180
|
+
const timeout = readRecordField(attributes ?? {}, "startToFireTimeout");
|
|
2181
|
+
if (!timeout) {
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
const seconds = timeout["seconds"];
|
|
2185
|
+
if (typeof seconds === "string" || typeof seconds === "number") {
|
|
2186
|
+
return `${seconds}s`;
|
|
2187
|
+
}
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
function recordTimerClose(event, timers, attributesField, status) {
|
|
2191
|
+
const attributes = readRecordField(event.raw, attributesField);
|
|
2192
|
+
const startedEventId = readPositiveIntegerField(attributes, "startedEventId");
|
|
2193
|
+
const timer = startedEventId ? timers.get(startedEventId) : undefined;
|
|
2194
|
+
if (timer) {
|
|
2195
|
+
timer.closed = { event, status };
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
function createTimerOperations(events) {
|
|
2199
|
+
const timers = new Map;
|
|
2200
|
+
for (const event of events) {
|
|
2201
|
+
if (event.eventType === eventTypes.timerStarted) {
|
|
2202
|
+
const attributes = readRecordField(event.raw, "timerStartedEventAttributes");
|
|
2203
|
+
const durationText = readDurationText(attributes);
|
|
2204
|
+
timers.set(event.eventId, {
|
|
2205
|
+
started: event,
|
|
2206
|
+
timerId: readStringField(attributes, "timerId") ?? String(event.eventId),
|
|
2207
|
+
...durationText ? { durationText } : {}
|
|
2208
|
+
});
|
|
2209
|
+
continue;
|
|
2210
|
+
}
|
|
2211
|
+
if (event.eventType === eventTypes.timerFired) {
|
|
2212
|
+
recordTimerClose(event, timers, "timerFiredEventAttributes", "fired");
|
|
2213
|
+
continue;
|
|
2214
|
+
}
|
|
2215
|
+
if (event.eventType === eventTypes.timerCanceled) {
|
|
2216
|
+
recordTimerClose(event, timers, "timerCanceledEventAttributes", "canceled");
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
return [...timers.values()].toSorted((left, right) => left.started.eventId - right.started.eventId).map((timer) => ({
|
|
2220
|
+
id: `timer:${timer.timerId}:${timer.started.eventId}`,
|
|
2221
|
+
kind: "timer",
|
|
2222
|
+
timerId: timer.timerId,
|
|
2223
|
+
status: timer.closed?.status ?? "pending",
|
|
2224
|
+
startedAt: timer.started.eventTime,
|
|
2225
|
+
...timer.closed ? { closedAt: timer.closed.event.eventTime } : {},
|
|
2226
|
+
...timer.durationText ? { durationText: timer.durationText } : {},
|
|
2227
|
+
eventReferences: [
|
|
2228
|
+
{ eventId: timer.started.eventId, eventType: timer.started.eventTypeName },
|
|
2229
|
+
...timer.closed ? [{ eventId: timer.closed.event.eventId, eventType: timer.closed.event.eventTypeName }] : []
|
|
2230
|
+
]
|
|
2231
|
+
}));
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
// packages/history/src/update-operations.ts
|
|
2235
|
+
function recordAcceptedUpdate(event, updates, payloads, configuration) {
|
|
2236
|
+
const attributes = readRecordField(event.raw, "workflowExecutionUpdateAcceptedEventAttributes");
|
|
2237
|
+
const acceptedRequest = readRecordField(attributes ?? {}, "acceptedRequest");
|
|
2238
|
+
const meta = readRecordField(acceptedRequest ?? {}, "meta");
|
|
2239
|
+
const input = readRecordField(acceptedRequest ?? {}, "input");
|
|
2240
|
+
const updateId = readStringField(meta, "updateId") ?? String(event.eventId);
|
|
2241
|
+
const updateName = readStringField(input, "name") ?? "unknownUpdate";
|
|
2242
|
+
updates.set(updateId, {
|
|
2243
|
+
accepted: event,
|
|
2244
|
+
updateId,
|
|
2245
|
+
updateName,
|
|
2246
|
+
payloadReferences: collectPayloadIds(payloads, event, readRecordField(input ?? {}, "args"), "update", configuration)
|
|
2247
|
+
});
|
|
2248
|
+
}
|
|
2249
|
+
function recordCompletedUpdate(event, updates, payloads, configuration) {
|
|
2250
|
+
const attributes = readRecordField(event.raw, "workflowExecutionUpdateCompletedEventAttributes");
|
|
2251
|
+
const updateId = readStringField(readRecordField(attributes ?? {}, "meta"), "updateId");
|
|
2252
|
+
const update = updateId ? updates.get(updateId) : undefined;
|
|
2253
|
+
if (!update) {
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
const outcome = readRecordField(attributes ?? {}, "outcome");
|
|
2257
|
+
const success = readRecordField(outcome ?? {}, "success");
|
|
2258
|
+
const status = success ? "completed" : "failed";
|
|
2259
|
+
update.closed = { event, status };
|
|
2260
|
+
update.payloadReferences.push(...collectPayloadIds(payloads, event, success, status === "completed" ? "result" : "failure", configuration));
|
|
2261
|
+
}
|
|
2262
|
+
function createUpdateOperations(events, payloads, configuration) {
|
|
2263
|
+
const updates = new Map;
|
|
2264
|
+
for (const event of events) {
|
|
2265
|
+
if (event.eventType === eventTypes.workflowExecutionUpdateAccepted) {
|
|
2266
|
+
recordAcceptedUpdate(event, updates, payloads, configuration);
|
|
2267
|
+
} else if (event.eventType === eventTypes.workflowExecutionUpdateCompleted) {
|
|
2268
|
+
recordCompletedUpdate(event, updates, payloads, configuration);
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
return [...updates.values()].toSorted((left, right) => left.accepted.eventId - right.accepted.eventId).map((update) => ({
|
|
2272
|
+
id: `update:${update.updateName}:${update.accepted.eventId}`,
|
|
2273
|
+
kind: "update",
|
|
2274
|
+
updateId: update.updateId,
|
|
2275
|
+
updateName: update.updateName,
|
|
2276
|
+
status: update.closed?.status ?? "accepted",
|
|
2277
|
+
acceptedAt: update.accepted.eventTime,
|
|
2278
|
+
...update.closed ? { closedAt: update.closed.event.eventTime } : {},
|
|
2279
|
+
eventReferences: [
|
|
2280
|
+
createEventReference(update.accepted),
|
|
2281
|
+
...update.closed ? [createEventReference(update.closed.event)] : []
|
|
2282
|
+
],
|
|
2283
|
+
payloadReferences: update.payloadReferences
|
|
2284
|
+
}));
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
// packages/history/src/trace-builder.ts
|
|
2288
|
+
function createMetadata(options) {
|
|
2289
|
+
return {
|
|
2290
|
+
temporalExplorerVersion: "0.0.0-mvp",
|
|
2291
|
+
schemaVersion: "temporal-trace/v1",
|
|
2292
|
+
inputs: {
|
|
2293
|
+
projectRoot: options.projectRoot ?? "",
|
|
2294
|
+
configHash: options.historyHash ?? "",
|
|
2295
|
+
sourceFileHashes: {},
|
|
2296
|
+
temporalSdkVersions: options.provenance?.temporalSdkVersion ? { "@temporalio/workflow": options.provenance.temporalSdkVersion } : {}
|
|
2297
|
+
}
|
|
2298
|
+
};
|
|
2299
|
+
}
|
|
2300
|
+
function createSource(lifecycle, eventCount, importedFrom) {
|
|
2301
|
+
const taskQueue = readStringField(readRecordField(lifecycle.workflowStartedAttributes ?? {}, "taskQueue"), "name");
|
|
2302
|
+
return {
|
|
2303
|
+
...taskQueue ? { taskQueue } : {},
|
|
2304
|
+
eventCount,
|
|
2305
|
+
importedFrom: importedFrom ?? "file"
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
function parseEventHistory(options) {
|
|
2309
|
+
const events = parseEventHistoryEvents(options.history);
|
|
2310
|
+
const payloads = [];
|
|
2311
|
+
const configuration = options.payloadPreview ?? {};
|
|
2312
|
+
const lifecycle = createWorkflowLifecycle(events, payloads, configuration, options.provenance);
|
|
2313
|
+
const detailOperations = [
|
|
2314
|
+
...createActivityOperations(events, payloads, configuration),
|
|
2315
|
+
...createSignalOperations(events, payloads, configuration),
|
|
2316
|
+
...createTimerOperations(events),
|
|
2317
|
+
...createUpdateOperations(events, payloads, configuration),
|
|
2318
|
+
...createChildWorkflowOperations(events, payloads, configuration),
|
|
2319
|
+
...createExternalSignalOperations(events, payloads, configuration),
|
|
2320
|
+
...createMarkerOperations(events),
|
|
2321
|
+
...createCancelRequestOperations(events),
|
|
2322
|
+
...createContinueAsNewOperations(events, payloads, configuration)
|
|
2323
|
+
];
|
|
2324
|
+
return {
|
|
2325
|
+
schemaVersion: "temporal-trace/v1",
|
|
2326
|
+
artifactId: `trace:${options.traceId ?? lifecycle.workflowType}:${lifecycle.runId}`,
|
|
2327
|
+
metadata: createMetadata(options),
|
|
2328
|
+
execution: createWorkflowExecution(lifecycle),
|
|
2329
|
+
source: createSource(lifecycle, events.length, options.importedFrom),
|
|
2330
|
+
operations: [...lifecycle.operations, ...detailOperations],
|
|
2331
|
+
timeline: createTimeline(lifecycle.workflowStartedEvent, lifecycle.workflowClosedEvent, detailOperations),
|
|
2332
|
+
payloads,
|
|
2333
|
+
diagnostics: createUnknownEventDiagnostics(events)
|
|
2334
|
+
};
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
// packages/history/src/live.ts
|
|
2338
|
+
import { Client, Connection } from "@temporalio/client";
|
|
2339
|
+
|
|
2340
|
+
// packages/history/src/proto-history.ts
|
|
2341
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
2342
|
+
function isLongLike(value) {
|
|
2343
|
+
const constructorName = value.constructor?.name;
|
|
2344
|
+
return constructorName === "Long";
|
|
2345
|
+
}
|
|
2346
|
+
function isProtoTimestamp(value) {
|
|
2347
|
+
return "seconds" in value && !("metadata" in value) && Object.keys(value).length <= 2;
|
|
2348
|
+
}
|
|
2349
|
+
function toNumeric(value) {
|
|
2350
|
+
if (typeof value === "number") {
|
|
2351
|
+
return value;
|
|
2352
|
+
}
|
|
2353
|
+
if (typeof value === "string") {
|
|
2354
|
+
return Number(value);
|
|
2355
|
+
}
|
|
2356
|
+
if (value && typeof value === "object" && isLongLike(value)) {
|
|
2357
|
+
return Number(value.toString());
|
|
2358
|
+
}
|
|
2359
|
+
return 0;
|
|
2360
|
+
}
|
|
2361
|
+
function toIsoTime(value) {
|
|
2362
|
+
const seconds = toNumeric(value["seconds"]);
|
|
2363
|
+
const nanos = toNumeric(value["nanos"]);
|
|
2364
|
+
return new Date(seconds * 1000 + Math.floor(nanos / 1e6)).toISOString();
|
|
2365
|
+
}
|
|
2366
|
+
function convertRecord(value, key) {
|
|
2367
|
+
if (key === "eventTime" && isProtoTimestamp(value)) {
|
|
2368
|
+
return toIsoTime(value);
|
|
2369
|
+
}
|
|
2370
|
+
const converted = {};
|
|
2371
|
+
for (const [childKey, childValue] of Object.entries(value)) {
|
|
2372
|
+
if (childValue !== undefined && childValue !== null) {
|
|
2373
|
+
converted[childKey] = convertValue(childValue, childKey);
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
return converted;
|
|
2377
|
+
}
|
|
2378
|
+
function convertValue(value, key) {
|
|
2379
|
+
if (value instanceof Uint8Array) {
|
|
2380
|
+
return Buffer2.from(value).toString("base64");
|
|
2381
|
+
}
|
|
2382
|
+
if (value && typeof value === "object" && isLongLike(value)) {
|
|
2383
|
+
return value.toString();
|
|
2384
|
+
}
|
|
2385
|
+
if (Array.isArray(value)) {
|
|
2386
|
+
return value.map((item) => convertValue(item));
|
|
2387
|
+
}
|
|
2388
|
+
if (isRecord2(value)) {
|
|
2389
|
+
return convertRecord(value, key);
|
|
2390
|
+
}
|
|
2391
|
+
return value;
|
|
2392
|
+
}
|
|
2393
|
+
function convertProtoHistory(history) {
|
|
2394
|
+
return convertValue(history);
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
// packages/history/src/live.ts
|
|
2398
|
+
async function createLiveClient(options) {
|
|
2399
|
+
const namespace = options.namespace ?? "default";
|
|
2400
|
+
const connection = await Connection.connect({
|
|
2401
|
+
address: options.address,
|
|
2402
|
+
...options.apiKey ? { apiKey: options.apiKey } : {},
|
|
2403
|
+
...options.tls?.enabled ? {
|
|
2404
|
+
tls: options.tls.serverNameOverride ? { serverNameOverride: options.tls.serverNameOverride } : true
|
|
2405
|
+
} : {}
|
|
2406
|
+
});
|
|
2407
|
+
return {
|
|
2408
|
+
client: new Client({ connection, namespace }),
|
|
2409
|
+
namespace,
|
|
2410
|
+
close: async () => {
|
|
2411
|
+
await connection.close();
|
|
2412
|
+
}
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
function buildListQuery(options) {
|
|
2416
|
+
if (options.query) {
|
|
2417
|
+
return options.query;
|
|
2418
|
+
}
|
|
2419
|
+
const clauses = [];
|
|
2420
|
+
if (options.workflowType) {
|
|
2421
|
+
clauses.push(`WorkflowType='${options.workflowType}'`);
|
|
2422
|
+
}
|
|
2423
|
+
if (options.workflowId) {
|
|
2424
|
+
clauses.push(`WorkflowId='${options.workflowId}'`);
|
|
2425
|
+
}
|
|
2426
|
+
if (options.status) {
|
|
2427
|
+
clauses.push(`ExecutionStatus='${options.status}'`);
|
|
2428
|
+
}
|
|
2429
|
+
return clauses.length > 0 ? clauses.join(" AND ") : undefined;
|
|
2430
|
+
}
|
|
2431
|
+
async function listWorkflowRuns(options) {
|
|
2432
|
+
const query = buildListQuery(options);
|
|
2433
|
+
const limit = options.limit ?? 50;
|
|
2434
|
+
const runs = [];
|
|
2435
|
+
for await (const execution of options.client.workflow.list(query ? { query } : {})) {
|
|
2436
|
+
runs.push({
|
|
2437
|
+
workflowId: execution.workflowId,
|
|
2438
|
+
runId: execution.runId,
|
|
2439
|
+
workflowType: execution.type,
|
|
2440
|
+
status: execution.status.name,
|
|
2441
|
+
...execution.startTime ? { startedAt: execution.startTime.toISOString() } : {},
|
|
2442
|
+
...execution.closeTime ? { closedAt: execution.closeTime.toISOString() } : {},
|
|
2443
|
+
...execution.taskQueue ? { taskQueue: execution.taskQueue } : {}
|
|
2444
|
+
});
|
|
2445
|
+
if (runs.length >= limit) {
|
|
2446
|
+
break;
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
return runs;
|
|
2450
|
+
}
|
|
2451
|
+
async function fetchEventHistory(options) {
|
|
2452
|
+
const handle = options.client.workflow.getHandle(options.workflowId, options.runId);
|
|
2453
|
+
const description = await handle.describe();
|
|
2454
|
+
const history = convertProtoHistory(await handle.fetchHistory());
|
|
2455
|
+
const { client: _client, workflowId, runId, ...parseOptions } = options;
|
|
2456
|
+
const trace = parseEventHistory({
|
|
2457
|
+
...parseOptions,
|
|
2458
|
+
history,
|
|
2459
|
+
importedFrom: "api",
|
|
2460
|
+
traceId: options.traceId ?? `${workflowId}.${description.runId}`,
|
|
2461
|
+
provenance: options.provenance ?? {
|
|
2462
|
+
workflowId,
|
|
2463
|
+
workflowType: description.type
|
|
2464
|
+
}
|
|
2465
|
+
});
|
|
2466
|
+
return { trace, runId: description.runId };
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
// packages/history/src/index.ts
|
|
2470
|
+
async function hashFile2(path) {
|
|
2471
|
+
const bytes = await Bun.file(path).bytes();
|
|
2472
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
2473
|
+
}
|
|
2474
|
+
function toProjectPath2(root, path) {
|
|
2475
|
+
return root ? relative2(root, path) || "." : path;
|
|
2476
|
+
}
|
|
2477
|
+
function getProvenancePath(historyPath) {
|
|
2478
|
+
const historyName = basename2(historyPath, ".json");
|
|
2479
|
+
return join(dirname(historyPath), `${historyName}.provenance.json`);
|
|
2480
|
+
}
|
|
2481
|
+
function readHistoryProvenance(value) {
|
|
2482
|
+
if (!isRecord2(value)) {
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
const workflowId = readStringField(value, "workflowId");
|
|
2486
|
+
const workflowType = readStringField(value, "workflowType");
|
|
2487
|
+
const temporalSdkVersion = readStringField(value, "temporalSdkVersion");
|
|
2488
|
+
const provenance = {};
|
|
2489
|
+
if (workflowId) {
|
|
2490
|
+
provenance.workflowId = workflowId;
|
|
2491
|
+
}
|
|
2492
|
+
if (workflowType) {
|
|
2493
|
+
provenance.workflowType = workflowType;
|
|
2494
|
+
}
|
|
2495
|
+
if (temporalSdkVersion) {
|
|
2496
|
+
provenance.temporalSdkVersion = temporalSdkVersion;
|
|
2497
|
+
}
|
|
2498
|
+
return provenance;
|
|
2499
|
+
}
|
|
2500
|
+
async function readOptionalProvenance(historyPath) {
|
|
2501
|
+
const provenanceFile = Bun.file(getProvenancePath(historyPath));
|
|
2502
|
+
if (!await provenanceFile.exists()) {
|
|
2503
|
+
return;
|
|
2504
|
+
}
|
|
2505
|
+
return readHistoryProvenance(await provenanceFile.json());
|
|
2506
|
+
}
|
|
2507
|
+
async function importEventHistoryFile(options) {
|
|
2508
|
+
const historyPath = resolve4(options.file);
|
|
2509
|
+
const projectRoot = options.projectRoot ? resolve4(options.projectRoot) : undefined;
|
|
2510
|
+
const history = await Bun.file(historyPath).json();
|
|
2511
|
+
const provenance = options.provenance ?? await readOptionalProvenance(historyPath);
|
|
2512
|
+
const parseOptions = {
|
|
2513
|
+
history,
|
|
2514
|
+
historyPath: toProjectPath2(projectRoot, historyPath),
|
|
2515
|
+
historyHash: options.historyHash ?? await hashFile2(historyPath),
|
|
2516
|
+
traceId: options.traceId ?? basename2(historyPath, ".json")
|
|
2517
|
+
};
|
|
2518
|
+
if (options.importedFrom) {
|
|
2519
|
+
parseOptions.importedFrom = options.importedFrom;
|
|
2520
|
+
}
|
|
2521
|
+
if (options.payloadPreview) {
|
|
2522
|
+
parseOptions.payloadPreview = options.payloadPreview;
|
|
2523
|
+
}
|
|
2524
|
+
if (projectRoot) {
|
|
2525
|
+
parseOptions.projectRoot = basename2(projectRoot);
|
|
2526
|
+
}
|
|
2527
|
+
if (provenance) {
|
|
2528
|
+
parseOptions.provenance = provenance;
|
|
2529
|
+
}
|
|
2530
|
+
return parseEventHistory(parseOptions);
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
// packages/mapper/src/construct-mappings.ts
|
|
2534
|
+
function getEventIds(operation) {
|
|
2535
|
+
return operation.eventReferences.map((reference) => reference.eventId);
|
|
2536
|
+
}
|
|
2537
|
+
function createUnmappedMapping(operation, reason) {
|
|
2538
|
+
return {
|
|
2539
|
+
runtimeOperationId: operation.id,
|
|
2540
|
+
confidence: "unknown",
|
|
2541
|
+
reason,
|
|
2542
|
+
evidence: [
|
|
2543
|
+
{
|
|
2544
|
+
kind: "unmapped",
|
|
2545
|
+
description: reason,
|
|
2546
|
+
eventIds: getEventIds(operation)
|
|
2547
|
+
}
|
|
2548
|
+
]
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
function createCommandMapping(operation, command, confidence, reason, evidenceKind, evidenceDescription) {
|
|
2552
|
+
return {
|
|
2553
|
+
runtimeOperationId: operation.id,
|
|
2554
|
+
staticNodeId: command.id,
|
|
2555
|
+
confidence,
|
|
2556
|
+
reason,
|
|
2557
|
+
evidence: [
|
|
2558
|
+
{
|
|
2559
|
+
kind: evidenceKind,
|
|
2560
|
+
description: evidenceDescription,
|
|
2561
|
+
eventIds: getEventIds(operation),
|
|
2562
|
+
staticNodeId: command.id
|
|
2563
|
+
},
|
|
2564
|
+
{
|
|
2565
|
+
kind: "source-location",
|
|
2566
|
+
description: `Static source is ${command.source.path}:${command.source.start.line}.`,
|
|
2567
|
+
staticNodeId: command.id
|
|
2568
|
+
}
|
|
2569
|
+
]
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
function mapUpdateOperation(operation, updates) {
|
|
2573
|
+
const update = updates.find((candidate) => candidate.name === operation.updateName);
|
|
2574
|
+
if (!update) {
|
|
2575
|
+
return createUnmappedMapping(operation, `No static Update definition matched ${operation.updateName}.`);
|
|
2576
|
+
}
|
|
2577
|
+
return {
|
|
2578
|
+
runtimeOperationId: operation.id,
|
|
2579
|
+
staticNodeId: update.id,
|
|
2580
|
+
confidence: "exact",
|
|
2581
|
+
reason: `Update ${operation.updateName} matched a static Update definition by name.`,
|
|
2582
|
+
evidence: [
|
|
2583
|
+
{
|
|
2584
|
+
kind: "update-name",
|
|
2585
|
+
description: `Runtime Update ${operation.updateName} matched static Update ${update.name}.`,
|
|
2586
|
+
eventIds: getEventIds(operation),
|
|
2587
|
+
staticNodeId: update.id
|
|
2588
|
+
},
|
|
2589
|
+
{
|
|
2590
|
+
kind: "source-location",
|
|
2591
|
+
description: `Static Update source is ${update.source.path}:${update.source.start.line}.`,
|
|
2592
|
+
staticNodeId: update.id
|
|
2593
|
+
}
|
|
2594
|
+
]
|
|
2595
|
+
};
|
|
2596
|
+
}
|
|
2597
|
+
function mapChildWorkflowOperation(operation, childCommands, occurrences) {
|
|
2598
|
+
const occurrence = occurrences.get(operation.workflowType) ?? 0;
|
|
2599
|
+
occurrences.set(operation.workflowType, occurrence + 1);
|
|
2600
|
+
const command = childCommands.filter((candidate) => candidate.name === operation.workflowType)[occurrence];
|
|
2601
|
+
if (!command) {
|
|
2602
|
+
return createUnmappedMapping(operation, `No static child Workflow command matched ${operation.workflowType} occurrence ${occurrence + 1}.`);
|
|
2603
|
+
}
|
|
2604
|
+
return createCommandMapping(operation, command, "exact", `Child Workflow ${operation.workflowType} matched by Workflow type and command order.`, "child-workflow-type", `Runtime child Workflow type ${operation.workflowType} matched static command ${command.id}.`);
|
|
2605
|
+
}
|
|
2606
|
+
function mapExternalSignalOperation(operation, externalCommands, occurrences) {
|
|
2607
|
+
const occurrence = occurrences.get(operation.signalName) ?? 0;
|
|
2608
|
+
occurrences.set(operation.signalName, occurrence + 1);
|
|
2609
|
+
const command = externalCommands.filter((candidate) => candidate.name === operation.signalName)[occurrence];
|
|
2610
|
+
if (!command) {
|
|
2611
|
+
return createUnmappedMapping(operation, `No static external Workflow command matched signal ${operation.signalName}.`);
|
|
2612
|
+
}
|
|
2613
|
+
return createCommandMapping(operation, command, "exact", `External signal ${operation.signalName} matched by signal name.`, "external-signal-name", `Runtime external signal ${operation.signalName} matched static command ${command.id}.`);
|
|
2614
|
+
}
|
|
2615
|
+
function mapMarkerOperation(operation, patchCommands) {
|
|
2616
|
+
if (!operation.patchId) {
|
|
2617
|
+
return createUnmappedMapping(operation, `Marker ${operation.markerName} has no static counterpart.`);
|
|
2618
|
+
}
|
|
2619
|
+
const command = patchCommands.find((candidate) => candidate.name === operation.patchId);
|
|
2620
|
+
if (!command) {
|
|
2621
|
+
return createUnmappedMapping(operation, `No static patch matched patch ID ${operation.patchId}.`);
|
|
2622
|
+
}
|
|
2623
|
+
return createCommandMapping(operation, command, "exact", `Patch marker ${operation.patchId} matched a static patch call by patch ID.`, "patch-id", `Runtime patch marker ${operation.patchId} matched static patch ${command.id}.`);
|
|
2624
|
+
}
|
|
2625
|
+
function mapContinueAsNewOperation(operation, continueAsNewCommands) {
|
|
2626
|
+
const [command] = continueAsNewCommands;
|
|
2627
|
+
if (!command) {
|
|
2628
|
+
return createUnmappedMapping(operation, "No static continue-as-new call matched the runtime rollover.");
|
|
2629
|
+
}
|
|
2630
|
+
return createCommandMapping(operation, command, continueAsNewCommands.length === 1 ? "exact" : "inferred", "Continue-as-new matched the static continue-as-new call.", "continue-as-new", `Runtime continue-as-new matched static command ${command.id}.`);
|
|
2631
|
+
}
|
|
2632
|
+
function mapCancelRequestOperation(operation, workflow) {
|
|
2633
|
+
return {
|
|
2634
|
+
runtimeOperationId: operation.id,
|
|
2635
|
+
staticNodeId: workflow.id,
|
|
2636
|
+
confidence: "exact",
|
|
2637
|
+
reason: "Cancellation was requested for the Workflow Execution.",
|
|
2638
|
+
evidence: [
|
|
2639
|
+
{
|
|
2640
|
+
kind: "workflow-type",
|
|
2641
|
+
description: `Cancellation request belongs to Workflow ${workflow.name}.`,
|
|
2642
|
+
eventIds: getEventIds(operation),
|
|
2643
|
+
staticNodeId: workflow.id
|
|
2644
|
+
}
|
|
2645
|
+
]
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2648
|
+
function mapDynamicActivityFallback(operation, dynamicCommands) {
|
|
2649
|
+
const [command] = dynamicCommands;
|
|
2650
|
+
if (!command) {
|
|
2651
|
+
return;
|
|
2652
|
+
}
|
|
2653
|
+
return createCommandMapping(operation, command, "dynamic", `Activity ${operation.activityType} was dispatched dynamically; the static call site cannot name it.`, "dynamic-dispatch", `Runtime Activity ${operation.activityType} attributed to dynamic dispatch site ${command.name}.`);
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
// packages/mapper/src/mappings.ts
|
|
2657
|
+
function getEventIds2(operation) {
|
|
2658
|
+
return operation.eventReferences.map((reference) => reference.eventId);
|
|
2659
|
+
}
|
|
2660
|
+
function getCommandsOfKind(workflow, kind) {
|
|
2661
|
+
return workflow.temporalCommands.filter((command) => command.kind === kind).toSorted((left, right) => left.staticOrder - right.staticOrder);
|
|
2662
|
+
}
|
|
2663
|
+
function createWorkflowMapping(operation, workflow, trace) {
|
|
2664
|
+
return {
|
|
2665
|
+
runtimeOperationId: operation.id,
|
|
2666
|
+
staticNodeId: workflow.id,
|
|
2667
|
+
confidence: "exact",
|
|
2668
|
+
reason: `Workflow lifecycle event belongs to ${trace.execution.workflowType}.`,
|
|
2669
|
+
evidence: [
|
|
2670
|
+
{
|
|
2671
|
+
kind: "workflow-type",
|
|
2672
|
+
description: `Runtime Workflow type ${trace.execution.workflowType} matched static Workflow ${workflow.name}.`,
|
|
2673
|
+
eventIds: getEventIds2(operation),
|
|
2674
|
+
staticNodeId: workflow.id
|
|
2675
|
+
},
|
|
2676
|
+
{
|
|
2677
|
+
kind: "source-location",
|
|
2678
|
+
description: `Static Workflow source is ${workflow.source.path}:${workflow.source.start.line}.`,
|
|
2679
|
+
staticNodeId: workflow.id
|
|
2680
|
+
}
|
|
2681
|
+
]
|
|
2682
|
+
};
|
|
2683
|
+
}
|
|
2684
|
+
function createActivityMapping(operation, command, occurrence) {
|
|
2685
|
+
return {
|
|
2686
|
+
runtimeOperationId: operation.id,
|
|
2687
|
+
staticNodeId: command.id,
|
|
2688
|
+
confidence: "exact",
|
|
2689
|
+
reason: `Activity ${operation.activityType} matched by Activity type and command order.`,
|
|
2690
|
+
evidence: [
|
|
2691
|
+
{
|
|
2692
|
+
kind: "activity-type",
|
|
2693
|
+
description: `Runtime Activity type ${operation.activityType} matched static Activity command ${command.name}.`,
|
|
2694
|
+
eventIds: getEventIds2(operation),
|
|
2695
|
+
staticNodeId: command.id
|
|
2696
|
+
},
|
|
2697
|
+
{
|
|
2698
|
+
kind: "command-order",
|
|
2699
|
+
description: `Runtime Activity occurrence ${occurrence + 1} matched static command order ${command.staticOrder}.`,
|
|
2700
|
+
eventIds: getEventIds2(operation),
|
|
2701
|
+
staticNodeId: command.id
|
|
2702
|
+
},
|
|
2703
|
+
{
|
|
2704
|
+
kind: "source-location",
|
|
2705
|
+
description: `Static Activity source is ${command.source.path}:${command.source.start.line}.`,
|
|
2706
|
+
staticNodeId: command.id
|
|
2707
|
+
}
|
|
2708
|
+
]
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2711
|
+
function createSignalMapping(operation, signal) {
|
|
2712
|
+
return {
|
|
2713
|
+
runtimeOperationId: operation.id,
|
|
2714
|
+
staticNodeId: signal.id,
|
|
2715
|
+
confidence: "exact",
|
|
2716
|
+
reason: `Signal ${operation.signalName} matched a static Signal definition by name.`,
|
|
2717
|
+
evidence: [
|
|
2718
|
+
{
|
|
2719
|
+
kind: "signal-name",
|
|
2720
|
+
description: `Runtime Signal ${operation.signalName} matched static Signal ${signal.name}.`,
|
|
2721
|
+
eventIds: getEventIds2(operation),
|
|
2722
|
+
staticNodeId: signal.id
|
|
2723
|
+
},
|
|
2724
|
+
{
|
|
2725
|
+
kind: "source-location",
|
|
2726
|
+
description: `Static Signal source is ${signal.source.path}:${signal.source.start.line}.`,
|
|
2727
|
+
staticNodeId: signal.id
|
|
2728
|
+
}
|
|
2729
|
+
]
|
|
2730
|
+
};
|
|
2731
|
+
}
|
|
2732
|
+
function createTimerMapping(operation, command, occurrence, confidence) {
|
|
2733
|
+
return {
|
|
2734
|
+
runtimeOperationId: operation.id,
|
|
2735
|
+
staticNodeId: command.id,
|
|
2736
|
+
confidence,
|
|
2737
|
+
reason: confidence === "exact" ? "The only runtime timer matched the only static timer." : `Runtime timer occurrence ${occurrence + 1} matched static timer order ${command.staticOrder}.`,
|
|
2738
|
+
evidence: [
|
|
2739
|
+
{
|
|
2740
|
+
kind: "timer-order",
|
|
2741
|
+
description: `Runtime timer ${operation.timerId} matched static timer command ${command.id} by start order.`,
|
|
2742
|
+
eventIds: getEventIds2(operation),
|
|
2743
|
+
staticNodeId: command.id
|
|
2744
|
+
},
|
|
2745
|
+
{
|
|
2746
|
+
kind: "source-location",
|
|
2747
|
+
description: `Static timer source is ${command.source.path}:${command.source.start.line}.`,
|
|
2748
|
+
staticNodeId: command.id
|
|
2749
|
+
}
|
|
2750
|
+
]
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
function mapActivityOperation(operation, activityCommands, dynamicCommands, occurrences) {
|
|
2754
|
+
const occurrence = occurrences.get(operation.activityType) ?? 0;
|
|
2755
|
+
occurrences.set(operation.activityType, occurrence + 1);
|
|
2756
|
+
const command = activityCommands.filter((candidate) => candidate.name === operation.activityType)[occurrence];
|
|
2757
|
+
if (command) {
|
|
2758
|
+
return createActivityMapping(operation, command, occurrence);
|
|
2759
|
+
}
|
|
2760
|
+
return mapDynamicActivityFallback(operation, dynamicCommands) ?? createUnmappedMapping(operation, `No static Activity command matched ${operation.activityType} occurrence ${occurrence + 1}.`);
|
|
2761
|
+
}
|
|
2762
|
+
function mapSignalOperation(operation, signals) {
|
|
2763
|
+
const signal = signals.find((candidate) => candidate.name === operation.signalName);
|
|
2764
|
+
return signal ? createSignalMapping(operation, signal) : createUnmappedMapping(operation, `No static Signal definition matched ${operation.signalName}.`);
|
|
2765
|
+
}
|
|
2766
|
+
function mapTimerOperation(operation, timerCommands, occurrence, runtimeTimerCount) {
|
|
2767
|
+
const command = timerCommands[occurrence];
|
|
2768
|
+
if (!command) {
|
|
2769
|
+
return createUnmappedMapping(operation, `No static timer command matched runtime timer occurrence ${occurrence + 1}.`);
|
|
2770
|
+
}
|
|
2771
|
+
const confidence = timerCommands.length === 1 && runtimeTimerCount === 1 ? "exact" : "inferred";
|
|
2772
|
+
return createTimerMapping(operation, command, occurrence, confidence);
|
|
2773
|
+
}
|
|
2774
|
+
function mapCoreOperation(operation, context) {
|
|
2775
|
+
switch (operation.kind) {
|
|
2776
|
+
case "workflow-lifecycle":
|
|
2777
|
+
return createWorkflowMapping(operation, context.workflow, context.trace);
|
|
2778
|
+
case "activity":
|
|
2779
|
+
return mapActivityOperation(operation, context.activityCommands, context.dynamicCommands, context.activityOccurrences);
|
|
2780
|
+
case "signal":
|
|
2781
|
+
return mapSignalOperation(operation, context.workflow.messageSurface.signals);
|
|
2782
|
+
case "timer": {
|
|
2783
|
+
const mapping = mapTimerOperation(operation, context.timerCommands, context.timerOccurrence, context.runtimeTimerCount);
|
|
2784
|
+
context.timerOccurrence += 1;
|
|
2785
|
+
return mapping;
|
|
2786
|
+
}
|
|
2787
|
+
default:
|
|
2788
|
+
return;
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
function mapExtendedOperation(operation, context) {
|
|
2792
|
+
switch (operation.kind) {
|
|
2793
|
+
case "update":
|
|
2794
|
+
return mapUpdateOperation(operation, context.workflow.messageSurface.updates);
|
|
2795
|
+
case "child-workflow":
|
|
2796
|
+
return mapChildWorkflowOperation(operation, context.childCommands, context.childOccurrences);
|
|
2797
|
+
case "external-signal":
|
|
2798
|
+
return mapExternalSignalOperation(operation, context.externalCommands, context.externalOccurrences);
|
|
2799
|
+
case "marker":
|
|
2800
|
+
return mapMarkerOperation(operation, context.patchCommands);
|
|
2801
|
+
case "continue-as-new":
|
|
2802
|
+
return mapContinueAsNewOperation(operation, context.continueAsNewCommands);
|
|
2803
|
+
case "cancel-request":
|
|
2804
|
+
return mapCancelRequestOperation(operation, context.workflow);
|
|
2805
|
+
default:
|
|
2806
|
+
return createUnmappedMapping(operation, `Runtime operation ${operation.id} is not supported yet.`);
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
function mapOperation(operation, context) {
|
|
2810
|
+
return mapCoreOperation(operation, context) ?? mapExtendedOperation(operation, context);
|
|
2811
|
+
}
|
|
2812
|
+
function createMappings(workflow, trace) {
|
|
2813
|
+
const context = {
|
|
2814
|
+
workflow,
|
|
2815
|
+
trace,
|
|
2816
|
+
activityCommands: getCommandsOfKind(workflow, "activity"),
|
|
2817
|
+
timerCommands: getCommandsOfKind(workflow, "timer"),
|
|
2818
|
+
dynamicCommands: getCommandsOfKind(workflow, "dynamic"),
|
|
2819
|
+
childCommands: getCommandsOfKind(workflow, "child-workflow"),
|
|
2820
|
+
externalCommands: getCommandsOfKind(workflow, "external-workflow"),
|
|
2821
|
+
patchCommands: getCommandsOfKind(workflow, "patch"),
|
|
2822
|
+
continueAsNewCommands: getCommandsOfKind(workflow, "continue-as-new"),
|
|
2823
|
+
activityOccurrences: new Map,
|
|
2824
|
+
childOccurrences: new Map,
|
|
2825
|
+
externalOccurrences: new Map,
|
|
2826
|
+
runtimeTimerCount: trace.operations.filter((operation) => operation.kind === "timer").length,
|
|
2827
|
+
timerOccurrence: 0
|
|
2828
|
+
};
|
|
2829
|
+
return trace.operations.map((operation) => mapOperation(operation, context));
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
// packages/mapper/src/coverage.ts
|
|
2833
|
+
var commandNodeKinds = [
|
|
2834
|
+
"activity",
|
|
2835
|
+
"timer",
|
|
2836
|
+
"condition",
|
|
2837
|
+
"child-workflow",
|
|
2838
|
+
"external-workflow",
|
|
2839
|
+
"continue-as-new",
|
|
2840
|
+
"patch",
|
|
2841
|
+
"cancellation-scope",
|
|
2842
|
+
"dynamic"
|
|
2843
|
+
];
|
|
2844
|
+
var messageNodeKinds = [
|
|
2845
|
+
{ surface: "signals", kind: "signal" },
|
|
2846
|
+
{ surface: "queries", kind: "query" },
|
|
2847
|
+
{ surface: "updates", kind: "update" }
|
|
2848
|
+
];
|
|
2849
|
+
function markObservedScopes(nodes) {
|
|
2850
|
+
const observedSources = nodes.filter((node) => node.observed && node.kind !== "cancellation-scope" && node.source).map((node) => node.source);
|
|
2851
|
+
for (const node of nodes) {
|
|
2852
|
+
if (node.kind !== "cancellation-scope" || node.observed || !node.source) {
|
|
2853
|
+
continue;
|
|
2854
|
+
}
|
|
2855
|
+
const scope = node.source;
|
|
2856
|
+
node.observed = observedSources.some((candidate) => candidate !== undefined && candidate.path === scope.path && candidate.start.offset >= scope.start.offset && candidate.end.offset <= scope.end.offset);
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
function createStaticNodes(workflow, mappings) {
|
|
2860
|
+
const observed = new Set(mappings.flatMap((mapping) => mapping.staticNodeId ?? []));
|
|
2861
|
+
const nodes = [
|
|
2862
|
+
{
|
|
2863
|
+
id: workflow.id,
|
|
2864
|
+
kind: "workflow",
|
|
2865
|
+
name: workflow.name,
|
|
2866
|
+
observed: observed.has(workflow.id),
|
|
2867
|
+
source: workflow.source
|
|
2868
|
+
}
|
|
2869
|
+
];
|
|
2870
|
+
for (const kind of commandNodeKinds) {
|
|
2871
|
+
for (const command of getCommandsOfKind(workflow, kind)) {
|
|
2872
|
+
nodes.push({
|
|
2873
|
+
id: command.id,
|
|
2874
|
+
kind,
|
|
2875
|
+
name: command.name,
|
|
2876
|
+
observed: observed.has(command.id),
|
|
2877
|
+
source: command.source
|
|
2878
|
+
});
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
for (const { surface, kind } of messageNodeKinds) {
|
|
2882
|
+
for (const message of workflow.messageSurface[surface]) {
|
|
2883
|
+
nodes.push({
|
|
2884
|
+
id: message.id,
|
|
2885
|
+
kind,
|
|
2886
|
+
name: message.name,
|
|
2887
|
+
observed: observed.has(message.id),
|
|
2888
|
+
source: message.source
|
|
2889
|
+
});
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
markObservedScopes(nodes);
|
|
2893
|
+
return nodes;
|
|
2894
|
+
}
|
|
2895
|
+
function createDiagnostics(mappings) {
|
|
2896
|
+
return mappings.filter((mapping) => !mapping.staticNodeId).map((mapping) => ({
|
|
2897
|
+
code: "TEM_UNMAPPED_RUNTIME_OPERATION",
|
|
2898
|
+
category: "mapping",
|
|
2899
|
+
severity: "warning",
|
|
2900
|
+
message: mapping.reason,
|
|
2901
|
+
confidence: mapping.confidence
|
|
2902
|
+
}));
|
|
2903
|
+
}
|
|
2904
|
+
function isActivityOperation(operation) {
|
|
2905
|
+
return operation.kind === "activity";
|
|
2906
|
+
}
|
|
2907
|
+
function createMessageCoverage(workflow, trace) {
|
|
2908
|
+
const receivedSignals = [
|
|
2909
|
+
...new Set(trace.operations.filter((operation) => operation.kind === "signal").map((operation) => operation.signalName))
|
|
2910
|
+
].toSorted((left, right) => left.localeCompare(right));
|
|
2911
|
+
const receivedUpdates = [
|
|
2912
|
+
...new Set(trace.operations.filter((operation) => operation.kind === "update").map((operation) => operation.updateName))
|
|
2913
|
+
].toSorted((left, right) => left.localeCompare(right));
|
|
2914
|
+
return {
|
|
2915
|
+
staticSignals: workflow.messageSurface.signals.length,
|
|
2916
|
+
receivedSignals,
|
|
2917
|
+
staticUpdates: workflow.messageSurface.updates.length,
|
|
2918
|
+
receivedUpdates,
|
|
2919
|
+
staticQueries: workflow.messageSurface.queries.length
|
|
2920
|
+
};
|
|
2921
|
+
}
|
|
2922
|
+
function createTimerCoverage(workflow, trace) {
|
|
2923
|
+
const timerOperations = trace.operations.filter((operation) => operation.kind === "timer");
|
|
2924
|
+
return {
|
|
2925
|
+
staticTotal: getCommandsOfKind(workflow, "timer").length,
|
|
2926
|
+
fired: timerOperations.filter((operation) => operation.status === "fired").length,
|
|
2927
|
+
canceled: timerOperations.filter((operation) => operation.status === "canceled").length,
|
|
2928
|
+
pending: timerOperations.filter((operation) => operation.status === "pending").length
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
function createCoverage(workflow, staticNodes, mappings, trace) {
|
|
2932
|
+
const activityOperations = trace.operations.filter(isActivityOperation);
|
|
2933
|
+
const observedActivityIds = new Set(staticNodes.filter((node) => node.kind === "activity" && node.observed).map((node) => node.id));
|
|
2934
|
+
return {
|
|
2935
|
+
nodes: {
|
|
2936
|
+
total: staticNodes.length,
|
|
2937
|
+
observed: staticNodes.filter((node) => node.observed).length,
|
|
2938
|
+
skipped: staticNodes.filter((node) => !node.observed).length,
|
|
2939
|
+
unmappedRuntimeOperations: mappings.filter((mapping) => !mapping.staticNodeId).length
|
|
2940
|
+
},
|
|
2941
|
+
activities: {
|
|
2942
|
+
staticTotal: staticNodes.filter((node) => node.kind === "activity").length,
|
|
2943
|
+
observed: observedActivityIds.size,
|
|
2944
|
+
retried: activityOperations.filter((operation) => operation.attempts.length > 1 || operation.attempts.some((attempt) => attempt.attempt > 1)).length,
|
|
2945
|
+
failed: activityOperations.filter((operation) => operation.status !== "completed").length
|
|
2946
|
+
},
|
|
2947
|
+
messages: createMessageCoverage(workflow, trace),
|
|
2948
|
+
timers: createTimerCoverage(workflow, trace)
|
|
2949
|
+
};
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
// packages/mapper/src/aggregate.ts
|
|
2953
|
+
function countBy(values) {
|
|
2954
|
+
const counts = {};
|
|
2955
|
+
for (const value of values) {
|
|
2956
|
+
counts[value] = (counts[value] ?? 0) + 1;
|
|
2957
|
+
}
|
|
2958
|
+
return Object.fromEntries(Object.entries(counts).toSorted(([left], [right]) => left.localeCompare(right)));
|
|
2959
|
+
}
|
|
2960
|
+
function aggregateActivities(traces) {
|
|
2961
|
+
const stats = new Map;
|
|
2962
|
+
for (const trace of traces) {
|
|
2963
|
+
for (const operation of trace.operations) {
|
|
2964
|
+
if (operation.kind !== "activity") {
|
|
2965
|
+
continue;
|
|
2966
|
+
}
|
|
2967
|
+
const entry = stats.get(operation.activityType) ?? {
|
|
2968
|
+
activityType: operation.activityType,
|
|
2969
|
+
executions: 0,
|
|
2970
|
+
retried: 0,
|
|
2971
|
+
failed: 0
|
|
2972
|
+
};
|
|
2973
|
+
entry.executions += 1;
|
|
2974
|
+
if (operation.attempts.length > 1 || operation.attempts.some((attempt) => attempt.attempt > 1)) {
|
|
2975
|
+
entry.retried += 1;
|
|
2976
|
+
}
|
|
2977
|
+
if (operation.status !== "completed") {
|
|
2978
|
+
entry.failed += 1;
|
|
2979
|
+
}
|
|
2980
|
+
stats.set(operation.activityType, entry);
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
return [...stats.values()].toSorted((left, right) => right.retried - left.retried || right.failed - left.failed || left.activityType.localeCompare(right.activityType));
|
|
2984
|
+
}
|
|
2985
|
+
function aggregateNodes(overlays) {
|
|
2986
|
+
const nodes = new Map;
|
|
2987
|
+
for (const overlay of overlays) {
|
|
2988
|
+
for (const node of overlay.staticNodes) {
|
|
2989
|
+
const entry = nodes.get(node.id) ?? {
|
|
2990
|
+
nodeId: node.id,
|
|
2991
|
+
kind: node.kind,
|
|
2992
|
+
name: node.name,
|
|
2993
|
+
observedRuns: 0,
|
|
2994
|
+
totalRuns: 0
|
|
2995
|
+
};
|
|
2996
|
+
entry.totalRuns += 1;
|
|
2997
|
+
if (node.observed) {
|
|
2998
|
+
entry.observedRuns += 1;
|
|
2999
|
+
}
|
|
3000
|
+
nodes.set(node.id, entry);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
return [...nodes.values()].toSorted((left, right) => left.nodeId.localeCompare(right.nodeId));
|
|
3004
|
+
}
|
|
3005
|
+
function aggregateTimers(traces) {
|
|
3006
|
+
const timers = { fired: 0, canceled: 0, pending: 0 };
|
|
3007
|
+
for (const trace of traces) {
|
|
3008
|
+
for (const operation of trace.operations) {
|
|
3009
|
+
if (operation.kind === "timer") {
|
|
3010
|
+
timers[operation.status] += 1;
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
return timers;
|
|
3015
|
+
}
|
|
3016
|
+
function createAggregateReport(options) {
|
|
3017
|
+
const traces = options.traces.filter((trace) => trace.execution.workflowType === options.workflowType);
|
|
3018
|
+
const overlays = options.overlays.filter((overlay) => overlay.workflow === options.workflowType);
|
|
3019
|
+
if (traces.length === 0) {
|
|
3020
|
+
throw new Error(`No trace artifacts matched Workflow type "${options.workflowType}".`);
|
|
3021
|
+
}
|
|
3022
|
+
const nodes = aggregateNodes(overlays);
|
|
3023
|
+
return {
|
|
3024
|
+
workflowType: options.workflowType,
|
|
3025
|
+
runs: traces.length,
|
|
3026
|
+
statuses: countBy(traces.map((trace) => trace.execution.status)),
|
|
3027
|
+
activities: aggregateActivities(traces),
|
|
3028
|
+
signals: countBy(traces.flatMap((trace) => trace.operations.flatMap((operation) => operation.kind === "signal" ? [operation.signalName] : []))),
|
|
3029
|
+
updates: countBy(traces.flatMap((trace) => trace.operations.flatMap((operation) => operation.kind === "update" ? [operation.updateName] : []))),
|
|
3030
|
+
timers: aggregateTimers(traces),
|
|
3031
|
+
hotPathNodes: nodes.filter((node) => node.totalRuns > 0 && node.observedRuns === node.totalRuns),
|
|
3032
|
+
rareBranchNodes: nodes.filter((node) => node.observedRuns > 0 && node.observedRuns * 2 <= node.totalRuns),
|
|
3033
|
+
nodes
|
|
3034
|
+
};
|
|
3035
|
+
}
|
|
3036
|
+
function formatAggregateReport(report) {
|
|
3037
|
+
const lines = [
|
|
3038
|
+
`Aggregate Report: ${report.workflowType}`,
|
|
3039
|
+
"",
|
|
3040
|
+
`Runs: ${report.runs}`,
|
|
3041
|
+
`Statuses: ${Object.entries(report.statuses).map(([status, count]) => `${status}=${count}`).join(", ")}`,
|
|
3042
|
+
"",
|
|
3043
|
+
"Activities (by retries, failures)"
|
|
3044
|
+
];
|
|
3045
|
+
for (const activity of report.activities) {
|
|
3046
|
+
lines.push(` ${activity.activityType}: ${activity.executions} execution(s), ${activity.retried} retried, ${activity.failed} failed`);
|
|
3047
|
+
}
|
|
3048
|
+
if (Object.keys(report.signals).length > 0) {
|
|
3049
|
+
lines.push("", "Signals received");
|
|
3050
|
+
for (const [name, count] of Object.entries(report.signals)) {
|
|
3051
|
+
lines.push(` ${name}: ${count}`);
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
if (Object.keys(report.updates).length > 0) {
|
|
3055
|
+
lines.push("", "Updates received");
|
|
3056
|
+
for (const [name, count] of Object.entries(report.updates)) {
|
|
3057
|
+
lines.push(` ${name}: ${count}`);
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
lines.push("", `Timers: ${report.timers.fired} fired, ${report.timers.canceled} canceled, ${report.timers.pending} pending`);
|
|
3061
|
+
lines.push("", "Path coverage");
|
|
3062
|
+
for (const node of report.nodes) {
|
|
3063
|
+
lines.push(` ${node.kind} ${node.name}: observed in ${node.observedRuns}/${node.totalRuns} run(s)`);
|
|
3064
|
+
}
|
|
3065
|
+
if (report.rareBranchNodes.length > 0) {
|
|
3066
|
+
lines.push("", "Rare branches (observed in at most half of runs)");
|
|
3067
|
+
for (const node of report.rareBranchNodes) {
|
|
3068
|
+
lines.push(` ${node.kind} ${node.name}: ${node.observedRuns}/${node.totalRuns}`);
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
return `${lines.join(`
|
|
3072
|
+
`)}
|
|
3073
|
+
`;
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
// packages/mapper/src/index.ts
|
|
3077
|
+
function getWorkflow(analysis, workflowName) {
|
|
3078
|
+
const workflow = analysis.workflows.find((candidate) => candidate.name === workflowName);
|
|
3079
|
+
if (!workflow) {
|
|
3080
|
+
throw new Error(`Workflow "${workflowName}" was not found in static analysis.`);
|
|
3081
|
+
}
|
|
3082
|
+
return workflow;
|
|
3083
|
+
}
|
|
3084
|
+
function applyReplayEvidence(mappings, trace, replayCapture) {
|
|
3085
|
+
const capturedActivities = replayCapture.filter((command) => command.kind === "activity").toSorted((left, right) => left.sequence - right.sequence);
|
|
3086
|
+
const activityOperations = trace.operations.filter((operation) => operation.kind === "activity").toSorted((left, right) => (left.eventReferences[0]?.eventId ?? 0) - (right.eventReferences[0]?.eventId ?? 0));
|
|
3087
|
+
activityOperations.forEach((operation, index) => {
|
|
3088
|
+
const captured = capturedActivities[index];
|
|
3089
|
+
if (!captured || operation.kind !== "activity" || captured.name !== operation.activityType) {
|
|
3090
|
+
return;
|
|
3091
|
+
}
|
|
3092
|
+
const mapping = mappings.find((entry) => entry.runtimeOperationId === operation.id);
|
|
3093
|
+
if (mapping?.confidence === "dynamic") {
|
|
3094
|
+
mapping.confidence = "inferred";
|
|
3095
|
+
mapping.reason = `${mapping.reason} Replay confirmed ${captured.name} at command sequence ${captured.sequence}.`;
|
|
3096
|
+
mapping.evidence.push({
|
|
3097
|
+
kind: "replay-command-sequence",
|
|
3098
|
+
description: `Replay observed Activity ${captured.name} scheduled at command sequence ${captured.sequence}.`,
|
|
3099
|
+
staticNodeId: mapping.staticNodeId ?? undefined
|
|
3100
|
+
});
|
|
3101
|
+
}
|
|
3102
|
+
});
|
|
3103
|
+
}
|
|
3104
|
+
function createExecutionOverlay(options) {
|
|
3105
|
+
const workflow = getWorkflow(options.analysis, options.workflowName);
|
|
3106
|
+
const mappings = createMappings(workflow, options.trace);
|
|
3107
|
+
if (options.replayCapture) {
|
|
3108
|
+
applyReplayEvidence(mappings, options.trace, options.replayCapture);
|
|
3109
|
+
}
|
|
3110
|
+
const staticNodes = createStaticNodes(workflow, mappings);
|
|
3111
|
+
return {
|
|
3112
|
+
schemaVersion: "temporal-overlay/v1",
|
|
3113
|
+
artifactId: `overlay:${options.workflowName}:${options.trace.artifactId}`,
|
|
3114
|
+
staticAnalysisId: options.analysis.artifactId,
|
|
3115
|
+
runtimeTraceId: options.trace.artifactId,
|
|
3116
|
+
workflow: options.workflowName,
|
|
3117
|
+
staticNodes,
|
|
3118
|
+
mappings,
|
|
3119
|
+
branchOutcomes: [],
|
|
3120
|
+
coverage: createCoverage(workflow, staticNodes, mappings, options.trace),
|
|
3121
|
+
diagnostics: createDiagnostics(mappings)
|
|
3122
|
+
};
|
|
3123
|
+
}
|
|
3124
|
+
function formatSource(node) {
|
|
3125
|
+
return node.source ? `${node.source.path}:${node.source.start.line}` : "unknown source";
|
|
3126
|
+
}
|
|
3127
|
+
function getMappingForNode(overlay, node) {
|
|
3128
|
+
return overlay.mappings.find((mapping) => mapping.staticNodeId === node.id);
|
|
3129
|
+
}
|
|
3130
|
+
function appendNodeSection(lines, overlay, kind, heading) {
|
|
3131
|
+
const nodes = overlay.staticNodes.filter((candidate) => candidate.kind === kind);
|
|
3132
|
+
if (nodes.length === 0) {
|
|
3133
|
+
return;
|
|
3134
|
+
}
|
|
3135
|
+
lines.push("", heading);
|
|
3136
|
+
for (const node of nodes) {
|
|
3137
|
+
const mapping = getMappingForNode(overlay, node);
|
|
3138
|
+
const state = node.observed ? "observed" : "not observed";
|
|
3139
|
+
const confidence = mapping?.confidence ?? "unknown";
|
|
3140
|
+
lines.push(` ${node.name} (${state}) -> ${formatSource(node)} [${confidence}]`);
|
|
3141
|
+
if (mapping) {
|
|
3142
|
+
lines.push(` ${mapping.reason}`);
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
function createOverlayReport(overlay) {
|
|
3147
|
+
const lines = [
|
|
3148
|
+
`Trace Report: ${overlay.workflow}`,
|
|
3149
|
+
"",
|
|
3150
|
+
`Mapped runtime operations: ${overlay.mappings.length - overlay.coverage.nodes.unmappedRuntimeOperations}/${overlay.mappings.length}`,
|
|
3151
|
+
`Unmapped runtime operations: ${overlay.coverage.nodes.unmappedRuntimeOperations}`,
|
|
3152
|
+
"",
|
|
3153
|
+
"Executed Activities"
|
|
3154
|
+
];
|
|
3155
|
+
for (const node of overlay.staticNodes.filter((candidate) => candidate.kind === "activity")) {
|
|
3156
|
+
const mapping = getMappingForNode(overlay, node);
|
|
3157
|
+
const state = node.observed ? "observed" : "not observed";
|
|
3158
|
+
const confidence = mapping?.confidence ?? "unknown";
|
|
3159
|
+
lines.push(` ${node.name} (${state}) -> ${formatSource(node)} [${confidence}]`);
|
|
3160
|
+
if (mapping) {
|
|
3161
|
+
lines.push(` ${mapping.reason}`);
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
appendNodeSection(lines, overlay, "signal", "Signals");
|
|
3165
|
+
appendNodeSection(lines, overlay, "timer", "Timers");
|
|
3166
|
+
appendNodeSection(lines, overlay, "condition", "Conditions");
|
|
3167
|
+
return `${lines.join(`
|
|
3168
|
+
`)}
|
|
3169
|
+
`;
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
// packages/renderers/src/shared.ts
|
|
3173
|
+
function getWorkflow2(analysis, workflowName) {
|
|
3174
|
+
const workflow = analysis.workflows.find((candidate) => candidate.name === workflowName);
|
|
3175
|
+
if (!workflow) {
|
|
3176
|
+
throw new Error(`Workflow "${workflowName}" was not found.`);
|
|
3177
|
+
}
|
|
3178
|
+
return workflow;
|
|
3179
|
+
}
|
|
3180
|
+
function sortWorkflows(workflows) {
|
|
3181
|
+
return workflows.toSorted((left, right) => left.name.localeCompare(right.name));
|
|
3182
|
+
}
|
|
3183
|
+
function getCommandsOfKind2(workflow, kind) {
|
|
3184
|
+
return workflow.temporalCommands.filter((command) => command.kind === kind).toSorted((left, right) => left.staticOrder - right.staticOrder);
|
|
3185
|
+
}
|
|
3186
|
+
function formatSource2(value) {
|
|
3187
|
+
return `${value.source.path}:${value.source.start.line}`;
|
|
3188
|
+
}
|
|
3189
|
+
function getOverlayForWorkflow(workflow, overlays) {
|
|
3190
|
+
return overlays.find((overlay) => overlay.workflow === workflow.name);
|
|
3191
|
+
}
|
|
3192
|
+
function isObserved(nodeId, overlay) {
|
|
3193
|
+
return Boolean(overlay?.staticNodes.some((node) => node.id === nodeId && node.observed));
|
|
3194
|
+
}
|
|
3195
|
+
function getMappingConfidence(nodeId, overlay) {
|
|
3196
|
+
return overlay?.mappings.find((mapping) => mapping.staticNodeId === nodeId)?.confidence ?? "unknown";
|
|
3197
|
+
}
|
|
3198
|
+
function formatWorkflowSignature(workflow) {
|
|
3199
|
+
const args = workflow.signature.args.map((arg) => `${arg.displayName ?? "arg"}: ${arg.display}`).join(", ");
|
|
3200
|
+
return `${workflow.name}(${args}): ${workflow.signature.result.display}`;
|
|
3201
|
+
}
|
|
3202
|
+
function createAlignedMarkdownTable(headers, alignments, rows) {
|
|
3203
|
+
const tableRows = [headers, ...rows];
|
|
3204
|
+
const widths = headers.map((_, columnIndex) => Math.max(...tableRows.map((row) => row[columnIndex]?.length ?? 0)));
|
|
3205
|
+
const separator = widths.map((width, columnIndex) => {
|
|
3206
|
+
const dashCount = Math.max(width - (alignments[columnIndex] === "right" ? 1 : 0), 3);
|
|
3207
|
+
return alignments[columnIndex] === "right" ? `${"-".repeat(dashCount)}:` : "-".repeat(dashCount);
|
|
3208
|
+
});
|
|
3209
|
+
return [
|
|
3210
|
+
formatAlignedTableRow(headers, widths, alignments),
|
|
3211
|
+
formatAlignedTableRow(separator, widths, alignments),
|
|
3212
|
+
...rows.map((row) => formatAlignedTableRow(row, widths, alignments))
|
|
3213
|
+
];
|
|
3214
|
+
}
|
|
3215
|
+
function formatAlignedTableRow(cells, widths, alignments) {
|
|
3216
|
+
return `| ${cells.map((cell, index) => alignments[index] === "right" ? cell.padStart(widths[index] ?? 0) : cell.padEnd(widths[index] ?? 0)).join(" | ")} |`;
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
// packages/renderers/src/markdown.ts
|
|
3220
|
+
function renderWorkflowIndexMarkdown(options) {
|
|
3221
|
+
const lines = ["# Temporal Workflow Explorer", "", "## Workflows", ""];
|
|
3222
|
+
for (const workflow of sortWorkflows(options.analysis.workflows)) {
|
|
3223
|
+
lines.push(`- [${workflow.name}](./${workflow.name}.md) - \`${formatSource2(workflow)}\``);
|
|
3224
|
+
}
|
|
3225
|
+
lines.push("", "## Artifacts", "");
|
|
3226
|
+
lines.push(`- Static analysis: \`${options.analysis.artifactId}\``);
|
|
3227
|
+
for (const trace of options.traces ?? []) {
|
|
3228
|
+
lines.push(`- Runtime trace: \`${trace.artifactId}\` (${trace.execution.status})`);
|
|
3229
|
+
}
|
|
3230
|
+
for (const overlay of options.overlays ?? []) {
|
|
3231
|
+
lines.push(`- Execution overlay: \`${overlay.artifactId}\``);
|
|
3232
|
+
}
|
|
3233
|
+
return `${lines.join(`
|
|
3234
|
+
`)}
|
|
3235
|
+
`;
|
|
3236
|
+
}
|
|
3237
|
+
function appendActivitiesSection(lines, workflow, overlay) {
|
|
3238
|
+
const commands = getCommandsOfKind2(workflow, "activity");
|
|
3239
|
+
lines.push("## Activities", "");
|
|
3240
|
+
if (commands.length === 0) {
|
|
3241
|
+
lines.push("- none");
|
|
3242
|
+
return;
|
|
3243
|
+
}
|
|
3244
|
+
lines.push(...createAlignedMarkdownTable(["Order", "Activity", "Source", "Observed", "Confidence"], ["right", "left", "left", "left", "left"], commands.map((command) => [
|
|
3245
|
+
String(command.staticOrder + 1),
|
|
3246
|
+
`\`${command.name}\``,
|
|
3247
|
+
`\`${formatSource2(command)}\``,
|
|
3248
|
+
isObserved(command.id, overlay) ? "yes" : "no",
|
|
3249
|
+
getMappingConfidence(command.id, overlay)
|
|
3250
|
+
])));
|
|
3251
|
+
}
|
|
3252
|
+
function collectMessageRows(workflow, overlay) {
|
|
3253
|
+
const formatPayload = (args) => args.map((arg) => `\`${arg.display}\``).join(", ") || "none";
|
|
3254
|
+
return [
|
|
3255
|
+
...workflow.messageSurface.signals.map((signal) => ({
|
|
3256
|
+
kind: "Signal",
|
|
3257
|
+
id: signal.id,
|
|
3258
|
+
name: signal.name,
|
|
3259
|
+
payload: formatPayload(signal.args),
|
|
3260
|
+
source: formatSource2(signal),
|
|
3261
|
+
received: isObserved(signal.id, overlay) ? "yes" : "no"
|
|
3262
|
+
})),
|
|
3263
|
+
...workflow.messageSurface.queries.map((query) => ({
|
|
3264
|
+
kind: "Query",
|
|
3265
|
+
id: query.id,
|
|
3266
|
+
name: query.name,
|
|
3267
|
+
payload: formatPayload(query.args),
|
|
3268
|
+
source: formatSource2(query),
|
|
3269
|
+
received: "not recorded"
|
|
3270
|
+
})),
|
|
3271
|
+
...workflow.messageSurface.updates.map((update) => ({
|
|
3272
|
+
kind: "Update",
|
|
3273
|
+
id: update.id,
|
|
3274
|
+
name: update.name,
|
|
3275
|
+
payload: formatPayload(update.args),
|
|
3276
|
+
source: formatSource2(update),
|
|
3277
|
+
received: isObserved(update.id, overlay) ? "yes" : "no"
|
|
3278
|
+
}))
|
|
3279
|
+
];
|
|
3280
|
+
}
|
|
3281
|
+
function appendMessagesSection(lines, workflow, overlay) {
|
|
3282
|
+
lines.push("", "## Messages", "");
|
|
3283
|
+
const rows = collectMessageRows(workflow, overlay);
|
|
3284
|
+
if (rows.length === 0) {
|
|
3285
|
+
lines.push("- none");
|
|
3286
|
+
return;
|
|
3287
|
+
}
|
|
3288
|
+
lines.push(...createAlignedMarkdownTable(["Kind", "Name", "Payload", "Source", "Received", "Confidence"], ["left", "left", "left", "left", "left", "left"], rows.map((row) => [
|
|
3289
|
+
row.kind,
|
|
3290
|
+
`\`${row.name}\``,
|
|
3291
|
+
row.payload,
|
|
3292
|
+
`\`${row.source}\``,
|
|
3293
|
+
row.received,
|
|
3294
|
+
row.kind === "Query" ? "exact" : getMappingConfidence(row.id, overlay)
|
|
3295
|
+
])));
|
|
3296
|
+
if (workflow.messageSurface.queries.length > 0) {
|
|
3297
|
+
lines.push("", "Queries are served from Workflow state and do not normally add events to Event History.");
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
var operationCommandKinds = [
|
|
3301
|
+
"child-workflow",
|
|
3302
|
+
"external-workflow",
|
|
3303
|
+
"continue-as-new",
|
|
3304
|
+
"patch",
|
|
3305
|
+
"cancellation-scope",
|
|
3306
|
+
"dynamic"
|
|
3307
|
+
];
|
|
3308
|
+
function appendOperationsSection(lines, workflow, overlay) {
|
|
3309
|
+
const operations = operationCommandKinds.flatMap((kind) => getCommandsOfKind2(workflow, kind)).toSorted((left, right) => left.staticOrder - right.staticOrder);
|
|
3310
|
+
lines.push("", "## Temporal Operations", "");
|
|
3311
|
+
if (operations.length === 0) {
|
|
3312
|
+
lines.push("- none");
|
|
3313
|
+
return;
|
|
3314
|
+
}
|
|
3315
|
+
lines.push(...createAlignedMarkdownTable(["Order", "Kind", "Target", "Source", "Observed", "Confidence"], ["right", "left", "left", "left", "left", "left"], operations.map((command) => [
|
|
3316
|
+
String(command.staticOrder + 1),
|
|
3317
|
+
command.kind,
|
|
3318
|
+
`\`${command.name}\``,
|
|
3319
|
+
`\`${formatSource2(command)}\``,
|
|
3320
|
+
isObserved(command.id, overlay) ? "yes" : "no",
|
|
3321
|
+
getMappingConfidence(command.id, overlay)
|
|
3322
|
+
])));
|
|
3323
|
+
}
|
|
3324
|
+
function appendWaitsSection(lines, workflow, overlay) {
|
|
3325
|
+
lines.push("", "## Waits", "");
|
|
3326
|
+
const waits = [
|
|
3327
|
+
...getCommandsOfKind2(workflow, "condition"),
|
|
3328
|
+
...getCommandsOfKind2(workflow, "timer")
|
|
3329
|
+
].toSorted((left, right) => left.staticOrder - right.staticOrder);
|
|
3330
|
+
if (waits.length === 0) {
|
|
3331
|
+
lines.push("- none");
|
|
3332
|
+
return;
|
|
3333
|
+
}
|
|
3334
|
+
lines.push(...createAlignedMarkdownTable(["Order", "Kind", "Expression", "Source", "Observed", "Confidence"], ["right", "left", "left", "left", "left", "left"], waits.map((command) => [
|
|
3335
|
+
String(command.staticOrder + 1),
|
|
3336
|
+
command.kind,
|
|
3337
|
+
`\`${command.name}\``,
|
|
3338
|
+
`\`${formatSource2(command)}\``,
|
|
3339
|
+
isObserved(command.id, overlay) ? "yes" : "no",
|
|
3340
|
+
getMappingConfidence(command.id, overlay)
|
|
3341
|
+
])));
|
|
3342
|
+
}
|
|
3343
|
+
function appendRuntimeSummary(lines, overlay) {
|
|
3344
|
+
lines.push("", "## Runtime Summary", "");
|
|
3345
|
+
if (!overlay) {
|
|
3346
|
+
lines.push("- No runtime overlay artifact was provided.");
|
|
3347
|
+
return;
|
|
3348
|
+
}
|
|
3349
|
+
lines.push(`- Mapped runtime operations: ${overlay.mappings.length - overlay.coverage.nodes.unmappedRuntimeOperations}/${overlay.mappings.length}`);
|
|
3350
|
+
lines.push(`- Unmapped runtime operations: ${overlay.coverage.nodes.unmappedRuntimeOperations}`);
|
|
3351
|
+
if (overlay.coverage.messages.receivedSignals.length > 0) {
|
|
3352
|
+
lines.push(`- Received Signals: ${overlay.coverage.messages.receivedSignals.join(", ")}`);
|
|
3353
|
+
}
|
|
3354
|
+
if (overlay.coverage.timers.staticTotal > 0) {
|
|
3355
|
+
lines.push(`- Timers: ${overlay.coverage.timers.fired} fired, ${overlay.coverage.timers.canceled} canceled, ${overlay.coverage.timers.pending} pending`);
|
|
3356
|
+
}
|
|
3357
|
+
lines.push("- Payload previews: redacted by default");
|
|
3358
|
+
}
|
|
3359
|
+
function renderWorkflowMarkdown(options) {
|
|
3360
|
+
const workflow = getWorkflow2(options.analysis, options.workflowName);
|
|
3361
|
+
const overlay = getOverlayForWorkflow(workflow, options.overlays ?? []);
|
|
3362
|
+
const lines = [
|
|
3363
|
+
`# ${workflow.name}`,
|
|
3364
|
+
"",
|
|
3365
|
+
`Source: \`${formatSource2(workflow)}\``,
|
|
3366
|
+
"",
|
|
3367
|
+
`Signature: \`${formatWorkflowSignature(workflow)}\``,
|
|
3368
|
+
""
|
|
3369
|
+
];
|
|
3370
|
+
appendActivitiesSection(lines, workflow, overlay);
|
|
3371
|
+
appendMessagesSection(lines, workflow, overlay);
|
|
3372
|
+
appendWaitsSection(lines, workflow, overlay);
|
|
3373
|
+
appendOperationsSection(lines, workflow, overlay);
|
|
3374
|
+
appendRuntimeSummary(lines, overlay);
|
|
3375
|
+
lines.push("", "## Warnings", "");
|
|
3376
|
+
if (workflow.diagnostics.length === 0) {
|
|
3377
|
+
lines.push("- none");
|
|
3378
|
+
}
|
|
3379
|
+
for (const diagnostic of workflow.diagnostics) {
|
|
3380
|
+
lines.push(`- ${diagnostic.severity}: ${diagnostic.message}`);
|
|
3381
|
+
}
|
|
3382
|
+
return `${lines.join(`
|
|
3383
|
+
`)}
|
|
3384
|
+
`;
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
// packages/renderers/src/mermaid.ts
|
|
3388
|
+
function toMermaidId(value) {
|
|
3389
|
+
return value.replaceAll(/[^A-Za-z0-9_]/g, "_");
|
|
3390
|
+
}
|
|
3391
|
+
function toMermaidLabel(value) {
|
|
3392
|
+
return value.replaceAll('"', "'");
|
|
3393
|
+
}
|
|
3394
|
+
var flowCommandKinds = new Set([
|
|
3395
|
+
"activity",
|
|
3396
|
+
"timer",
|
|
3397
|
+
"condition",
|
|
3398
|
+
"child-workflow",
|
|
3399
|
+
"external-workflow",
|
|
3400
|
+
"continue-as-new",
|
|
3401
|
+
"patch",
|
|
3402
|
+
"dynamic"
|
|
3403
|
+
]);
|
|
3404
|
+
function renderCommandNode(command) {
|
|
3405
|
+
const id = toMermaidId(command.id);
|
|
3406
|
+
const label = toMermaidLabel(command.name);
|
|
3407
|
+
if (command.kind === "condition" || command.kind === "patch") {
|
|
3408
|
+
return ` ${id}{"${label}"}`;
|
|
3409
|
+
}
|
|
3410
|
+
if (command.kind === "timer") {
|
|
3411
|
+
return ` ${id}(("${label}"))`;
|
|
3412
|
+
}
|
|
3413
|
+
if (command.kind === "child-workflow" || command.kind === "external-workflow") {
|
|
3414
|
+
return ` ${id}[["${label}"]]`;
|
|
3415
|
+
}
|
|
3416
|
+
if (command.kind === "dynamic") {
|
|
3417
|
+
return ` ${id}[/"${label}"/]`;
|
|
3418
|
+
}
|
|
3419
|
+
return ` ${id}["${label}"]`;
|
|
3420
|
+
}
|
|
3421
|
+
function renderWorkflowMermaid(analysis, workflowName) {
|
|
3422
|
+
const workflow = getWorkflow2(analysis, workflowName);
|
|
3423
|
+
const commands = workflow.temporalCommands.filter((command) => flowCommandKinds.has(command.kind)).toSorted((left, right) => left.staticOrder - right.staticOrder);
|
|
3424
|
+
const lines = ["flowchart TD", ` start(["${workflow.name}"])`];
|
|
3425
|
+
for (const command of commands) {
|
|
3426
|
+
lines.push(renderCommandNode(command));
|
|
3427
|
+
}
|
|
3428
|
+
lines.push(' complete(["complete"])');
|
|
3429
|
+
const nodeIds = ["start", ...commands.map((command) => toMermaidId(command.id)), "complete"];
|
|
3430
|
+
for (let index = 0;index < nodeIds.length - 1; index += 1) {
|
|
3431
|
+
lines.push(` ${nodeIds[index]} --> ${nodeIds[index + 1]}`);
|
|
3432
|
+
}
|
|
3433
|
+
return `${lines.join(`
|
|
3434
|
+
`)}
|
|
3435
|
+
`;
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
// packages/renderers/src/declarations.ts
|
|
3439
|
+
import { dirname as dirname2, relative as relative3 } from "path";
|
|
3440
|
+
var declarationsDirectory = ".temporal-explorer/workflows";
|
|
3441
|
+
function toImportSpecifier(module) {
|
|
3442
|
+
const specifier = relative3(dirname2(`${declarationsDirectory}/placeholder.d.ts`), module).split("\\").join("/");
|
|
3443
|
+
return specifier.startsWith(".") ? specifier : `./${specifier}`;
|
|
3444
|
+
}
|
|
3445
|
+
function renderImports(workflow) {
|
|
3446
|
+
const byModule = new Map;
|
|
3447
|
+
for (const dependency of workflow.dependencies) {
|
|
3448
|
+
const names = byModule.get(dependency.module) ?? [];
|
|
3449
|
+
names.push(dependency.name);
|
|
3450
|
+
byModule.set(dependency.module, names);
|
|
3451
|
+
}
|
|
3452
|
+
return [...byModule.entries()].toSorted(([left], [right]) => left.localeCompare(right)).map(([module, names]) => `import type { ${names.toSorted((a, b) => a.localeCompare(b)).join(", ")} } from '${toImportSpecifier(module)}';`);
|
|
3453
|
+
}
|
|
3454
|
+
function knownTypeNames(workflow) {
|
|
3455
|
+
return new Set(workflow.dependencies.map((dependency) => dependency.name));
|
|
3456
|
+
}
|
|
3457
|
+
function renderTypeText(display, known) {
|
|
3458
|
+
return display.replaceAll(/\b[A-Z][A-Za-z0-9_]*\b/gu, (name) => {
|
|
3459
|
+
if (known.has(name) || structuralTypeNames.has(name)) {
|
|
3460
|
+
return name;
|
|
3461
|
+
}
|
|
3462
|
+
return "unknown";
|
|
3463
|
+
});
|
|
3464
|
+
}
|
|
3465
|
+
var structuralTypeNames = new Set([
|
|
3466
|
+
"Promise",
|
|
3467
|
+
"Array",
|
|
3468
|
+
"Record",
|
|
3469
|
+
"Map",
|
|
3470
|
+
"Set",
|
|
3471
|
+
"Date",
|
|
3472
|
+
"Partial",
|
|
3473
|
+
"Readonly",
|
|
3474
|
+
"Pick",
|
|
3475
|
+
"Omit"
|
|
3476
|
+
]);
|
|
3477
|
+
function renderSignatureLines(workflow, known) {
|
|
3478
|
+
const parameters = workflow.signature.args.map((arg, index) => `${arg.displayName ?? `arg${index}`}: ${renderTypeText(arg.display, known)}`).join(", ");
|
|
3479
|
+
const result = renderTypeText(workflow.signature.result.display, known);
|
|
3480
|
+
const resultType = result.startsWith("Promise<") ? result : `Promise<${result}>`;
|
|
3481
|
+
return [
|
|
3482
|
+
`/** Source: ${workflow.source.path}:${workflow.source.start.line} [confidence: exact] */`,
|
|
3483
|
+
`export declare function ${workflow.name}(${parameters}): ${resultType};`
|
|
3484
|
+
];
|
|
3485
|
+
}
|
|
3486
|
+
function renderMessageLines(workflow, known) {
|
|
3487
|
+
const lines = [];
|
|
3488
|
+
for (const signal of workflow.messageSurface.signals) {
|
|
3489
|
+
const args = signal.args.map((arg) => renderTypeText(arg.display, known)).join(", ");
|
|
3490
|
+
lines.push(`/** Source: ${signal.source.path}:${signal.source.start.line} [confidence: ${signal.confidence}] */`, `export declare const ${signal.name}Signal: import('@temporalio/workflow').SignalDefinition<[${args}]>;`);
|
|
3491
|
+
}
|
|
3492
|
+
for (const query of workflow.messageSurface.queries) {
|
|
3493
|
+
const args = query.args.map((arg) => renderTypeText(arg.display, known)).join(", ");
|
|
3494
|
+
const result = query.result ? renderTypeText(query.result.display, known) : "unknown";
|
|
3495
|
+
lines.push(`/** Source: ${query.source.path}:${query.source.start.line} [confidence: ${query.confidence}] */`, `export declare const ${query.name}Query: import('@temporalio/workflow').QueryDefinition<${result}, [${args}]>;`);
|
|
3496
|
+
}
|
|
3497
|
+
for (const update of workflow.messageSurface.updates) {
|
|
3498
|
+
const args = update.args.map((arg) => renderTypeText(arg.display, known)).join(", ");
|
|
3499
|
+
const result = update.result ? renderTypeText(update.result.display, known) : "unknown";
|
|
3500
|
+
lines.push(`/** Source: ${update.source.path}:${update.source.start.line} [confidence: ${update.confidence}] */`, `export declare const ${update.name}Update: import('@temporalio/workflow').UpdateDefinition<${result}, [${args}]>;`);
|
|
3501
|
+
}
|
|
3502
|
+
return lines;
|
|
3503
|
+
}
|
|
3504
|
+
function renderActivityLines(analysis, workflow) {
|
|
3505
|
+
const activityCommands = workflow.temporalCommands.filter((command) => command.kind === "activity");
|
|
3506
|
+
if (activityCommands.length === 0) {
|
|
3507
|
+
return [];
|
|
3508
|
+
}
|
|
3509
|
+
const names = [...new Set(activityCommands.map((command) => command.name))].toSorted((a, b) => a.localeCompare(b));
|
|
3510
|
+
const lines = ["/** Activity names referenced by this Workflow. */"];
|
|
3511
|
+
lines.push(`export type ${capitalize(workflow.name)}ActivityName = ${names.map((name) => `'${name}'`).join(" | ")};`);
|
|
3512
|
+
for (const name of names) {
|
|
3513
|
+
const activity = analysis.activities.find((candidate) => candidate.name === name);
|
|
3514
|
+
if (activity?.implementationSource) {
|
|
3515
|
+
lines.push(`/** Implementation: ${activity.implementationSource.path}:${activity.implementationSource.start.line} [confidence: ${activity.confidence}] */`);
|
|
3516
|
+
} else {
|
|
3517
|
+
lines.push(`/** Implementation unresolved [confidence: ${activity?.confidence ?? "unknown"}] */`);
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
return lines;
|
|
3521
|
+
}
|
|
3522
|
+
function capitalize(value) {
|
|
3523
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
3524
|
+
}
|
|
3525
|
+
function renderWorkflowDeclaration(analysis, workflowName) {
|
|
3526
|
+
const workflow = getWorkflow2(analysis, workflowName);
|
|
3527
|
+
const known = knownTypeNames(workflow);
|
|
3528
|
+
const sections = [
|
|
3529
|
+
["// Generated by Temporal Workflow Explorer. Do not edit."],
|
|
3530
|
+
renderImports(workflow),
|
|
3531
|
+
renderSignatureLines(workflow, known),
|
|
3532
|
+
renderMessageLines(workflow, known),
|
|
3533
|
+
renderActivityLines(analysis, workflow)
|
|
3534
|
+
].filter((section) => section.length > 0);
|
|
3535
|
+
return `${sections.map((section) => section.join(`
|
|
3536
|
+
`)).join(`
|
|
3537
|
+
|
|
3538
|
+
`)}
|
|
3539
|
+
`;
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
// packages/renderers/src/index.ts
|
|
3543
|
+
function createDocumentationSet(options) {
|
|
3544
|
+
const files = [
|
|
3545
|
+
{
|
|
3546
|
+
path: "index.md",
|
|
3547
|
+
contents: renderWorkflowIndexMarkdown(options)
|
|
3548
|
+
}
|
|
3549
|
+
];
|
|
3550
|
+
for (const workflow of sortWorkflows(options.analysis.workflows)) {
|
|
3551
|
+
files.push({
|
|
3552
|
+
path: `${workflow.name}.md`,
|
|
3553
|
+
contents: renderWorkflowMarkdown({ ...options, workflowName: workflow.name })
|
|
3554
|
+
});
|
|
3555
|
+
files.push({
|
|
3556
|
+
path: `${workflow.name}.mmd`,
|
|
3557
|
+
contents: renderWorkflowMermaid(options.analysis, workflow.name)
|
|
3558
|
+
});
|
|
3559
|
+
}
|
|
3560
|
+
return files.toSorted((left, right) => left.path.localeCompare(right.path));
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
// packages/schemas/src/temporal-analysis.ts
|
|
3564
|
+
import { z as z2 } from "zod";
|
|
3565
|
+
|
|
3566
|
+
// packages/schemas/src/common.ts
|
|
3567
|
+
import { z } from "zod";
|
|
3568
|
+
var confidenceSchema = z.union([
|
|
3569
|
+
z.literal("exact"),
|
|
3570
|
+
z.literal("inferred"),
|
|
3571
|
+
z.literal("partial"),
|
|
3572
|
+
z.literal("ambiguous"),
|
|
3573
|
+
z.literal("dynamic"),
|
|
3574
|
+
z.literal("unknown")
|
|
3575
|
+
]);
|
|
3576
|
+
var sourcePositionSchema = z.object({
|
|
3577
|
+
line: z.number().int().positive(),
|
|
3578
|
+
column: z.number().int().positive(),
|
|
3579
|
+
offset: z.number().int().nonnegative()
|
|
3580
|
+
}).strict();
|
|
3581
|
+
var sourceLocationSchema = z.object({
|
|
3582
|
+
path: z.string().min(1),
|
|
3583
|
+
pathKind: z.union([z.literal("project-relative"), z.literal("absolute")]),
|
|
3584
|
+
start: sourcePositionSchema,
|
|
3585
|
+
end: sourcePositionSchema,
|
|
3586
|
+
symbolName: z.string().min(1).optional()
|
|
3587
|
+
}).strict();
|
|
3588
|
+
var diagnosticSchema = z.object({
|
|
3589
|
+
code: z.string().min(1),
|
|
3590
|
+
category: z.union([
|
|
3591
|
+
z.literal("configuration"),
|
|
3592
|
+
z.literal("discovery"),
|
|
3593
|
+
z.literal("type-extraction"),
|
|
3594
|
+
z.literal("control-flow"),
|
|
3595
|
+
z.literal("determinism"),
|
|
3596
|
+
z.literal("history"),
|
|
3597
|
+
z.literal("mapping"),
|
|
3598
|
+
z.literal("rendering"),
|
|
3599
|
+
z.literal("privacy"),
|
|
3600
|
+
z.literal("compatibility")
|
|
3601
|
+
]),
|
|
3602
|
+
severity: z.union([z.literal("error"), z.literal("warning"), z.literal("info")]),
|
|
3603
|
+
message: z.string().min(1),
|
|
3604
|
+
source: sourceLocationSchema.optional(),
|
|
3605
|
+
relatedSources: z.array(sourceLocationSchema).optional(),
|
|
3606
|
+
confidence: confidenceSchema,
|
|
3607
|
+
documentationUrl: z.string().url().optional()
|
|
3608
|
+
}).strict();
|
|
3609
|
+
var artifactMetadataSchema = z.object({
|
|
3610
|
+
temporalExplorerVersion: z.string().min(1),
|
|
3611
|
+
schemaVersion: z.string().min(1),
|
|
3612
|
+
generatedAt: z.string().datetime().optional(),
|
|
3613
|
+
inputs: z.object({
|
|
3614
|
+
projectRoot: z.string(),
|
|
3615
|
+
configHash: z.string(),
|
|
3616
|
+
tsconfigHash: z.string().optional(),
|
|
3617
|
+
packageMetadataHash: z.string().optional(),
|
|
3618
|
+
lockfileHash: z.string().optional(),
|
|
3619
|
+
sourceFileHashes: z.record(z.string(), z.string()),
|
|
3620
|
+
temporalSdkVersions: z.record(z.string(), z.string())
|
|
3621
|
+
}).strict()
|
|
3622
|
+
}).strict();
|
|
3623
|
+
|
|
3624
|
+
// packages/schemas/src/temporal-analysis.ts
|
|
3625
|
+
var typeShapeSchema = z2.lazy(() => z2.object({
|
|
3626
|
+
id: z2.string().min(1),
|
|
3627
|
+
display: z2.string().min(1),
|
|
3628
|
+
displayName: z2.string().min(1).optional(),
|
|
3629
|
+
fullyQualifiedName: z2.string().min(1).optional(),
|
|
3630
|
+
kind: z2.union([
|
|
3631
|
+
z2.literal("primitive"),
|
|
3632
|
+
z2.literal("object"),
|
|
3633
|
+
z2.literal("array"),
|
|
3634
|
+
z2.literal("tuple"),
|
|
3635
|
+
z2.literal("union"),
|
|
3636
|
+
z2.literal("intersection"),
|
|
3637
|
+
z2.literal("literal"),
|
|
3638
|
+
z2.literal("enum"),
|
|
3639
|
+
z2.literal("function"),
|
|
3640
|
+
z2.literal("promise"),
|
|
3641
|
+
z2.literal("external"),
|
|
3642
|
+
z2.literal("unknown")
|
|
3643
|
+
]),
|
|
3644
|
+
nullable: z2.boolean().optional(),
|
|
3645
|
+
optional: z2.boolean().optional(),
|
|
3646
|
+
properties: z2.record(z2.string(), typeShapeSchema).optional(),
|
|
3647
|
+
items: typeShapeSchema.optional(),
|
|
3648
|
+
tupleItems: z2.array(typeShapeSchema).optional(),
|
|
3649
|
+
union: z2.array(typeShapeSchema).optional(),
|
|
3650
|
+
intersection: z2.array(typeShapeSchema).optional(),
|
|
3651
|
+
source: sourceLocationSchema.optional(),
|
|
3652
|
+
module: z2.string().min(1).optional(),
|
|
3653
|
+
declaration: sourceLocationSchema.optional(),
|
|
3654
|
+
typeArguments: z2.array(typeShapeSchema).optional(),
|
|
3655
|
+
references: z2.array(z2.string().min(1)).optional(),
|
|
3656
|
+
recursive: z2.boolean().optional(),
|
|
3657
|
+
confidence: confidenceSchema
|
|
3658
|
+
}).strict());
|
|
3659
|
+
var workflowSignatureSchema = z2.object({
|
|
3660
|
+
args: z2.array(typeShapeSchema),
|
|
3661
|
+
result: typeShapeSchema
|
|
3662
|
+
}).strict();
|
|
3663
|
+
var temporalCommandSchema = z2.object({
|
|
3664
|
+
id: z2.string().min(1),
|
|
3665
|
+
kind: z2.union([
|
|
3666
|
+
z2.literal("activity"),
|
|
3667
|
+
z2.literal("workflow-lifecycle"),
|
|
3668
|
+
z2.literal("timer"),
|
|
3669
|
+
z2.literal("condition"),
|
|
3670
|
+
z2.literal("signal"),
|
|
3671
|
+
z2.literal("query"),
|
|
3672
|
+
z2.literal("update"),
|
|
3673
|
+
z2.literal("child-workflow"),
|
|
3674
|
+
z2.literal("external-workflow"),
|
|
3675
|
+
z2.literal("continue-as-new"),
|
|
3676
|
+
z2.literal("patch"),
|
|
3677
|
+
z2.literal("cancellation-scope"),
|
|
3678
|
+
z2.literal("dynamic")
|
|
3679
|
+
]),
|
|
3680
|
+
name: z2.string().min(1),
|
|
3681
|
+
source: sourceLocationSchema,
|
|
3682
|
+
confidence: confidenceSchema,
|
|
3683
|
+
staticOrder: z2.number().int().nonnegative()
|
|
3684
|
+
}).strict();
|
|
3685
|
+
var signalDefinitionSchema = z2.object({
|
|
3686
|
+
id: z2.string().min(1),
|
|
3687
|
+
name: z2.string().min(1),
|
|
3688
|
+
source: sourceLocationSchema,
|
|
3689
|
+
args: z2.array(typeShapeSchema),
|
|
3690
|
+
handlerSource: sourceLocationSchema.optional(),
|
|
3691
|
+
confidence: confidenceSchema
|
|
3692
|
+
}).strict();
|
|
3693
|
+
var queryDefinitionSchema = z2.object({
|
|
3694
|
+
id: z2.string().min(1),
|
|
3695
|
+
name: z2.string().min(1),
|
|
3696
|
+
source: sourceLocationSchema,
|
|
3697
|
+
args: z2.array(typeShapeSchema),
|
|
3698
|
+
result: typeShapeSchema.optional(),
|
|
3699
|
+
handlerSource: sourceLocationSchema.optional(),
|
|
3700
|
+
confidence: confidenceSchema
|
|
3701
|
+
}).strict();
|
|
3702
|
+
var updateDefinitionSchema = z2.object({
|
|
3703
|
+
id: z2.string().min(1),
|
|
3704
|
+
name: z2.string().min(1),
|
|
3705
|
+
source: sourceLocationSchema,
|
|
3706
|
+
args: z2.array(typeShapeSchema),
|
|
3707
|
+
result: typeShapeSchema.optional(),
|
|
3708
|
+
handlerSource: sourceLocationSchema.optional(),
|
|
3709
|
+
validatorSource: sourceLocationSchema.optional(),
|
|
3710
|
+
confidence: confidenceSchema
|
|
3711
|
+
}).strict();
|
|
3712
|
+
var workflowDependencySchema = z2.object({
|
|
3713
|
+
kind: z2.literal("type-import"),
|
|
3714
|
+
name: z2.string().min(1),
|
|
3715
|
+
module: z2.string().min(1)
|
|
3716
|
+
}).strict();
|
|
3717
|
+
var workflowDefinitionSchema = z2.object({
|
|
3718
|
+
id: z2.string().min(1),
|
|
3719
|
+
name: z2.string().min(1),
|
|
3720
|
+
source: sourceLocationSchema,
|
|
3721
|
+
exported: z2.boolean(),
|
|
3722
|
+
signature: workflowSignatureSchema,
|
|
3723
|
+
messageSurface: z2.object({
|
|
3724
|
+
signals: z2.array(signalDefinitionSchema),
|
|
3725
|
+
queries: z2.array(queryDefinitionSchema),
|
|
3726
|
+
updates: z2.array(updateDefinitionSchema)
|
|
3727
|
+
}).strict(),
|
|
3728
|
+
state: z2.object({
|
|
3729
|
+
variables: z2.array(z2.unknown())
|
|
3730
|
+
}).strict(),
|
|
3731
|
+
body: z2.object({
|
|
3732
|
+
nodes: z2.array(z2.unknown())
|
|
3733
|
+
}).strict(),
|
|
3734
|
+
temporalCommands: z2.array(temporalCommandSchema),
|
|
3735
|
+
dependencies: z2.array(workflowDependencySchema),
|
|
3736
|
+
diagnostics: z2.array(diagnosticSchema)
|
|
3737
|
+
}).strict();
|
|
3738
|
+
var activityDefinitionSchema = z2.object({
|
|
3739
|
+
id: z2.string().min(1),
|
|
3740
|
+
name: z2.string().min(1),
|
|
3741
|
+
source: sourceLocationSchema.optional(),
|
|
3742
|
+
implementationSource: sourceLocationSchema.optional(),
|
|
3743
|
+
confidence: confidenceSchema
|
|
3744
|
+
}).strict();
|
|
3745
|
+
var temporalAnalysisDocumentSchema = z2.object({
|
|
3746
|
+
schemaVersion: z2.literal("temporal-analysis/v1"),
|
|
3747
|
+
artifactId: z2.string().min(1),
|
|
3748
|
+
metadata: artifactMetadataSchema,
|
|
3749
|
+
project: z2.object({
|
|
3750
|
+
root: z2.string().min(1),
|
|
3751
|
+
tsconfig: z2.string().min(1),
|
|
3752
|
+
packageManager: z2.union([z2.literal("bun"), z2.literal("npm"), z2.literal("pnpm"), z2.literal("yarn")]).optional()
|
|
3753
|
+
}).strict(),
|
|
3754
|
+
sdk: z2.object({
|
|
3755
|
+
temporalTypeScriptVersion: z2.string().min(1).optional(),
|
|
3756
|
+
detectedPackages: z2.array(z2.string().min(1))
|
|
3757
|
+
}).strict(),
|
|
3758
|
+
workers: z2.array(z2.unknown()),
|
|
3759
|
+
workflows: z2.array(workflowDefinitionSchema),
|
|
3760
|
+
activities: z2.array(activityDefinitionSchema),
|
|
3761
|
+
clients: z2.array(z2.unknown()),
|
|
3762
|
+
diagnostics: z2.array(diagnosticSchema)
|
|
3763
|
+
}).strict();
|
|
3764
|
+
|
|
3765
|
+
// packages/schemas/src/temporal-overlay.ts
|
|
3766
|
+
import { z as z3 } from "zod";
|
|
3767
|
+
var staticOverlayNodeSchema = z3.object({
|
|
3768
|
+
id: z3.string().min(1),
|
|
3769
|
+
kind: z3.union([
|
|
3770
|
+
z3.literal("workflow"),
|
|
3771
|
+
z3.literal("activity"),
|
|
3772
|
+
z3.literal("signal"),
|
|
3773
|
+
z3.literal("timer"),
|
|
3774
|
+
z3.literal("condition"),
|
|
3775
|
+
z3.literal("query"),
|
|
3776
|
+
z3.literal("update"),
|
|
3777
|
+
z3.literal("child-workflow"),
|
|
3778
|
+
z3.literal("external-workflow"),
|
|
3779
|
+
z3.literal("continue-as-new"),
|
|
3780
|
+
z3.literal("patch"),
|
|
3781
|
+
z3.literal("cancellation-scope"),
|
|
3782
|
+
z3.literal("dynamic")
|
|
3783
|
+
]),
|
|
3784
|
+
name: z3.string().min(1),
|
|
3785
|
+
observed: z3.boolean(),
|
|
3786
|
+
source: sourceLocationSchema.optional()
|
|
3787
|
+
}).strict();
|
|
3788
|
+
var mappingEvidenceSchema = z3.object({
|
|
3789
|
+
kind: z3.union([
|
|
3790
|
+
z3.literal("activity-type"),
|
|
3791
|
+
z3.literal("command-order"),
|
|
3792
|
+
z3.literal("workflow-type"),
|
|
3793
|
+
z3.literal("signal-name"),
|
|
3794
|
+
z3.literal("timer-order"),
|
|
3795
|
+
z3.literal("update-name"),
|
|
3796
|
+
z3.literal("child-workflow-type"),
|
|
3797
|
+
z3.literal("external-signal-name"),
|
|
3798
|
+
z3.literal("patch-id"),
|
|
3799
|
+
z3.literal("continue-as-new"),
|
|
3800
|
+
z3.literal("scope-containment"),
|
|
3801
|
+
z3.literal("dynamic-dispatch"),
|
|
3802
|
+
z3.literal("replay-command-sequence"),
|
|
3803
|
+
z3.literal("event-reference"),
|
|
3804
|
+
z3.literal("source-location"),
|
|
3805
|
+
z3.literal("unmapped")
|
|
3806
|
+
]),
|
|
3807
|
+
description: z3.string().min(1),
|
|
3808
|
+
eventIds: z3.array(z3.number().int().positive()).optional(),
|
|
3809
|
+
staticNodeId: z3.string().min(1).optional()
|
|
3810
|
+
}).strict();
|
|
3811
|
+
var runtimeNodeMappingSchema = z3.object({
|
|
3812
|
+
runtimeOperationId: z3.string().min(1),
|
|
3813
|
+
staticNodeId: z3.string().min(1).optional(),
|
|
3814
|
+
confidence: confidenceSchema,
|
|
3815
|
+
reason: z3.string().min(1),
|
|
3816
|
+
evidence: z3.array(mappingEvidenceSchema)
|
|
3817
|
+
}).strict();
|
|
3818
|
+
var executionOverlayDocumentSchema = z3.object({
|
|
3819
|
+
schemaVersion: z3.literal("temporal-overlay/v1"),
|
|
3820
|
+
artifactId: z3.string().min(1),
|
|
3821
|
+
staticAnalysisId: z3.string().min(1),
|
|
3822
|
+
runtimeTraceId: z3.string().min(1),
|
|
3823
|
+
workflow: z3.string().min(1),
|
|
3824
|
+
staticNodes: z3.array(staticOverlayNodeSchema),
|
|
3825
|
+
mappings: z3.array(runtimeNodeMappingSchema),
|
|
3826
|
+
branchOutcomes: z3.array(z3.unknown()),
|
|
3827
|
+
coverage: z3.object({
|
|
3828
|
+
nodes: z3.object({
|
|
3829
|
+
total: z3.number().int().nonnegative(),
|
|
3830
|
+
observed: z3.number().int().nonnegative(),
|
|
3831
|
+
skipped: z3.number().int().nonnegative(),
|
|
3832
|
+
unmappedRuntimeOperations: z3.number().int().nonnegative()
|
|
3833
|
+
}).strict(),
|
|
3834
|
+
activities: z3.object({
|
|
3835
|
+
staticTotal: z3.number().int().nonnegative(),
|
|
3836
|
+
observed: z3.number().int().nonnegative(),
|
|
3837
|
+
retried: z3.number().int().nonnegative(),
|
|
3838
|
+
failed: z3.number().int().nonnegative()
|
|
3839
|
+
}).strict(),
|
|
3840
|
+
messages: z3.object({
|
|
3841
|
+
staticSignals: z3.number().int().nonnegative(),
|
|
3842
|
+
receivedSignals: z3.array(z3.string()),
|
|
3843
|
+
staticUpdates: z3.number().int().nonnegative(),
|
|
3844
|
+
receivedUpdates: z3.array(z3.string()),
|
|
3845
|
+
staticQueries: z3.number().int().nonnegative()
|
|
3846
|
+
}).strict(),
|
|
3847
|
+
timers: z3.object({
|
|
3848
|
+
staticTotal: z3.number().int().nonnegative(),
|
|
3849
|
+
fired: z3.number().int().nonnegative(),
|
|
3850
|
+
canceled: z3.number().int().nonnegative(),
|
|
3851
|
+
pending: z3.number().int().nonnegative()
|
|
3852
|
+
}).strict()
|
|
3853
|
+
}).strict(),
|
|
3854
|
+
diagnostics: z3.array(diagnosticSchema)
|
|
3855
|
+
}).strict();
|
|
3856
|
+
|
|
3857
|
+
// packages/schemas/src/temporal-trace.ts
|
|
3858
|
+
import { z as z4 } from "zod";
|
|
3859
|
+
var eventReferenceSchema = z4.object({
|
|
3860
|
+
eventId: z4.number().int().positive(),
|
|
3861
|
+
eventType: z4.string().min(1)
|
|
3862
|
+
}).strict();
|
|
3863
|
+
var payloadReferenceSchema = z4.object({
|
|
3864
|
+
id: z4.string().min(1),
|
|
3865
|
+
eventId: z4.number().int().positive(),
|
|
3866
|
+
kind: z4.union([
|
|
3867
|
+
z4.literal("input"),
|
|
3868
|
+
z4.literal("result"),
|
|
3869
|
+
z4.literal("failure"),
|
|
3870
|
+
z4.literal("signal"),
|
|
3871
|
+
z4.literal("update")
|
|
3872
|
+
]),
|
|
3873
|
+
decoded: z4.boolean(),
|
|
3874
|
+
preview: z4.unknown().optional(),
|
|
3875
|
+
redacted: z4.boolean()
|
|
3876
|
+
}).strict();
|
|
3877
|
+
var activityAttemptSchema = z4.object({
|
|
3878
|
+
attempt: z4.number().int().positive(),
|
|
3879
|
+
scheduledEventId: z4.number().int().positive(),
|
|
3880
|
+
startedEventId: z4.number().int().positive().optional(),
|
|
3881
|
+
closedEventId: z4.number().int().positive().optional(),
|
|
3882
|
+
status: z4.union([
|
|
3883
|
+
z4.literal("completed"),
|
|
3884
|
+
z4.literal("failed"),
|
|
3885
|
+
z4.literal("timedOut"),
|
|
3886
|
+
z4.literal("canceled"),
|
|
3887
|
+
z4.literal("pending")
|
|
3888
|
+
])
|
|
3889
|
+
}).strict();
|
|
3890
|
+
var workflowLifecycleOperationSchema = z4.object({
|
|
3891
|
+
id: z4.string().min(1),
|
|
3892
|
+
kind: z4.literal("workflow-lifecycle"),
|
|
3893
|
+
status: z4.union([
|
|
3894
|
+
z4.literal("started"),
|
|
3895
|
+
z4.literal("completed"),
|
|
3896
|
+
z4.literal("failed"),
|
|
3897
|
+
z4.literal("canceled"),
|
|
3898
|
+
z4.literal("terminated"),
|
|
3899
|
+
z4.literal("timedOut"),
|
|
3900
|
+
z4.literal("continued-as-new")
|
|
3901
|
+
]),
|
|
3902
|
+
eventReferences: z4.array(eventReferenceSchema),
|
|
3903
|
+
payloadReferences: z4.array(z4.string().min(1))
|
|
3904
|
+
}).strict();
|
|
3905
|
+
var activityExecutionOperationSchema = z4.object({
|
|
3906
|
+
id: z4.string().min(1),
|
|
3907
|
+
kind: z4.literal("activity"),
|
|
3908
|
+
activityType: z4.string().min(1),
|
|
3909
|
+
activityId: z4.string().min(1),
|
|
3910
|
+
status: z4.union([
|
|
3911
|
+
z4.literal("completed"),
|
|
3912
|
+
z4.literal("failed"),
|
|
3913
|
+
z4.literal("timedOut"),
|
|
3914
|
+
z4.literal("canceled"),
|
|
3915
|
+
z4.literal("pending")
|
|
3916
|
+
]),
|
|
3917
|
+
attempts: z4.array(activityAttemptSchema),
|
|
3918
|
+
firstScheduledAt: z4.string().datetime(),
|
|
3919
|
+
closedAt: z4.string().datetime().optional(),
|
|
3920
|
+
durationMs: z4.number().nonnegative().optional(),
|
|
3921
|
+
eventReferences: z4.array(eventReferenceSchema),
|
|
3922
|
+
payloadReferences: z4.array(z4.string().min(1))
|
|
3923
|
+
}).strict();
|
|
3924
|
+
var signalDeliveryOperationSchema = z4.object({
|
|
3925
|
+
id: z4.string().min(1),
|
|
3926
|
+
kind: z4.literal("signal"),
|
|
3927
|
+
signalName: z4.string().min(1),
|
|
3928
|
+
receivedAt: z4.string().datetime(),
|
|
3929
|
+
eventReferences: z4.array(eventReferenceSchema),
|
|
3930
|
+
payloadReferences: z4.array(z4.string().min(1))
|
|
3931
|
+
}).strict();
|
|
3932
|
+
var timerOperationSchema = z4.object({
|
|
3933
|
+
id: z4.string().min(1),
|
|
3934
|
+
kind: z4.literal("timer"),
|
|
3935
|
+
timerId: z4.string().min(1),
|
|
3936
|
+
status: z4.union([z4.literal("fired"), z4.literal("canceled"), z4.literal("pending")]),
|
|
3937
|
+
startedAt: z4.string().datetime(),
|
|
3938
|
+
closedAt: z4.string().datetime().optional(),
|
|
3939
|
+
durationText: z4.string().min(1).optional(),
|
|
3940
|
+
eventReferences: z4.array(eventReferenceSchema)
|
|
3941
|
+
}).strict();
|
|
3942
|
+
var updateOperationSchema = z4.object({
|
|
3943
|
+
id: z4.string().min(1),
|
|
3944
|
+
kind: z4.literal("update"),
|
|
3945
|
+
updateId: z4.string().min(1),
|
|
3946
|
+
updateName: z4.string().min(1),
|
|
3947
|
+
status: z4.union([z4.literal("accepted"), z4.literal("completed"), z4.literal("failed")]),
|
|
3948
|
+
acceptedAt: z4.string().datetime(),
|
|
3949
|
+
closedAt: z4.string().datetime().optional(),
|
|
3950
|
+
eventReferences: z4.array(eventReferenceSchema),
|
|
3951
|
+
payloadReferences: z4.array(z4.string().min(1))
|
|
3952
|
+
}).strict();
|
|
3953
|
+
var childWorkflowOperationSchema = z4.object({
|
|
3954
|
+
id: z4.string().min(1),
|
|
3955
|
+
kind: z4.literal("child-workflow"),
|
|
3956
|
+
workflowType: z4.string().min(1),
|
|
3957
|
+
childWorkflowId: z4.string().min(1),
|
|
3958
|
+
childRunId: z4.string().min(1).optional(),
|
|
3959
|
+
status: z4.union([
|
|
3960
|
+
z4.literal("initiated"),
|
|
3961
|
+
z4.literal("startFailed"),
|
|
3962
|
+
z4.literal("started"),
|
|
3963
|
+
z4.literal("completed"),
|
|
3964
|
+
z4.literal("failed"),
|
|
3965
|
+
z4.literal("canceled"),
|
|
3966
|
+
z4.literal("timedOut"),
|
|
3967
|
+
z4.literal("terminated")
|
|
3968
|
+
]),
|
|
3969
|
+
initiatedAt: z4.string().datetime(),
|
|
3970
|
+
closedAt: z4.string().datetime().optional(),
|
|
3971
|
+
eventReferences: z4.array(eventReferenceSchema),
|
|
3972
|
+
payloadReferences: z4.array(z4.string().min(1))
|
|
3973
|
+
}).strict();
|
|
3974
|
+
var externalSignalOperationSchema = z4.object({
|
|
3975
|
+
id: z4.string().min(1),
|
|
3976
|
+
kind: z4.literal("external-signal"),
|
|
3977
|
+
signalName: z4.string().min(1),
|
|
3978
|
+
targetWorkflowId: z4.string().min(1),
|
|
3979
|
+
targetRunId: z4.string().min(1).optional(),
|
|
3980
|
+
status: z4.union([z4.literal("initiated"), z4.literal("signaled"), z4.literal("failed")]),
|
|
3981
|
+
initiatedAt: z4.string().datetime(),
|
|
3982
|
+
closedAt: z4.string().datetime().optional(),
|
|
3983
|
+
eventReferences: z4.array(eventReferenceSchema),
|
|
3984
|
+
payloadReferences: z4.array(z4.string().min(1))
|
|
3985
|
+
}).strict();
|
|
3986
|
+
var markerOperationSchema = z4.object({
|
|
3987
|
+
id: z4.string().min(1),
|
|
3988
|
+
kind: z4.literal("marker"),
|
|
3989
|
+
markerName: z4.string().min(1),
|
|
3990
|
+
patchId: z4.string().min(1).optional(),
|
|
3991
|
+
deprecated: z4.boolean().optional(),
|
|
3992
|
+
recordedAt: z4.string().datetime(),
|
|
3993
|
+
eventReferences: z4.array(eventReferenceSchema)
|
|
3994
|
+
}).strict();
|
|
3995
|
+
var continueAsNewOperationSchema = z4.object({
|
|
3996
|
+
id: z4.string().min(1),
|
|
3997
|
+
kind: z4.literal("continue-as-new"),
|
|
3998
|
+
newRunId: z4.string().min(1).optional(),
|
|
3999
|
+
occurredAt: z4.string().datetime(),
|
|
4000
|
+
eventReferences: z4.array(eventReferenceSchema),
|
|
4001
|
+
payloadReferences: z4.array(z4.string().min(1))
|
|
4002
|
+
}).strict();
|
|
4003
|
+
var cancelRequestOperationSchema = z4.object({
|
|
4004
|
+
id: z4.string().min(1),
|
|
4005
|
+
kind: z4.literal("cancel-request"),
|
|
4006
|
+
requestedAt: z4.string().datetime(),
|
|
4007
|
+
eventReferences: z4.array(eventReferenceSchema)
|
|
4008
|
+
}).strict();
|
|
4009
|
+
var unmappedHistoryOperationSchema = z4.object({
|
|
4010
|
+
id: z4.string().min(1),
|
|
4011
|
+
kind: z4.literal("unmapped"),
|
|
4012
|
+
eventReferences: z4.array(eventReferenceSchema),
|
|
4013
|
+
reason: z4.string().min(1)
|
|
4014
|
+
}).strict();
|
|
4015
|
+
var runtimeOperationSchema = z4.discriminatedUnion("kind", [
|
|
4016
|
+
workflowLifecycleOperationSchema,
|
|
4017
|
+
activityExecutionOperationSchema,
|
|
4018
|
+
signalDeliveryOperationSchema,
|
|
4019
|
+
timerOperationSchema,
|
|
4020
|
+
updateOperationSchema,
|
|
4021
|
+
childWorkflowOperationSchema,
|
|
4022
|
+
externalSignalOperationSchema,
|
|
4023
|
+
markerOperationSchema,
|
|
4024
|
+
continueAsNewOperationSchema,
|
|
4025
|
+
cancelRequestOperationSchema,
|
|
4026
|
+
unmappedHistoryOperationSchema
|
|
4027
|
+
]);
|
|
4028
|
+
var runtimeTimelineEntrySchema = z4.object({
|
|
4029
|
+
id: z4.string().min(1),
|
|
4030
|
+
operationId: z4.string().min(1),
|
|
4031
|
+
at: z4.string().datetime(),
|
|
4032
|
+
label: z4.string().min(1),
|
|
4033
|
+
eventIds: z4.array(z4.number().int().positive())
|
|
4034
|
+
}).strict();
|
|
4035
|
+
var runtimeTraceDocumentSchema = z4.object({
|
|
4036
|
+
schemaVersion: z4.literal("temporal-trace/v1"),
|
|
4037
|
+
artifactId: z4.string().min(1),
|
|
4038
|
+
metadata: artifactMetadataSchema,
|
|
4039
|
+
execution: z4.object({
|
|
4040
|
+
workflowType: z4.string().min(1),
|
|
4041
|
+
workflowId: z4.string().min(1),
|
|
4042
|
+
runId: z4.string().min(1),
|
|
4043
|
+
status: z4.union([
|
|
4044
|
+
z4.literal("running"),
|
|
4045
|
+
z4.literal("completed"),
|
|
4046
|
+
z4.literal("failed"),
|
|
4047
|
+
z4.literal("canceled"),
|
|
4048
|
+
z4.literal("terminated"),
|
|
4049
|
+
z4.literal("timedOut"),
|
|
4050
|
+
z4.literal("continued-as-new")
|
|
4051
|
+
]),
|
|
4052
|
+
startedAt: z4.string().datetime(),
|
|
4053
|
+
closedAt: z4.string().datetime().optional(),
|
|
4054
|
+
durationMs: z4.number().nonnegative().optional()
|
|
4055
|
+
}).strict(),
|
|
4056
|
+
source: z4.object({
|
|
4057
|
+
namespace: z4.string().min(1).optional(),
|
|
4058
|
+
taskQueue: z4.string().min(1).optional(),
|
|
4059
|
+
eventCount: z4.number().int().nonnegative(),
|
|
4060
|
+
importedFrom: z4.union([z4.literal("file"), z4.literal("api"), z4.literal("cli")])
|
|
4061
|
+
}).strict(),
|
|
4062
|
+
operations: z4.array(runtimeOperationSchema),
|
|
4063
|
+
timeline: z4.array(runtimeTimelineEntrySchema),
|
|
4064
|
+
payloads: z4.array(payloadReferenceSchema),
|
|
4065
|
+
diagnostics: z4.array(diagnosticSchema)
|
|
4066
|
+
}).strict();
|
|
4067
|
+
|
|
4068
|
+
// packages/schemas/src/index.ts
|
|
4069
|
+
var artifactSchemaVersions = {
|
|
4070
|
+
analysis: "temporal-analysis/v1",
|
|
4071
|
+
trace: "temporal-trace/v1",
|
|
4072
|
+
overlay: "temporal-overlay/v1"
|
|
4073
|
+
};
|
|
4074
|
+
var artifactSchemaVersionValues = new Set(Object.values(artifactSchemaVersions));
|
|
4075
|
+
function isTemporalExplorerArtifactSchemaVersion(value) {
|
|
4076
|
+
return artifactSchemaVersionValues.has(value);
|
|
4077
|
+
}
|
|
4078
|
+
var artifactSchemasByVersion = {
|
|
4079
|
+
[artifactSchemaVersions.analysis]: temporalAnalysisDocumentSchema,
|
|
4080
|
+
[artifactSchemaVersions.trace]: runtimeTraceDocumentSchema,
|
|
4081
|
+
[artifactSchemaVersions.overlay]: executionOverlayDocumentSchema
|
|
4082
|
+
};
|
|
4083
|
+
function getSchemaVersion(value) {
|
|
4084
|
+
if (!value || typeof value !== "object" || !("schemaVersion" in value)) {
|
|
4085
|
+
return;
|
|
4086
|
+
}
|
|
4087
|
+
const candidate = value.schemaVersion;
|
|
4088
|
+
return typeof candidate === "string" ? candidate : undefined;
|
|
4089
|
+
}
|
|
4090
|
+
function formatZodIssue(issue) {
|
|
4091
|
+
return {
|
|
4092
|
+
path: issue.path.length > 0 ? issue.path.join(".") : "<root>",
|
|
4093
|
+
message: issue.message
|
|
4094
|
+
};
|
|
4095
|
+
}
|
|
4096
|
+
function validateArtifact(value) {
|
|
4097
|
+
const schemaVersion = getSchemaVersion(value);
|
|
4098
|
+
if (!schemaVersion) {
|
|
4099
|
+
return {
|
|
4100
|
+
success: false,
|
|
4101
|
+
code: "TES_MISSING_SCHEMA_VERSION",
|
|
4102
|
+
issues: [{ path: "schemaVersion", message: "Artifact is missing schemaVersion." }]
|
|
4103
|
+
};
|
|
4104
|
+
}
|
|
4105
|
+
if (!isTemporalExplorerArtifactSchemaVersion(schemaVersion)) {
|
|
4106
|
+
return {
|
|
4107
|
+
success: false,
|
|
4108
|
+
code: "TES_UNSUPPORTED_SCHEMA_VERSION",
|
|
4109
|
+
issues: [
|
|
4110
|
+
{
|
|
4111
|
+
path: "schemaVersion",
|
|
4112
|
+
message: `Unsupported artifact schema version: ${schemaVersion}. This build supports ${Object.values(artifactSchemaVersions).join(", ")}; newer artifacts require upgrading temporal-explorer.`
|
|
4113
|
+
}
|
|
4114
|
+
]
|
|
4115
|
+
};
|
|
4116
|
+
}
|
|
4117
|
+
const result = artifactSchemasByVersion[schemaVersion].safeParse(value);
|
|
4118
|
+
if (!result.success) {
|
|
4119
|
+
return {
|
|
4120
|
+
success: false,
|
|
4121
|
+
code: "TES_SCHEMA_VALIDATION_FAILED",
|
|
4122
|
+
schemaVersion,
|
|
4123
|
+
issues: result.error.issues.map(formatZodIssue)
|
|
4124
|
+
};
|
|
4125
|
+
}
|
|
4126
|
+
return {
|
|
4127
|
+
success: true,
|
|
4128
|
+
schemaVersion,
|
|
4129
|
+
value: result.data,
|
|
4130
|
+
issues: []
|
|
4131
|
+
};
|
|
4132
|
+
}
|
|
4133
|
+
|
|
4134
|
+
// packages/api/src/result.ts
|
|
4135
|
+
var temporalExplorerArtifactVersions = artifactSchemaVersions;
|
|
4136
|
+
function getTemporalExplorerVersion() {
|
|
4137
|
+
return "0.0.0-mvp";
|
|
4138
|
+
}
|
|
4139
|
+
function createTemporalExplorerResult(value, options = {}) {
|
|
4140
|
+
return {
|
|
4141
|
+
value,
|
|
4142
|
+
diagnostics: options.diagnostics ?? [],
|
|
4143
|
+
warnings: options.warnings ?? [],
|
|
4144
|
+
artifacts: options.artifacts ?? [],
|
|
4145
|
+
metadata: options.metadata ?? {
|
|
4146
|
+
temporalExplorerVersion: getTemporalExplorerVersion(),
|
|
4147
|
+
schemaVersion: temporalExplorerArtifactVersions.analysis,
|
|
4148
|
+
inputs: {
|
|
4149
|
+
projectRoot: "",
|
|
4150
|
+
configHash: "",
|
|
4151
|
+
sourceFileHashes: {},
|
|
4152
|
+
temporalSdkVersions: {}
|
|
4153
|
+
}
|
|
4154
|
+
}
|
|
4155
|
+
};
|
|
4156
|
+
}
|
|
4157
|
+
// packages/api/src/library-parity.ts
|
|
4158
|
+
function renderMarkdown(options) {
|
|
4159
|
+
const contents = options.workflowName ? renderWorkflowMarkdown({ ...options, workflowName: options.workflowName }) : renderWorkflowIndexMarkdown(options);
|
|
4160
|
+
return createTemporalExplorerResult(contents, {
|
|
4161
|
+
metadata: options.analysis.metadata
|
|
4162
|
+
});
|
|
4163
|
+
}
|
|
4164
|
+
function renderWorkflowJson(options) {
|
|
4165
|
+
const workflow = options.analysis.workflows.find((candidate) => candidate.name === options.workflow);
|
|
4166
|
+
if (!workflow) {
|
|
4167
|
+
throw new Error(`Workflow "${options.workflow}" was not found.`);
|
|
4168
|
+
}
|
|
4169
|
+
return createTemporalExplorerResult(workflow, {
|
|
4170
|
+
diagnostics: workflow.diagnostics,
|
|
4171
|
+
metadata: options.analysis.metadata
|
|
4172
|
+
});
|
|
4173
|
+
}
|
|
4174
|
+
function renderTypeDeclarations(options) {
|
|
4175
|
+
const workflows = options.workflowName ? options.analysis.workflows.filter((workflow) => workflow.name === options.workflowName) : options.analysis.workflows;
|
|
4176
|
+
if (options.workflowName && workflows.length === 0) {
|
|
4177
|
+
throw new Error(`Workflow "${options.workflowName}" was not found.`);
|
|
4178
|
+
}
|
|
4179
|
+
const files = workflows.toSorted((left, right) => left.name.localeCompare(right.name)).map((workflow) => ({
|
|
4180
|
+
path: `${workflow.name}.d.ts`,
|
|
4181
|
+
contents: renderWorkflowDeclaration(options.analysis, workflow.name)
|
|
4182
|
+
}));
|
|
4183
|
+
return createTemporalExplorerResult(files, {
|
|
4184
|
+
metadata: options.analysis.metadata
|
|
4185
|
+
});
|
|
4186
|
+
}
|
|
4187
|
+
function isLoadedProject(value) {
|
|
4188
|
+
return typeof value.root === "string" && typeof value.tsconfig === "string" && Array.isArray(value.workflowFiles) && "outputDirectory" in value;
|
|
4189
|
+
}
|
|
4190
|
+
async function runDiagnostics(projectOrOptions = {}) {
|
|
4191
|
+
const project = isLoadedProject(projectOrOptions) ? projectOrOptions : await loadTemporalExplorerProject(projectOrOptions);
|
|
4192
|
+
const analysis = await analyzeProject(project);
|
|
4193
|
+
const diagnostics = applySeverityOverrides(analysis.diagnostics, project.configuration?.diagnostics);
|
|
4194
|
+
return createTemporalExplorerResult(diagnostics, {
|
|
4195
|
+
diagnostics,
|
|
4196
|
+
warnings: diagnostics.filter((diagnostic) => diagnostic.severity === "warning"),
|
|
4197
|
+
metadata: analysis.metadata
|
|
4198
|
+
});
|
|
4199
|
+
}
|
|
4200
|
+
|
|
4201
|
+
// packages/api/src/index.ts
|
|
4202
|
+
function isTemporalExplorerProject(value) {
|
|
4203
|
+
const candidate = value && typeof value === "object" ? value : {};
|
|
4204
|
+
return typeof candidate.root === "string" && typeof candidate.tsconfig === "string" && typeof candidate.outputDirectory === "string" && Array.isArray(candidate.workflowFiles);
|
|
4205
|
+
}
|
|
4206
|
+
function isWarningDiagnostic(diagnostic) {
|
|
4207
|
+
return diagnostic.severity === "warning";
|
|
4208
|
+
}
|
|
4209
|
+
function getWarningDiagnostics(diagnostics) {
|
|
4210
|
+
return diagnostics.filter(isWarningDiagnostic);
|
|
4211
|
+
}
|
|
4212
|
+
async function loadTemporalExplorerProject2(options = {}) {
|
|
4213
|
+
return await loadTemporalExplorerProject(options);
|
|
4214
|
+
}
|
|
4215
|
+
async function analyzeProject2(projectOrOptions = {}) {
|
|
4216
|
+
const project = isTemporalExplorerProject(projectOrOptions) ? projectOrOptions : await loadTemporalExplorerProject2(projectOrOptions);
|
|
4217
|
+
const analysis = await analyzeProject(project);
|
|
4218
|
+
return createTemporalExplorerResult(analysis, {
|
|
4219
|
+
diagnostics: analysis.diagnostics,
|
|
4220
|
+
warnings: getWarningDiagnostics(analysis.diagnostics),
|
|
4221
|
+
metadata: analysis.metadata
|
|
4222
|
+
});
|
|
4223
|
+
}
|
|
4224
|
+
async function analyzeWorkflowFiles2(options) {
|
|
4225
|
+
const analysis = await analyzeWorkflowFiles(options);
|
|
4226
|
+
return createTemporalExplorerResult(analysis, {
|
|
4227
|
+
diagnostics: analysis.diagnostics,
|
|
4228
|
+
warnings: getWarningDiagnostics(analysis.diagnostics),
|
|
4229
|
+
metadata: analysis.metadata
|
|
4230
|
+
});
|
|
4231
|
+
}
|
|
4232
|
+
function parseEventHistory2(options) {
|
|
4233
|
+
const trace = parseEventHistory(options);
|
|
4234
|
+
return createTemporalExplorerResult(trace, {
|
|
4235
|
+
diagnostics: trace.diagnostics,
|
|
4236
|
+
warnings: getWarningDiagnostics(trace.diagnostics),
|
|
4237
|
+
metadata: trace.metadata
|
|
4238
|
+
});
|
|
4239
|
+
}
|
|
4240
|
+
async function importHistoryFromFile(options) {
|
|
4241
|
+
const trace = await importEventHistoryFile(options);
|
|
4242
|
+
return createTemporalExplorerResult(trace, {
|
|
4243
|
+
diagnostics: trace.diagnostics,
|
|
4244
|
+
warnings: getWarningDiagnostics(trace.diagnostics),
|
|
4245
|
+
metadata: trace.metadata
|
|
4246
|
+
});
|
|
4247
|
+
}
|
|
4248
|
+
function parseArtifactOrThrow(label, result) {
|
|
4249
|
+
if (result.success) {
|
|
4250
|
+
return result.data;
|
|
4251
|
+
}
|
|
4252
|
+
const issues = result.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join(`
|
|
4253
|
+
`);
|
|
4254
|
+
throw new Error(`${label} failed schema validation:
|
|
4255
|
+
${issues}`);
|
|
4256
|
+
}
|
|
4257
|
+
function createExecutionOverlay2(options) {
|
|
4258
|
+
const overlay = createExecutionOverlay(options);
|
|
4259
|
+
return createTemporalExplorerResult(overlay, {
|
|
4260
|
+
diagnostics: overlay.diagnostics,
|
|
4261
|
+
warnings: getWarningDiagnostics(overlay.diagnostics),
|
|
4262
|
+
metadata: options.analysis.metadata
|
|
4263
|
+
});
|
|
4264
|
+
}
|
|
4265
|
+
function createExecutionOverlayFromArtifacts(options) {
|
|
4266
|
+
return createExecutionOverlay2({
|
|
4267
|
+
analysis: parseArtifactOrThrow("analysis artifact", temporalAnalysisDocumentSchema.safeParse(options.analysisArtifact)),
|
|
4268
|
+
trace: parseArtifactOrThrow("trace artifact", runtimeTraceDocumentSchema.safeParse(options.traceArtifact)),
|
|
4269
|
+
workflowName: options.workflowName,
|
|
4270
|
+
...options.replayCapture ? { replayCapture: options.replayCapture } : {}
|
|
4271
|
+
});
|
|
4272
|
+
}
|
|
4273
|
+
function createOverlayReportFromArtifact(overlayArtifact) {
|
|
4274
|
+
return createOverlayReport(parseArtifactOrThrow("overlay artifact", executionOverlayDocumentSchema.safeParse(overlayArtifact)));
|
|
4275
|
+
}
|
|
4276
|
+
function createDocumentationSet2(options) {
|
|
4277
|
+
const files = createDocumentationSet(options);
|
|
4278
|
+
return createTemporalExplorerResult(files, {
|
|
4279
|
+
metadata: options.analysis.metadata
|
|
4280
|
+
});
|
|
4281
|
+
}
|
|
4282
|
+
function createDocumentationSetFromArtifacts(options) {
|
|
4283
|
+
return createDocumentationSet2({
|
|
4284
|
+
analysis: parseArtifactOrThrow("analysis artifact", temporalAnalysisDocumentSchema.safeParse(options.analysisArtifact)),
|
|
4285
|
+
traces: (options.traceArtifacts ?? []).map((artifact, index) => parseArtifactOrThrow(`trace artifact ${index + 1}`, runtimeTraceDocumentSchema.safeParse(artifact))),
|
|
4286
|
+
overlays: (options.overlayArtifacts ?? []).map((artifact, index) => parseArtifactOrThrow(`overlay artifact ${index + 1}`, executionOverlayDocumentSchema.safeParse(artifact)))
|
|
4287
|
+
});
|
|
4288
|
+
}
|
|
4289
|
+
function renderWorkflowMermaidFromArtifacts(options) {
|
|
4290
|
+
return renderWorkflowMermaid(parseArtifactOrThrow("analysis artifact", temporalAnalysisDocumentSchema.safeParse(options.analysisArtifact)), options.workflowName);
|
|
4291
|
+
}
|
|
4292
|
+
export {
|
|
4293
|
+
validateArtifact,
|
|
4294
|
+
toProjectPath,
|
|
4295
|
+
temporalExplorerArtifactVersions,
|
|
4296
|
+
runDiagnostics,
|
|
4297
|
+
renderWorkflowMermaidFromArtifacts,
|
|
4298
|
+
renderWorkflowJson,
|
|
4299
|
+
renderTypeDeclarations,
|
|
4300
|
+
renderMarkdown,
|
|
4301
|
+
parseEventHistory2 as parseEventHistory,
|
|
4302
|
+
loadTemporalExplorerProject2 as loadTemporalExplorerProject,
|
|
4303
|
+
listWorkflowRuns,
|
|
4304
|
+
importHistoryFromFile,
|
|
4305
|
+
getTemporalExplorerVersion,
|
|
4306
|
+
formatAggregateReport,
|
|
4307
|
+
fetchEventHistory,
|
|
4308
|
+
defineConfig,
|
|
4309
|
+
createTemporalExplorerResult,
|
|
4310
|
+
createSourceFileHashes,
|
|
4311
|
+
createOverlayReportFromArtifact,
|
|
4312
|
+
createOverlayReport,
|
|
4313
|
+
createLiveClient,
|
|
4314
|
+
createExecutionOverlayFromArtifacts,
|
|
4315
|
+
createExecutionOverlay2 as createExecutionOverlay,
|
|
4316
|
+
createDocumentationSetFromArtifacts,
|
|
4317
|
+
createDocumentationSet2 as createDocumentationSet,
|
|
4318
|
+
createAggregateReport,
|
|
4319
|
+
applySeverityOverrides,
|
|
4320
|
+
analyzeWorkflowFiles2 as analyzeWorkflowFiles,
|
|
4321
|
+
analyzeProject2 as analyzeProject
|
|
4322
|
+
};
|