@optave/codegraph 3.1.1 → 3.1.3
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/README.md +6 -6
- package/package.json +7 -7
- package/src/ast-analysis/engine.js +365 -0
- package/src/ast-analysis/metrics.js +118 -0
- package/src/ast-analysis/visitor-utils.js +176 -0
- package/src/ast-analysis/visitor.js +162 -0
- package/src/ast-analysis/visitors/ast-store-visitor.js +150 -0
- package/src/ast-analysis/visitors/cfg-visitor.js +792 -0
- package/src/ast-analysis/visitors/complexity-visitor.js +243 -0
- package/src/ast-analysis/visitors/dataflow-visitor.js +358 -0
- package/src/ast.js +13 -140
- package/src/audit.js +2 -87
- package/src/batch.js +0 -25
- package/src/boundaries.js +1 -1
- package/src/branch-compare.js +1 -96
- package/src/builder.js +60 -178
- package/src/cfg.js +89 -883
- package/src/check.js +1 -84
- package/src/cli.js +31 -22
- package/src/cochange.js +1 -39
- package/src/commands/audit.js +88 -0
- package/src/commands/batch.js +26 -0
- package/src/commands/branch-compare.js +97 -0
- package/src/commands/cfg.js +55 -0
- package/src/commands/check.js +82 -0
- package/src/commands/cochange.js +37 -0
- package/src/commands/communities.js +69 -0
- package/src/commands/complexity.js +77 -0
- package/src/commands/dataflow.js +110 -0
- package/src/commands/flow.js +70 -0
- package/src/commands/manifesto.js +77 -0
- package/src/commands/owners.js +52 -0
- package/src/commands/query.js +21 -0
- package/src/commands/sequence.js +33 -0
- package/src/commands/structure.js +64 -0
- package/src/commands/triage.js +49 -0
- package/src/communities.js +12 -83
- package/src/complexity.js +43 -357
- package/src/cycles.js +1 -1
- package/src/dataflow.js +12 -665
- package/src/db/repository/build-stmts.js +104 -0
- package/src/db/repository/cached-stmt.js +19 -0
- package/src/db/repository/cfg.js +72 -0
- package/src/db/repository/cochange.js +54 -0
- package/src/db/repository/complexity.js +20 -0
- package/src/db/repository/dataflow.js +17 -0
- package/src/db/repository/edges.js +281 -0
- package/src/db/repository/embeddings.js +51 -0
- package/src/db/repository/graph-read.js +59 -0
- package/src/db/repository/index.js +43 -0
- package/src/db/repository/nodes.js +247 -0
- package/src/db.js +40 -1
- package/src/embedder.js +14 -34
- package/src/export.js +1 -1
- package/src/extractors/javascript.js +130 -5
- package/src/flow.js +2 -70
- package/src/index.js +30 -20
- package/src/{result-formatter.js → infrastructure/result-formatter.js} +1 -1
- package/src/kinds.js +1 -0
- package/src/manifesto.js +0 -76
- package/src/native.js +31 -9
- package/src/owners.js +1 -56
- package/src/parser.js +53 -2
- package/src/queries-cli.js +1 -1
- package/src/queries.js +79 -280
- package/src/sequence.js +5 -44
- package/src/structure.js +16 -75
- package/src/triage.js +1 -54
- package/src/viewer.js +1 -1
- package/src/watcher.js +7 -4
- package/src/db/repository.js +0 -134
- /package/src/{test-filter.js → infrastructure/test-filter.js} +0 -0
package/src/builder.js
CHANGED
|
@@ -4,7 +4,17 @@ import path from 'node:path';
|
|
|
4
4
|
import { performance } from 'node:perf_hooks';
|
|
5
5
|
import { loadConfig } from './config.js';
|
|
6
6
|
import { EXTENSIONS, IGNORE_DIRS, normalizePath } from './constants.js';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
bulkNodeIdsByFile,
|
|
9
|
+
closeDb,
|
|
10
|
+
getBuildMeta,
|
|
11
|
+
getNodeId,
|
|
12
|
+
initSchema,
|
|
13
|
+
MIGRATIONS,
|
|
14
|
+
openDb,
|
|
15
|
+
purgeFilesData,
|
|
16
|
+
setBuildMeta,
|
|
17
|
+
} from './db.js';
|
|
8
18
|
import { readJournal, writeJournalHeader } from './journal.js';
|
|
9
19
|
import { debug, info, warn } from './logger.js';
|
|
10
20
|
import { loadNative } from './native.js';
|
|
@@ -350,91 +360,11 @@ function getChangedFiles(db, allFiles, rootDir) {
|
|
|
350
360
|
* @param {boolean} [options.purgeHashes=true] - Also delete file_hashes entries
|
|
351
361
|
*/
|
|
352
362
|
export function purgeFilesFromGraph(db, files, options = {}) {
|
|
353
|
-
|
|
354
|
-
if (!files || files.length === 0) return;
|
|
355
|
-
|
|
356
|
-
// Check if embeddings table exists
|
|
357
|
-
let hasEmbeddings = false;
|
|
358
|
-
try {
|
|
359
|
-
db.prepare('SELECT 1 FROM embeddings LIMIT 1').get();
|
|
360
|
-
hasEmbeddings = true;
|
|
361
|
-
} catch {
|
|
362
|
-
/* table doesn't exist */
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const deleteEmbeddingsForFile = hasEmbeddings
|
|
366
|
-
? db.prepare('DELETE FROM embeddings WHERE node_id IN (SELECT id FROM nodes WHERE file = ?)')
|
|
367
|
-
: null;
|
|
368
|
-
const deleteNodesForFile = db.prepare('DELETE FROM nodes WHERE file = ?');
|
|
369
|
-
const deleteEdgesForFile = db.prepare(`
|
|
370
|
-
DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file = @f)
|
|
371
|
-
OR target_id IN (SELECT id FROM nodes WHERE file = @f)
|
|
372
|
-
`);
|
|
373
|
-
const deleteMetricsForFile = db.prepare(
|
|
374
|
-
'DELETE FROM node_metrics WHERE node_id IN (SELECT id FROM nodes WHERE file = ?)',
|
|
375
|
-
);
|
|
376
|
-
let deleteComplexityForFile;
|
|
377
|
-
try {
|
|
378
|
-
deleteComplexityForFile = db.prepare(
|
|
379
|
-
'DELETE FROM function_complexity WHERE node_id IN (SELECT id FROM nodes WHERE file = ?)',
|
|
380
|
-
);
|
|
381
|
-
} catch {
|
|
382
|
-
deleteComplexityForFile = null;
|
|
383
|
-
}
|
|
384
|
-
let deleteDataflowForFile;
|
|
385
|
-
try {
|
|
386
|
-
deleteDataflowForFile = db.prepare(
|
|
387
|
-
'DELETE FROM dataflow WHERE source_id IN (SELECT id FROM nodes WHERE file = ?) OR target_id IN (SELECT id FROM nodes WHERE file = ?)',
|
|
388
|
-
);
|
|
389
|
-
} catch {
|
|
390
|
-
deleteDataflowForFile = null;
|
|
391
|
-
}
|
|
392
|
-
let deleteHashForFile;
|
|
393
|
-
if (purgeHashes) {
|
|
394
|
-
try {
|
|
395
|
-
deleteHashForFile = db.prepare('DELETE FROM file_hashes WHERE file = ?');
|
|
396
|
-
} catch {
|
|
397
|
-
deleteHashForFile = null;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
let deleteAstNodesForFile;
|
|
401
|
-
try {
|
|
402
|
-
deleteAstNodesForFile = db.prepare('DELETE FROM ast_nodes WHERE file = ?');
|
|
403
|
-
} catch {
|
|
404
|
-
deleteAstNodesForFile = null;
|
|
405
|
-
}
|
|
406
|
-
let deleteCfgForFile;
|
|
407
|
-
try {
|
|
408
|
-
deleteCfgForFile = db.prepare(
|
|
409
|
-
'DELETE FROM cfg_edges WHERE function_node_id IN (SELECT id FROM nodes WHERE file = ?)',
|
|
410
|
-
);
|
|
411
|
-
} catch {
|
|
412
|
-
deleteCfgForFile = null;
|
|
413
|
-
}
|
|
414
|
-
let deleteCfgBlocksForFile;
|
|
415
|
-
try {
|
|
416
|
-
deleteCfgBlocksForFile = db.prepare(
|
|
417
|
-
'DELETE FROM cfg_blocks WHERE function_node_id IN (SELECT id FROM nodes WHERE file = ?)',
|
|
418
|
-
);
|
|
419
|
-
} catch {
|
|
420
|
-
deleteCfgBlocksForFile = null;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
for (const relPath of files) {
|
|
424
|
-
deleteEmbeddingsForFile?.run(relPath);
|
|
425
|
-
deleteEdgesForFile.run({ f: relPath });
|
|
426
|
-
deleteMetricsForFile.run(relPath);
|
|
427
|
-
deleteComplexityForFile?.run(relPath);
|
|
428
|
-
deleteDataflowForFile?.run(relPath, relPath);
|
|
429
|
-
deleteAstNodesForFile?.run(relPath);
|
|
430
|
-
deleteCfgForFile?.run(relPath);
|
|
431
|
-
deleteCfgBlocksForFile?.run(relPath);
|
|
432
|
-
deleteNodesForFile.run(relPath);
|
|
433
|
-
if (purgeHashes) deleteHashForFile?.run(relPath);
|
|
434
|
-
}
|
|
363
|
+
purgeFilesData(db, files, options);
|
|
435
364
|
}
|
|
436
365
|
|
|
437
366
|
export async function buildGraph(rootDir, opts = {}) {
|
|
367
|
+
const _t_buildStart = performance.now();
|
|
438
368
|
rootDir = path.resolve(rootDir);
|
|
439
369
|
const dbPath = path.join(rootDir, '.codegraph', 'graph.db');
|
|
440
370
|
const db = openDb(dbPath);
|
|
@@ -677,9 +607,12 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
677
607
|
}
|
|
678
608
|
}
|
|
679
609
|
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
610
|
+
const getNodeIdStmt = {
|
|
611
|
+
get: (name, kind, file, line) => {
|
|
612
|
+
const id = getNodeId(db, name, kind, file, line);
|
|
613
|
+
return id != null ? { id } : undefined;
|
|
614
|
+
},
|
|
615
|
+
};
|
|
683
616
|
|
|
684
617
|
// Batch INSERT helpers — multi-value INSERTs reduce SQLite round-trips
|
|
685
618
|
const BATCH_CHUNK = 200;
|
|
@@ -736,6 +669,7 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
736
669
|
|
|
737
670
|
// ── Phase timing ────────────────────────────────────────────────────
|
|
738
671
|
const _t = {};
|
|
672
|
+
_t.setupMs = performance.now() - _t_buildStart;
|
|
739
673
|
|
|
740
674
|
// ── Unified parse via parseFilesAuto ───────────────────────────────
|
|
741
675
|
const filePaths = filesToParse.map((item) => item.file);
|
|
@@ -752,7 +686,7 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
752
686
|
}
|
|
753
687
|
|
|
754
688
|
// Bulk-fetch all node IDs for a file in one query (replaces per-node getNodeId calls)
|
|
755
|
-
const bulkGetNodeIds =
|
|
689
|
+
const bulkGetNodeIds = { all: (file) => bulkNodeIdsByFile(db, file) };
|
|
756
690
|
|
|
757
691
|
const insertAll = db.transaction(() => {
|
|
758
692
|
// Phase 1: Batch insert all file nodes + definitions + exports
|
|
@@ -1032,16 +966,22 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
1032
966
|
for (const [relPath, symbols] of fileSymbols) {
|
|
1033
967
|
// Skip barrel-only files — loaded for resolution, edges already in DB
|
|
1034
968
|
if (barrelOnlyFiles.has(relPath)) continue;
|
|
1035
|
-
const fileNodeRow =
|
|
969
|
+
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
1036
970
|
if (!fileNodeRow) continue;
|
|
1037
971
|
const fileNodeId = fileNodeRow.id;
|
|
1038
972
|
|
|
1039
973
|
// Import edges
|
|
1040
974
|
for (const imp of symbols.imports) {
|
|
1041
975
|
const resolvedPath = getResolved(path.join(rootDir, relPath), imp.source);
|
|
1042
|
-
const targetRow =
|
|
976
|
+
const targetRow = getNodeIdStmt.get(resolvedPath, 'file', resolvedPath, 0);
|
|
1043
977
|
if (targetRow) {
|
|
1044
|
-
const edgeKind = imp.reexport
|
|
978
|
+
const edgeKind = imp.reexport
|
|
979
|
+
? 'reexports'
|
|
980
|
+
: imp.typeOnly
|
|
981
|
+
? 'imports-type'
|
|
982
|
+
: imp.dynamicImport
|
|
983
|
+
? 'dynamic-imports'
|
|
984
|
+
: 'imports';
|
|
1045
985
|
allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0]);
|
|
1046
986
|
|
|
1047
987
|
if (!imp.reexport && isBarrelFile(resolvedPath)) {
|
|
@@ -1055,12 +995,16 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
1055
995
|
!resolvedSources.has(actualSource)
|
|
1056
996
|
) {
|
|
1057
997
|
resolvedSources.add(actualSource);
|
|
1058
|
-
const actualRow =
|
|
998
|
+
const actualRow = getNodeIdStmt.get(actualSource, 'file', actualSource, 0);
|
|
1059
999
|
if (actualRow) {
|
|
1060
1000
|
allEdgeRows.push([
|
|
1061
1001
|
fileNodeId,
|
|
1062
1002
|
actualRow.id,
|
|
1063
|
-
edgeKind === 'imports-type'
|
|
1003
|
+
edgeKind === 'imports-type'
|
|
1004
|
+
? 'imports-type'
|
|
1005
|
+
: edgeKind === 'dynamic-imports'
|
|
1006
|
+
? 'dynamic-imports'
|
|
1007
|
+
: 'imports',
|
|
1064
1008
|
0.9,
|
|
1065
1009
|
0,
|
|
1066
1010
|
]);
|
|
@@ -1078,7 +1022,7 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
1078
1022
|
const nativeFiles = [];
|
|
1079
1023
|
for (const [relPath, symbols] of fileSymbols) {
|
|
1080
1024
|
if (barrelOnlyFiles.has(relPath)) continue;
|
|
1081
|
-
const fileNodeRow =
|
|
1025
|
+
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
1082
1026
|
if (!fileNodeRow) continue;
|
|
1083
1027
|
|
|
1084
1028
|
// Pre-resolve imported names (including barrel resolution)
|
|
@@ -1120,7 +1064,7 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
1120
1064
|
// JS fallback — call/receiver/extends/implements edges
|
|
1121
1065
|
for (const [relPath, symbols] of fileSymbols) {
|
|
1122
1066
|
if (barrelOnlyFiles.has(relPath)) continue;
|
|
1123
|
-
const fileNodeRow =
|
|
1067
|
+
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
1124
1068
|
if (!fileNodeRow) continue;
|
|
1125
1069
|
|
|
1126
1070
|
// Build import name -> target file mapping
|
|
@@ -1145,14 +1089,14 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
1145
1089
|
if (call.line <= end) {
|
|
1146
1090
|
const span = end - def.line;
|
|
1147
1091
|
if (span < callerSpan) {
|
|
1148
|
-
const row =
|
|
1092
|
+
const row = getNodeIdStmt.get(def.name, def.kind, relPath, def.line);
|
|
1149
1093
|
if (row) {
|
|
1150
1094
|
caller = row;
|
|
1151
1095
|
callerSpan = span;
|
|
1152
1096
|
}
|
|
1153
1097
|
}
|
|
1154
1098
|
} else if (!caller) {
|
|
1155
|
-
const row =
|
|
1099
|
+
const row = getNodeIdStmt.get(def.name, def.kind, relPath, def.line);
|
|
1156
1100
|
if (row) caller = row;
|
|
1157
1101
|
}
|
|
1158
1102
|
}
|
|
@@ -1393,96 +1337,30 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
1393
1337
|
}
|
|
1394
1338
|
}
|
|
1395
1339
|
|
|
1396
|
-
//
|
|
1397
|
-
|
|
1398
|
-
|
|
1340
|
+
// ── Unified AST analysis engine ──────────────────────────────────────
|
|
1341
|
+
// Replaces 4 sequential buildXxx calls with one coordinated pass.
|
|
1342
|
+
{
|
|
1343
|
+
const { runAnalyses } = await import('./ast-analysis/engine.js');
|
|
1399
1344
|
try {
|
|
1400
|
-
const
|
|
1401
|
-
|
|
1345
|
+
const analysisTiming = await runAnalyses(db, astComplexitySymbols, rootDir, opts, engineOpts);
|
|
1346
|
+
_t.astMs = analysisTiming.astMs;
|
|
1347
|
+
_t.complexityMs = analysisTiming.complexityMs;
|
|
1348
|
+
_t.cfgMs = analysisTiming.cfgMs;
|
|
1349
|
+
_t.dataflowMs = analysisTiming.dataflowMs;
|
|
1402
1350
|
} catch (err) {
|
|
1403
|
-
debug(`
|
|
1351
|
+
debug(`Unified analysis engine failed: ${err.message}`);
|
|
1404
1352
|
}
|
|
1405
1353
|
}
|
|
1406
|
-
_t.astMs = performance.now() - _t.ast0;
|
|
1407
1354
|
|
|
1408
|
-
|
|
1409
|
-
_t.complexity0 = performance.now();
|
|
1410
|
-
if (opts.complexity !== false) {
|
|
1411
|
-
try {
|
|
1412
|
-
const { buildComplexityMetrics } = await import('./complexity.js');
|
|
1413
|
-
await buildComplexityMetrics(db, astComplexitySymbols, rootDir, engineOpts);
|
|
1414
|
-
} catch (err) {
|
|
1415
|
-
debug(`Complexity analysis failed: ${err.message}`);
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
_t.complexityMs = performance.now() - _t.complexity0;
|
|
1419
|
-
|
|
1420
|
-
// Pre-parse files missing WASM trees (native builds) so CFG + dataflow
|
|
1421
|
-
// share a single parse pass instead of each creating parsers independently.
|
|
1422
|
-
// Skip entirely when native engine already provides CFG + dataflow data.
|
|
1423
|
-
if (opts.cfg !== false || opts.dataflow !== false) {
|
|
1424
|
-
const needsCfg = opts.cfg !== false;
|
|
1425
|
-
const needsDataflow = opts.dataflow !== false;
|
|
1426
|
-
|
|
1427
|
-
let needsWasmTrees = false;
|
|
1428
|
-
for (const [, symbols] of astComplexitySymbols) {
|
|
1429
|
-
if (symbols._tree) continue; // already has a tree
|
|
1430
|
-
// CFG: need tree if any function/method def lacks native CFG
|
|
1431
|
-
if (needsCfg) {
|
|
1432
|
-
const fnDefs = (symbols.definitions || []).filter(
|
|
1433
|
-
(d) => (d.kind === 'function' || d.kind === 'method') && d.line,
|
|
1434
|
-
);
|
|
1435
|
-
if (
|
|
1436
|
-
fnDefs.length > 0 &&
|
|
1437
|
-
!fnDefs.every((d) => d.cfg === null || Array.isArray(d.cfg?.blocks))
|
|
1438
|
-
) {
|
|
1439
|
-
needsWasmTrees = true;
|
|
1440
|
-
break;
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
// Dataflow: need tree if file lacks native dataflow
|
|
1444
|
-
if (needsDataflow && !symbols.dataflow) {
|
|
1445
|
-
needsWasmTrees = true;
|
|
1446
|
-
break;
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1355
|
+
_t.finalize0 = performance.now();
|
|
1449
1356
|
|
|
1450
|
-
|
|
1357
|
+
// Release any remaining cached WASM trees — call .delete() to free WASM memory
|
|
1358
|
+
for (const [, symbols] of allSymbols) {
|
|
1359
|
+
if (symbols._tree && typeof symbols._tree.delete === 'function') {
|
|
1451
1360
|
try {
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
} catch (err) {
|
|
1455
|
-
debug(`WASM pre-parse failed: ${err.message}`);
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
// CFG analysis (skip with --no-cfg)
|
|
1461
|
-
if (opts.cfg !== false) {
|
|
1462
|
-
_t.cfg0 = performance.now();
|
|
1463
|
-
try {
|
|
1464
|
-
const { buildCFGData } = await import('./cfg.js');
|
|
1465
|
-
await buildCFGData(db, astComplexitySymbols, rootDir, engineOpts);
|
|
1466
|
-
} catch (err) {
|
|
1467
|
-
debug(`CFG analysis failed: ${err.message}`);
|
|
1468
|
-
}
|
|
1469
|
-
_t.cfgMs = performance.now() - _t.cfg0;
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
// Dataflow analysis (skip with --no-dataflow)
|
|
1473
|
-
if (opts.dataflow !== false) {
|
|
1474
|
-
_t.dataflow0 = performance.now();
|
|
1475
|
-
try {
|
|
1476
|
-
const { buildDataflowEdges } = await import('./dataflow.js');
|
|
1477
|
-
await buildDataflowEdges(db, astComplexitySymbols, rootDir, engineOpts);
|
|
1478
|
-
} catch (err) {
|
|
1479
|
-
debug(`Dataflow analysis failed: ${err.message}`);
|
|
1361
|
+
symbols._tree.delete();
|
|
1362
|
+
} catch {}
|
|
1480
1363
|
}
|
|
1481
|
-
_t.dataflowMs = performance.now() - _t.dataflow0;
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
// Release any remaining cached WASM trees for GC
|
|
1485
|
-
for (const [, symbols] of allSymbols) {
|
|
1486
1364
|
symbols._tree = null;
|
|
1487
1365
|
symbols._langId = null;
|
|
1488
1366
|
}
|
|
@@ -1587,8 +1465,11 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
1587
1465
|
}
|
|
1588
1466
|
}
|
|
1589
1467
|
|
|
1468
|
+
_t.finalizeMs = performance.now() - _t.finalize0;
|
|
1469
|
+
|
|
1590
1470
|
return {
|
|
1591
1471
|
phases: {
|
|
1472
|
+
setupMs: +_t.setupMs.toFixed(1),
|
|
1592
1473
|
parseMs: +_t.parseMs.toFixed(1),
|
|
1593
1474
|
insertMs: +_t.insertMs.toFixed(1),
|
|
1594
1475
|
resolveMs: +_t.resolveMs.toFixed(1),
|
|
@@ -1599,6 +1480,7 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
1599
1480
|
complexityMs: +_t.complexityMs.toFixed(1),
|
|
1600
1481
|
...(_t.cfgMs != null && { cfgMs: +_t.cfgMs.toFixed(1) }),
|
|
1601
1482
|
...(_t.dataflowMs != null && { dataflowMs: +_t.dataflowMs.toFixed(1) }),
|
|
1483
|
+
finalizeMs: +_t.finalizeMs.toFixed(1),
|
|
1602
1484
|
},
|
|
1603
1485
|
};
|
|
1604
1486
|
}
|