@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
- ## Features
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
- - **ManifestGraph**: Build and manage dependency graphs from dbt manifests
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
- // Get summary statistics
41
+ // Summary statistics
27
42
  const summary = graph.getSummary();
28
- console.log(`Total nodes: ${summary.total_nodes}`);
29
- console.log(`Has cycles: ${summary.has_cycles}`);
43
+ console.log(`Nodes: ${summary.total_nodes}, Cycles: ${summary.has_cycles}`);
30
44
 
31
- // Get upstream dependencies
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
- - `getGraph()`: Get the underlying graphology graph
43
- - `getSummary()`: Get summary statistics
44
- - `getUpstream(nodeId)`: Get all upstream dependencies
45
- - `getDownstream(nodeId)`: Get all downstream dependents
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
- - `getSummary()`: Get execution summary with critical path
50
- - `getNodeExecutions()`: Get execution details for each node
51
- - `getGanttData()`: Get Gantt chart data for visualization
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: typeof attributes.compiled_code === "string"
187
- ? attributes.compiled_code
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.2",
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.2"
32
+ "dbt-artifacts-parser": "0.3.4"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/node": "^25.5.0",