@dbt-tools/core 0.3.2 → 0.3.4
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
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
# @dbt-tools/core
|
|
2
2
|
|
|
3
|
-
Core library for dbt artifact graph management and analysis.
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Core library for dbt artifact graph management and analysis. Provides the analysis engine used by [`@dbt-tools/cli`](../cli/README.md) and [`@dbt-tools/web`](../web/README.md).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
graph TD
|
|
11
|
+
AF[dbt-artifacts-parser\nparseManifest · parseRunResults]
|
|
12
|
+
AF --> AL[ArtifactLoader]
|
|
13
|
+
AL --> MG[ManifestGraph\ngraphology DAG]
|
|
14
|
+
AL --> EA[ExecutionAnalyzer\ncritical path · Gantt]
|
|
15
|
+
MG --> DS[DependencyService\nupstream · downstream · build order]
|
|
16
|
+
MG --> GE[GraphExport\nJSON · DOT · GEXF]
|
|
17
|
+
EA --> OF[OutputFormatter\nfield filtering · JSON/text]
|
|
18
|
+
OF --> FF[FieldFilter]
|
|
19
|
+
```
|
|
6
20
|
|
|
7
|
-
|
|
8
|
-
- **ExecutionAnalyzer**: Analyze execution results and calculate critical paths
|
|
9
|
-
- **High Performance**: Optimized for large manifests with 100k+ nodes
|
|
21
|
+
---
|
|
10
22
|
|
|
11
23
|
## Installation
|
|
12
24
|
|
|
@@ -14,38 +26,166 @@ Core library for dbt artifact graph management and analysis.
|
|
|
14
26
|
pnpm add @dbt-tools/core
|
|
15
27
|
```
|
|
16
28
|
|
|
29
|
+
---
|
|
30
|
+
|
|
17
31
|
## Usage
|
|
18
32
|
|
|
19
33
|
```typescript
|
|
20
34
|
import { parseManifest } from "dbt-artifacts-parser/manifest";
|
|
21
|
-
import { ManifestGraph } from "@dbt-tools/core";
|
|
35
|
+
import { ManifestGraph, ExecutionAnalyzer } from "@dbt-tools/core";
|
|
22
36
|
|
|
37
|
+
// Build dependency graph
|
|
23
38
|
const manifest = parseManifest(manifestJson);
|
|
24
39
|
const graph = new ManifestGraph(manifest);
|
|
25
40
|
|
|
26
|
-
//
|
|
41
|
+
// Summary statistics
|
|
27
42
|
const summary = graph.getSummary();
|
|
28
|
-
console.log(`
|
|
29
|
-
console.log(`Has cycles: ${summary.has_cycles}`);
|
|
43
|
+
console.log(`Nodes: ${summary.total_nodes}, Cycles: ${summary.has_cycles}`);
|
|
30
44
|
|
|
31
|
-
//
|
|
45
|
+
// Dependency traversal
|
|
32
46
|
const upstream = graph.getUpstream("model.my_project.my_model");
|
|
33
|
-
|
|
34
|
-
// Get downstream dependents
|
|
35
47
|
const downstream = graph.getDownstream("model.my_project.my_model");
|
|
48
|
+
|
|
49
|
+
// Execution analysis
|
|
50
|
+
const analyzer = new ExecutionAnalyzer(runResults, manifest);
|
|
51
|
+
const report = analyzer.getSummary();
|
|
52
|
+
console.log(
|
|
53
|
+
`Critical path: ${report.critical_path.map((n) => n.name).join(" → ")}`,
|
|
54
|
+
);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Exports
|
|
60
|
+
|
|
61
|
+
### Node.js (default)
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import {
|
|
65
|
+
// Analysis
|
|
66
|
+
ManifestGraph,
|
|
67
|
+
ExecutionAnalyzer,
|
|
68
|
+
DependencyService,
|
|
69
|
+
SqlAnalyzer,
|
|
70
|
+
RunResultsSearch,
|
|
71
|
+
AnalysisSnapshot,
|
|
72
|
+
// I/O
|
|
73
|
+
ArtifactLoader,
|
|
74
|
+
// Validation
|
|
75
|
+
InputValidator,
|
|
76
|
+
// Formatting
|
|
77
|
+
OutputFormatter,
|
|
78
|
+
FieldFilter,
|
|
79
|
+
GraphExport,
|
|
80
|
+
// Errors
|
|
81
|
+
ErrorHandler,
|
|
82
|
+
// Introspection
|
|
83
|
+
SchemaGenerator,
|
|
84
|
+
} from "@dbt-tools/core";
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Browser (no Node.js dependencies)
|
|
88
|
+
|
|
89
|
+
For use in browser environments (e.g. web workers in `@dbt-tools/web`):
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import {
|
|
93
|
+
ManifestGraph,
|
|
94
|
+
ExecutionAnalyzer,
|
|
95
|
+
RunResultsSearch,
|
|
96
|
+
AnalysisSnapshot,
|
|
97
|
+
} from "@dbt-tools/core/browser";
|
|
36
98
|
```
|
|
37
99
|
|
|
100
|
+
---
|
|
101
|
+
|
|
38
102
|
## API
|
|
39
103
|
|
|
40
104
|
### ManifestGraph
|
|
41
105
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
106
|
+
Builds a directed acyclic graph (DAG) from a parsed dbt manifest using [graphology](https://graphology.github.io/).
|
|
107
|
+
|
|
108
|
+
| Method | Description |
|
|
109
|
+
| ------------------------------- | ----------------------------------------------------------------------- |
|
|
110
|
+
| `getGraph()` | Returns the underlying `graphology` `DirectedGraph` |
|
|
111
|
+
| `getSummary()` | Returns `{ total_nodes, total_edges, has_cycles, node_counts_by_type }` |
|
|
112
|
+
| `getUpstream(nodeId, depth?)` | All nodes that `nodeId` depends on (transitive, optional depth limit) |
|
|
113
|
+
| `getDownstream(nodeId, depth?)` | All nodes that depend on `nodeId` (transitive, optional depth limit) |
|
|
46
114
|
|
|
47
115
|
### ExecutionAnalyzer
|
|
48
116
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
117
|
+
Analyzes dbt execution results to compute critical paths and bottlenecks.
|
|
118
|
+
|
|
119
|
+
| Method | Description |
|
|
120
|
+
| --------------------- | ----------------------------------------------------------------------- |
|
|
121
|
+
| `getSummary()` | Returns execution summary with critical path, total time, slowest nodes |
|
|
122
|
+
| `getNodeExecutions()` | Per-node execution details (status, duration, thread) |
|
|
123
|
+
| `getGanttData()` | Gantt chart data for timeline visualization |
|
|
124
|
+
|
|
125
|
+
### DependencyService
|
|
126
|
+
|
|
127
|
+
Higher-level dependency queries with build-order support.
|
|
128
|
+
|
|
129
|
+
| Method | Description |
|
|
130
|
+
| -------------------------------------- | --------------------------------------------- |
|
|
131
|
+
| `getUpstreamBuildOrder(nodeId)` | Topological ordering of upstream dependencies |
|
|
132
|
+
| `getDependencyTree(nodeId, direction)` | Nested tree structure of dependencies |
|
|
133
|
+
|
|
134
|
+
### ArtifactLoader
|
|
135
|
+
|
|
136
|
+
Loads dbt artifact files from disk.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { ArtifactLoader } from "@dbt-tools/core";
|
|
140
|
+
|
|
141
|
+
const loader = new ArtifactLoader({ targetDir: "./target" });
|
|
142
|
+
const manifest = await loader.loadManifest();
|
|
143
|
+
const runResults = await loader.loadRunResults();
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### InputValidator
|
|
147
|
+
|
|
148
|
+
Validates user-supplied strings against common injection patterns (path traversal, control characters, URL encoding tricks).
|
|
149
|
+
|
|
150
|
+
### OutputFormatter / FieldFilter
|
|
151
|
+
|
|
152
|
+
Formats analysis output as JSON or human-readable text. `FieldFilter` limits output to a specified set of fields (useful for reducing context window usage in AI agents).
|
|
153
|
+
|
|
154
|
+
### GraphExport
|
|
155
|
+
|
|
156
|
+
Exports the dependency graph in multiple formats:
|
|
157
|
+
|
|
158
|
+
- `json` — nodes and edges as JSON
|
|
159
|
+
- `dot` — Graphviz DOT format
|
|
160
|
+
- `gexf` — GEXF format (for Gephi and other tools)
|
|
161
|
+
|
|
162
|
+
### ErrorHandler
|
|
163
|
+
|
|
164
|
+
Standardized error wrapping with typed error codes (`VALIDATION_ERROR`, `FILE_NOT_FOUND`, `PARSE_ERROR`, `UNSUPPORTED_VERSION`, `UNKNOWN_ERROR`).
|
|
165
|
+
|
|
166
|
+
### SchemaGenerator
|
|
167
|
+
|
|
168
|
+
Runtime introspection — generates machine-readable schemas for CLI commands. Used by `@dbt-tools/cli schema`.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Performance
|
|
173
|
+
|
|
174
|
+
`ManifestGraph` uses graphology's adjacency-list representation and is optimized for large manifests with 100k+ nodes.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Development
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
pnpm build
|
|
182
|
+
pnpm test
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
See [CONTRIBUTING.md](../../../CONTRIBUTING.md) for the full developer guide.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
Apache License 2.0.
|
|
@@ -2,6 +2,7 @@ import type { ParsedManifest } from "dbt-artifacts-parser/manifest";
|
|
|
2
2
|
import type { ParsedRunResults } from "dbt-artifacts-parser/run_results";
|
|
3
3
|
import { type BottleneckResult } from "./run-results-search";
|
|
4
4
|
import { type ExecutionSummary } from "./execution-analyzer";
|
|
5
|
+
import { ManifestGraph } from "./manifest-graph";
|
|
5
6
|
export interface GanttItem {
|
|
6
7
|
unique_id: string;
|
|
7
8
|
name: string;
|
|
@@ -106,6 +107,10 @@ export interface DependencyPreview {
|
|
|
106
107
|
resourceType: string;
|
|
107
108
|
depth: number;
|
|
108
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Direct (1-hop) dependency edges only. `upstream` / `downstream` preview at
|
|
112
|
+
* most 8 neighbors; counts are total direct neighbors, not transitive closure.
|
|
113
|
+
*/
|
|
109
114
|
export interface ResourceConnectionSummary {
|
|
110
115
|
upstreamCount: number;
|
|
111
116
|
downstreamCount: number;
|
|
@@ -141,5 +146,6 @@ export interface AnalysisSnapshotBuildTimings {
|
|
|
141
146
|
export declare function buildAnalysisSnapshotFromParsedArtifacts(manifestJson: Record<string, unknown>, runResultsJson: Record<string, unknown>, manifest: ParsedManifest, runResults: ParsedRunResults): {
|
|
142
147
|
analysis: AnalysisSnapshot;
|
|
143
148
|
timings: AnalysisSnapshotBuildTimings;
|
|
149
|
+
graph: ManifestGraph;
|
|
144
150
|
};
|
|
145
151
|
export declare function buildAnalysisSnapshotFromArtifacts(manifestJson: Record<string, unknown>, runResultsJson: Record<string, unknown>): Promise<AnalysisSnapshot>;
|
|
@@ -183,10 +183,8 @@ function buildResourcesAndDependencyIndex(graph, executionById) {
|
|
|
183
183
|
description: typeof attributes.description === "string"
|
|
184
184
|
? attributes.description
|
|
185
185
|
: null,
|
|
186
|
-
compiledCode:
|
|
187
|
-
|
|
188
|
-
: null,
|
|
189
|
-
rawCode: typeof attributes.raw_code === "string" ? attributes.raw_code : null,
|
|
186
|
+
compiledCode: null,
|
|
187
|
+
rawCode: null,
|
|
190
188
|
definition: buildResourceDefinition(String(attributes.resource_type || "unknown"), attributes),
|
|
191
189
|
status: (execution === null || execution === void 0 ? void 0 : execution.status) ? statusLabel(execution.status) : null,
|
|
192
190
|
statusTone: statusTone(execution === null || execution === void 0 ? void 0 : execution.status),
|
|
@@ -195,8 +193,8 @@ function buildResourcesAndDependencyIndex(graph, executionById) {
|
|
|
195
193
|
: null,
|
|
196
194
|
threadId: typeof (execution === null || execution === void 0 ? void 0 : execution.thread_id) === "string" ? execution.thread_id : null,
|
|
197
195
|
});
|
|
198
|
-
const upstream = graph.getUpstream(uniqueId);
|
|
199
|
-
const downstream = graph.getDownstream(uniqueId);
|
|
196
|
+
const upstream = graph.getUpstream(uniqueId, 1);
|
|
197
|
+
const downstream = graph.getDownstream(uniqueId, 1);
|
|
200
198
|
dependencyIndex[uniqueId] = {
|
|
201
199
|
upstreamCount: upstream.length,
|
|
202
200
|
downstreamCount: downstream.length,
|
|
@@ -602,6 +600,7 @@ function buildAnalysisSnapshotFromParsedArtifacts(manifestJson, runResultsJson,
|
|
|
602
600
|
graphBuildMs,
|
|
603
601
|
snapshotBuildMs: now() - snapshotStart,
|
|
604
602
|
},
|
|
603
|
+
graph,
|
|
605
604
|
};
|
|
606
605
|
}
|
|
607
606
|
async function buildAnalysisSnapshotFromArtifacts(manifestJson, runResultsJson) {
|
|
@@ -72,8 +72,7 @@ class FieldFilter {
|
|
|
72
72
|
if (!Object.prototype.hasOwnProperty.call(current, part)) {
|
|
73
73
|
return undefined;
|
|
74
74
|
}
|
|
75
|
-
// nosemgrep: javascript.lang.security.audit.prototype-pollution.prototype-pollution-loop
|
|
76
|
-
// Part is validated by isUnsafeKey above; hasOwnProperty ensures we don't read proto chain.
|
|
75
|
+
// nosemgrep: javascript.lang.security.audit.prototype-pollution.prototype-pollution-loop.prototype-pollution-loop — part blocked by isUnsafeKey; hasOwnProperty guards proto chain.
|
|
77
76
|
current = current[part];
|
|
78
77
|
}
|
|
79
78
|
return current;
|
|
@@ -96,8 +95,7 @@ class FieldFilter {
|
|
|
96
95
|
current[part] === null) {
|
|
97
96
|
current[part] = Object.create(null);
|
|
98
97
|
}
|
|
99
|
-
// nosemgrep: javascript.lang.security.audit.prototype-pollution.prototype-pollution-loop
|
|
100
|
-
// Part is validated by isUnsafeKey check above; assignment creates own property only.
|
|
98
|
+
// nosemgrep: javascript.lang.security.audit.prototype-pollution.prototype-pollution-loop.prototype-pollution-loop — part blocked by isUnsafeKey; assignment is own property only.
|
|
101
99
|
current = current[part];
|
|
102
100
|
}
|
|
103
101
|
const lastPart = parts[parts.length - 1];
|
|
@@ -52,9 +52,11 @@ function resolveManifestPath(manifestPath, targetDir) {
|
|
|
52
52
|
if (manifestPath.endsWith(".json")) {
|
|
53
53
|
return (0, input_validator_1.resolveSafePath)(manifestPath);
|
|
54
54
|
}
|
|
55
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal — resolveSafePath validates manifestPath before join.
|
|
55
56
|
return path.join((0, input_validator_1.resolveSafePath)(manifestPath), MANIFEST_FILE);
|
|
56
57
|
}
|
|
57
58
|
const effectiveTargetDir = targetDir || process.env.DBT_TARGET_DIR || DEFAULT_TARGET_DIR;
|
|
59
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal — resolveSafePath validates effectiveTargetDir before join.
|
|
58
60
|
return path.join((0, input_validator_1.resolveSafePath)(effectiveTargetDir), MANIFEST_FILE);
|
|
59
61
|
}
|
|
60
62
|
function resolveRunResultsPath(runResultsPath, targetDir) {
|
|
@@ -62,9 +64,11 @@ function resolveRunResultsPath(runResultsPath, targetDir) {
|
|
|
62
64
|
if (runResultsPath.endsWith(".json")) {
|
|
63
65
|
return (0, input_validator_1.resolveSafePath)(runResultsPath);
|
|
64
66
|
}
|
|
67
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal — resolveSafePath validates runResultsPath before join.
|
|
65
68
|
return path.join((0, input_validator_1.resolveSafePath)(runResultsPath), RUN_RESULTS_FILE);
|
|
66
69
|
}
|
|
67
70
|
const effectiveTargetDir = targetDir || process.env.DBT_TARGET_DIR || DEFAULT_TARGET_DIR;
|
|
71
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal — resolveSafePath validates effectiveTargetDir before join.
|
|
68
72
|
return path.join((0, input_validator_1.resolveSafePath)(effectiveTargetDir), RUN_RESULTS_FILE);
|
|
69
73
|
}
|
|
70
74
|
function resolveCatalogPath(catalogPath, targetDir) {
|
|
@@ -72,9 +76,11 @@ function resolveCatalogPath(catalogPath, targetDir) {
|
|
|
72
76
|
if (catalogPath.endsWith(".json")) {
|
|
73
77
|
return (0, input_validator_1.resolveSafePath)(catalogPath);
|
|
74
78
|
}
|
|
79
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal — resolveSafePath validates catalogPath before join.
|
|
75
80
|
return path.join((0, input_validator_1.resolveSafePath)(catalogPath), CATALOG_FILE);
|
|
76
81
|
}
|
|
77
82
|
const effectiveTargetDir = targetDir || process.env.DBT_TARGET_DIR || DEFAULT_TARGET_DIR;
|
|
83
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal — resolveSafePath validates effectiveTargetDir before join.
|
|
78
84
|
return path.join((0, input_validator_1.resolveSafePath)(effectiveTargetDir), CATALOG_FILE);
|
|
79
85
|
}
|
|
80
86
|
/**
|
|
@@ -66,6 +66,7 @@ function validateSafePath(pathInput) {
|
|
|
66
66
|
*/
|
|
67
67
|
function resolveSafePath(pathInput) {
|
|
68
68
|
validateSafePath(pathInput);
|
|
69
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal — validateSafePath rejects traversal before resolve.
|
|
69
70
|
return path.resolve(pathInput);
|
|
70
71
|
}
|
|
71
72
|
/**
|
|
@@ -74,7 +75,10 @@ function resolveSafePath(pathInput) {
|
|
|
74
75
|
*/
|
|
75
76
|
function assertPathUnderBase(resolvedPath, baseDir) {
|
|
76
77
|
validateSafePath(resolvedPath);
|
|
78
|
+
validateSafePath(baseDir);
|
|
79
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal — validateSafePath on both args before resolve.
|
|
77
80
|
const absPath = path.resolve(resolvedPath);
|
|
81
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal — validateSafePath on both args before resolve.
|
|
78
82
|
const baseAbs = path.resolve(baseDir);
|
|
79
83
|
const relative = path.relative(baseAbs, absPath);
|
|
80
84
|
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dbt-tools/core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Core library for dbt artifact graph management and analysis",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dbt",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"graphology": "^0.26.0",
|
|
30
30
|
"graphology-dag": "^0.4.1",
|
|
31
31
|
"node-sql-parser": "^5.4.0",
|
|
32
|
-
"dbt-artifacts-parser": "0.3.
|
|
32
|
+
"dbt-artifacts-parser": "0.3.4"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "^25.5.0",
|