@duckcodeailabs/dql-core 1.14.1 → 1.14.3-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contracts/analytical.d.ts +30 -4
- package/dist/contracts/analytical.d.ts.map +1 -1
- package/dist/contracts/analytical.js +36 -1
- package/dist/contracts/analytical.js.map +1 -1
- package/dist/lineage/sql-parser.d.ts +10 -0
- package/dist/lineage/sql-parser.d.ts.map +1 -1
- package/dist/lineage/sql-parser.js +11 -1
- package/dist/lineage/sql-parser.js.map +1 -1
- package/dist/manifest/builder.d.ts.map +1 -1
- package/dist/manifest/builder.js +90 -34
- package/dist/manifest/builder.js.map +1 -1
- package/dist/semantic/semantic-layer.d.ts.map +1 -1
- package/dist/semantic/semantic-layer.js +14 -1
- package/dist/semantic/semantic-layer.js.map +1 -1
- package/package.json +1 -1
package/dist/manifest/builder.js
CHANGED
|
@@ -36,6 +36,10 @@ import { loadManifestKnowledgeSkills, manifestKnowledgeSkillInputFiles } from '.
|
|
|
36
36
|
export function collectInputFiles(options) {
|
|
37
37
|
const { projectRoot } = options;
|
|
38
38
|
const files = new Set();
|
|
39
|
+
// Cache overlapping directory walks here as well. `sync dbt` asks for these
|
|
40
|
+
// tracked files before a cache lookup, so repeated block/domain roots must
|
|
41
|
+
// not turn a warm-cache check into several 4k-file traversals.
|
|
42
|
+
const scanCache = createManifestScanCache();
|
|
39
43
|
const configPath = join(projectRoot, 'dql.config.json');
|
|
40
44
|
if (existsSync(configPath))
|
|
41
45
|
files.add(configPath);
|
|
@@ -46,27 +50,27 @@ export function collectInputFiles(options) {
|
|
|
46
50
|
}
|
|
47
51
|
const blockDirs = ['blocks', 'domains', 'dashboards', 'workbooks', ...(options.extraBlockDirs ?? [])];
|
|
48
52
|
for (const dir of blockDirs) {
|
|
49
|
-
for (const f of scanFilesRecursive(join(projectRoot, dir), ['.dql']))
|
|
53
|
+
for (const f of scanFilesRecursive(join(projectRoot, dir), ['.dql'], scanCache))
|
|
50
54
|
files.add(f);
|
|
51
55
|
}
|
|
52
56
|
const businessViewDirs = ['blocks', 'business-views', 'domains', 'dashboards', 'workbooks', ...(options.extraBlockDirs ?? [])];
|
|
53
57
|
for (const dir of businessViewDirs) {
|
|
54
|
-
for (const f of scanFilesRecursive(join(projectRoot, dir), ['.dql']))
|
|
58
|
+
for (const f of scanFilesRecursive(join(projectRoot, dir), ['.dql'], scanCache))
|
|
55
59
|
files.add(f);
|
|
56
60
|
}
|
|
57
61
|
const termDirs = ['terms', 'blocks', 'business-views', 'domains', 'dashboards', 'workbooks', ...(options.extraBlockDirs ?? [])];
|
|
58
62
|
for (const dir of termDirs) {
|
|
59
|
-
for (const f of scanFilesRecursive(join(projectRoot, dir), ['.dql']))
|
|
63
|
+
for (const f of scanFilesRecursive(join(projectRoot, dir), ['.dql'], scanCache))
|
|
60
64
|
files.add(f);
|
|
61
65
|
}
|
|
62
66
|
const notebookDirs = ['notebooks', 'domains', 'blocks', 'dashboards', 'workbooks', ...(options.extraNotebookDirs ?? [])];
|
|
63
67
|
for (const dir of notebookDirs) {
|
|
64
|
-
for (const f of scanFilesRecursive(join(projectRoot, dir), ['.dqlnb']))
|
|
68
|
+
for (const f of scanFilesRecursive(join(projectRoot, dir), ['.dqlnb'], scanCache))
|
|
65
69
|
files.add(f);
|
|
66
70
|
}
|
|
67
71
|
const semanticDir = resolveSemanticPath(projectRoot, config);
|
|
68
72
|
if (existsSync(semanticDir)) {
|
|
69
|
-
for (const f of scanFilesRecursive(semanticDir, ['.yaml', '.yml']))
|
|
73
|
+
for (const f of scanFilesRecursive(semanticDir, ['.yaml', '.yml'], scanCache))
|
|
70
74
|
files.add(f);
|
|
71
75
|
}
|
|
72
76
|
if (options.dbtManifestPath && existsSync(options.dbtManifestPath)) {
|
|
@@ -79,7 +83,7 @@ export function collectInputFiles(options) {
|
|
|
79
83
|
files.add(semanticManifestPath);
|
|
80
84
|
}
|
|
81
85
|
if (config.manifestVersion === 3 && config.modeling?.mode === 'dbt-first') {
|
|
82
|
-
for (const f of scanFilesRecursive(join(projectRoot, 'domains'), ['.yaml', '.yml']))
|
|
86
|
+
for (const f of scanFilesRecursive(join(projectRoot, 'domains'), ['.yaml', '.yml'], scanCache))
|
|
83
87
|
files.add(f);
|
|
84
88
|
const registry = loadDomainPackageRegistry(projectRoot);
|
|
85
89
|
for (const f of manifestKnowledgeSkillInputFiles(projectRoot, registry))
|
|
@@ -98,6 +102,12 @@ export function collectInputFiles(options) {
|
|
|
98
102
|
export function buildManifest(options) {
|
|
99
103
|
const { projectRoot, dqlVersion = '0.6.0' } = options;
|
|
100
104
|
const diagnostics = [];
|
|
105
|
+
// A large project commonly stores blocks, terms, and domains under the same
|
|
106
|
+
// directory tree. The manifest scanner needs distinct declaration passes,
|
|
107
|
+
// but it must not re-walk that tree or re-read the same source three times
|
|
108
|
+
// during one build. Source text is always read freshly; unchanged text can
|
|
109
|
+
// reuse a bounded parsed AST across adjacent builds.
|
|
110
|
+
const scanCache = createManifestScanCache(projectRoot);
|
|
101
111
|
// Load project config
|
|
102
112
|
const config = loadProjectConfig(projectRoot);
|
|
103
113
|
const projectName = config.project ?? 'dql-project';
|
|
@@ -126,20 +136,20 @@ export function buildManifest(options) {
|
|
|
126
136
|
const packageRegistry = dbtFirstV3 ? loadDomainPackageRegistry(projectRoot) : undefined;
|
|
127
137
|
const domains = packageRegistry
|
|
128
138
|
? Object.fromEntries(packageRegistry.values().map((pkg) => [pkg.id, pkg.domain]))
|
|
129
|
-
: scanDomains(projectRoot, domainDirs, diagnostics);
|
|
139
|
+
: scanDomains(projectRoot, domainDirs, diagnostics, scanCache);
|
|
130
140
|
if (!packageRegistry)
|
|
131
141
|
validateDomainHierarchy(domains, diagnostics);
|
|
132
142
|
// Scan blocks
|
|
133
143
|
const blockDirs = ['blocks', 'domains', 'dashboards', 'workbooks', ...(options.extraBlockDirs ?? [])];
|
|
134
|
-
const blockScan = scanBlocks(projectRoot, blockDirs, diagnostics, datalexRegistry);
|
|
144
|
+
const blockScan = scanBlocks(projectRoot, blockDirs, diagnostics, datalexRegistry, scanCache);
|
|
135
145
|
const blocks = blockScan.blocks;
|
|
136
146
|
const knowledgeBlocks = blockScan.all;
|
|
137
147
|
// Scan business composition views
|
|
138
148
|
const businessViewDirs = ['blocks', 'business-views', 'domains', 'dashboards', 'workbooks', ...(options.extraBlockDirs ?? [])];
|
|
139
|
-
const businessViews = scanBusinessViews(projectRoot, businessViewDirs, diagnostics);
|
|
149
|
+
const businessViews = scanBusinessViews(projectRoot, businessViewDirs, diagnostics, scanCache);
|
|
140
150
|
// Scan business glossary terms
|
|
141
151
|
const termDirs = ['terms', 'blocks', 'business-views', 'domains', 'dashboards', 'workbooks', ...(options.extraBlockDirs ?? [])];
|
|
142
|
-
const terms = scanTerms(projectRoot, termDirs, diagnostics);
|
|
152
|
+
const terms = scanTerms(projectRoot, termDirs, diagnostics, scanCache);
|
|
143
153
|
if (packageRegistry) {
|
|
144
154
|
applyDomainPackageOwnership(projectRoot, packageRegistry, blocks, businessViews, terms, diagnostics);
|
|
145
155
|
applyDomainPackageOwnershipToValues(projectRoot, packageRegistry, knowledgeBlocks, 'Block', diagnostics);
|
|
@@ -455,11 +465,44 @@ function resolveSemanticPath(projectRoot, config) {
|
|
|
455
465
|
return join(projectRoot, customPath);
|
|
456
466
|
return join(projectRoot, 'semantic-layer');
|
|
457
467
|
}
|
|
458
|
-
//
|
|
459
|
-
|
|
468
|
+
// Keep only the most recently compiled project roots. Source text is compared
|
|
469
|
+
// before any AST reuse, so this is a parser cache rather than a stale-source
|
|
470
|
+
// cache; it makes watcher-triggered one-file edits avoid reparsing the other
|
|
471
|
+
// thousands of unchanged DQL declarations.
|
|
472
|
+
const MAX_MANIFEST_PARSE_CACHE_PROJECTS = 4;
|
|
473
|
+
const manifestParseCaches = new Map();
|
|
474
|
+
function createManifestScanCache(projectRoot) {
|
|
475
|
+
let parsedSources;
|
|
476
|
+
if (projectRoot) {
|
|
477
|
+
parsedSources = manifestParseCaches.get(projectRoot);
|
|
478
|
+
if (parsedSources) {
|
|
479
|
+
// Map insertion order doubles as a tiny LRU.
|
|
480
|
+
manifestParseCaches.delete(projectRoot);
|
|
481
|
+
manifestParseCaches.set(projectRoot, parsedSources);
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
parsedSources = new Map();
|
|
485
|
+
manifestParseCaches.set(projectRoot, parsedSources);
|
|
486
|
+
while (manifestParseCaches.size > MAX_MANIFEST_PARSE_CACHE_PROJECTS) {
|
|
487
|
+
const oldest = manifestParseCaches.keys().next().value;
|
|
488
|
+
if (!oldest)
|
|
489
|
+
break;
|
|
490
|
+
manifestParseCaches.delete(oldest);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return { filesByDirectory: new Map(), sources: new Map(), parsedSources };
|
|
495
|
+
}
|
|
496
|
+
function scanFilesRecursive(dir, extensions, cache) {
|
|
497
|
+
const cacheKey = `${dir}\u0000${[...extensions].sort().join(',')}`;
|
|
498
|
+
const cached = cache?.filesByDirectory.get(cacheKey);
|
|
499
|
+
if (cached)
|
|
500
|
+
return cached;
|
|
460
501
|
const results = [];
|
|
461
|
-
if (!existsSync(dir))
|
|
502
|
+
if (!existsSync(dir)) {
|
|
503
|
+
cache?.filesByDirectory.set(cacheKey, results);
|
|
462
504
|
return results;
|
|
505
|
+
}
|
|
463
506
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
464
507
|
for (const entry of entries) {
|
|
465
508
|
const fullPath = join(dir, entry.name);
|
|
@@ -467,32 +510,48 @@ function scanFilesRecursive(dir, extensions) {
|
|
|
467
510
|
// Skip hidden dirs and node_modules
|
|
468
511
|
if (entry.name.startsWith('.') || entry.name === 'node_modules')
|
|
469
512
|
continue;
|
|
470
|
-
results.push(...scanFilesRecursive(fullPath, extensions));
|
|
513
|
+
results.push(...scanFilesRecursive(fullPath, extensions, cache));
|
|
471
514
|
}
|
|
472
515
|
else if (entry.isFile() && extensions.includes(extname(entry.name))) {
|
|
473
516
|
results.push(fullPath);
|
|
474
517
|
}
|
|
475
518
|
}
|
|
519
|
+
cache?.filesByDirectory.set(cacheKey, results);
|
|
476
520
|
return results;
|
|
477
521
|
}
|
|
522
|
+
function readManifestSource(filePath, cache) {
|
|
523
|
+
const cached = cache?.sources.get(filePath);
|
|
524
|
+
if (cached !== undefined)
|
|
525
|
+
return cached;
|
|
526
|
+
const source = readFileSync(filePath, 'utf-8');
|
|
527
|
+
cache?.sources.set(filePath, source);
|
|
528
|
+
return source;
|
|
529
|
+
}
|
|
530
|
+
function parseManifestSource(source, relPath, filePath, cache) {
|
|
531
|
+
const cached = cache?.parsedSources?.get(filePath);
|
|
532
|
+
if (cached?.source === source)
|
|
533
|
+
return cached.ast;
|
|
534
|
+
const ast = new Parser(source, relPath).parse();
|
|
535
|
+
cache?.parsedSources?.set(filePath, { source, ast });
|
|
536
|
+
return ast;
|
|
537
|
+
}
|
|
478
538
|
// ---- Block Scanning ----
|
|
479
|
-
function scanDomains(projectRoot, dirs, diagnostics) {
|
|
539
|
+
function scanDomains(projectRoot, dirs, diagnostics, scanCache) {
|
|
480
540
|
const domains = {};
|
|
481
541
|
const seenFiles = new Set();
|
|
482
542
|
for (const dir of dirs) {
|
|
483
543
|
const dirPath = join(projectRoot, dir);
|
|
484
|
-
const files = scanFilesRecursive(dirPath, ['.dql']);
|
|
544
|
+
const files = scanFilesRecursive(dirPath, ['.dql'], scanCache);
|
|
485
545
|
for (const filePath of files) {
|
|
486
546
|
if (seenFiles.has(filePath))
|
|
487
547
|
continue;
|
|
488
548
|
seenFiles.add(filePath);
|
|
489
549
|
const relPath = relative(projectRoot, filePath);
|
|
490
550
|
try {
|
|
491
|
-
const source =
|
|
551
|
+
const source = readManifestSource(filePath, scanCache);
|
|
492
552
|
if (!/(^|\n)\s*domain\s+"/.test(source))
|
|
493
553
|
continue;
|
|
494
|
-
const
|
|
495
|
-
const ast = parser.parse();
|
|
554
|
+
const ast = parseManifestSource(source, relPath, filePath, scanCache);
|
|
496
555
|
for (const stmt of ast.statements) {
|
|
497
556
|
const domain = stmt;
|
|
498
557
|
if (domain.kind !== 'DomainDecl')
|
|
@@ -557,22 +616,21 @@ function validateDomainHierarchy(domains, diagnostics) {
|
|
|
557
616
|
}
|
|
558
617
|
}
|
|
559
618
|
}
|
|
560
|
-
function scanBlocks(projectRoot, dirs, diagnostics, datalexRegistry) {
|
|
619
|
+
function scanBlocks(projectRoot, dirs, diagnostics, datalexRegistry, scanCache) {
|
|
561
620
|
const blocks = {};
|
|
562
621
|
const all = [];
|
|
563
622
|
const seenFiles = new Set();
|
|
564
623
|
for (const dir of dirs) {
|
|
565
624
|
const dirPath = join(projectRoot, dir);
|
|
566
|
-
const files = scanFilesRecursive(dirPath, ['.dql']);
|
|
625
|
+
const files = scanFilesRecursive(dirPath, ['.dql'], scanCache);
|
|
567
626
|
for (const filePath of files) {
|
|
568
627
|
if (seenFiles.has(filePath))
|
|
569
628
|
continue;
|
|
570
629
|
seenFiles.add(filePath);
|
|
571
630
|
const relPath = relative(projectRoot, filePath);
|
|
572
631
|
try {
|
|
573
|
-
const source =
|
|
574
|
-
const
|
|
575
|
-
const ast = parser.parse();
|
|
632
|
+
const source = readManifestSource(filePath, scanCache);
|
|
633
|
+
const ast = parseManifestSource(source, relPath, filePath, scanCache);
|
|
576
634
|
collectContractDiagnostics(ast, relPath, diagnostics, datalexRegistry);
|
|
577
635
|
for (const stmt of ast.statements) {
|
|
578
636
|
const block = stmt;
|
|
@@ -629,23 +687,22 @@ function duplicateBlockPriority(block) {
|
|
|
629
687
|
return score;
|
|
630
688
|
}
|
|
631
689
|
// ---- Business View Scanning ----
|
|
632
|
-
function scanBusinessViews(projectRoot, dirs, diagnostics) {
|
|
690
|
+
function scanBusinessViews(projectRoot, dirs, diagnostics, scanCache) {
|
|
633
691
|
const views = {};
|
|
634
692
|
const seenFiles = new Set();
|
|
635
693
|
for (const dir of dirs) {
|
|
636
694
|
const dirPath = join(projectRoot, dir);
|
|
637
|
-
const files = scanFilesRecursive(dirPath, ['.dql']);
|
|
695
|
+
const files = scanFilesRecursive(dirPath, ['.dql'], scanCache);
|
|
638
696
|
for (const filePath of files) {
|
|
639
697
|
if (seenFiles.has(filePath))
|
|
640
698
|
continue;
|
|
641
699
|
seenFiles.add(filePath);
|
|
642
700
|
const relPath = relative(projectRoot, filePath);
|
|
643
701
|
try {
|
|
644
|
-
const source =
|
|
702
|
+
const source = readManifestSource(filePath, scanCache);
|
|
645
703
|
if (!source.includes('business_view'))
|
|
646
704
|
continue;
|
|
647
|
-
const
|
|
648
|
-
const ast = parser.parse();
|
|
705
|
+
const ast = parseManifestSource(source, relPath, filePath, scanCache);
|
|
649
706
|
for (const stmt of ast.statements) {
|
|
650
707
|
const view = stmt;
|
|
651
708
|
if (view.kind !== 'BusinessViewDecl')
|
|
@@ -676,23 +733,22 @@ function scanBusinessViews(projectRoot, dirs, diagnostics) {
|
|
|
676
733
|
return views;
|
|
677
734
|
}
|
|
678
735
|
// ---- Business Term Scanning ----
|
|
679
|
-
function scanTerms(projectRoot, dirs, diagnostics) {
|
|
736
|
+
function scanTerms(projectRoot, dirs, diagnostics, scanCache) {
|
|
680
737
|
const terms = {};
|
|
681
738
|
const seenFiles = new Set();
|
|
682
739
|
for (const dir of dirs) {
|
|
683
740
|
const dirPath = join(projectRoot, dir);
|
|
684
|
-
const files = scanFilesRecursive(dirPath, ['.dql']);
|
|
741
|
+
const files = scanFilesRecursive(dirPath, ['.dql'], scanCache);
|
|
685
742
|
for (const filePath of files) {
|
|
686
743
|
if (seenFiles.has(filePath))
|
|
687
744
|
continue;
|
|
688
745
|
seenFiles.add(filePath);
|
|
689
746
|
const relPath = relative(projectRoot, filePath);
|
|
690
747
|
try {
|
|
691
|
-
const source =
|
|
748
|
+
const source = readManifestSource(filePath, scanCache);
|
|
692
749
|
if (!source.includes('term'))
|
|
693
750
|
continue;
|
|
694
|
-
const
|
|
695
|
-
const ast = parser.parse();
|
|
751
|
+
const ast = parseManifestSource(source, relPath, filePath, scanCache);
|
|
696
752
|
for (const stmt of ast.statements) {
|
|
697
753
|
const term = stmt;
|
|
698
754
|
if (term.kind !== 'TermDecl')
|