@duckcodeailabs/dql-core 1.6.18 → 1.6.19

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.
Files changed (55) hide show
  1. package/dist/apps/app-document.d.ts +2 -1
  2. package/dist/apps/app-document.d.ts.map +1 -1
  3. package/dist/apps/app-document.js +18 -5
  4. package/dist/apps/app-document.js.map +1 -1
  5. package/dist/apps/dashboard-document.d.ts +19 -0
  6. package/dist/apps/dashboard-document.d.ts.map +1 -1
  7. package/dist/apps/dashboard-document.js +72 -0
  8. package/dist/apps/dashboard-document.js.map +1 -1
  9. package/dist/apps/index.d.ts +1 -1
  10. package/dist/apps/index.d.ts.map +1 -1
  11. package/dist/ast/nodes.d.ts +40 -1
  12. package/dist/ast/nodes.d.ts.map +1 -1
  13. package/dist/ast/nodes.js +1 -0
  14. package/dist/ast/nodes.js.map +1 -1
  15. package/dist/ast/printer.d.ts.map +1 -1
  16. package/dist/ast/printer.js +40 -0
  17. package/dist/ast/printer.js.map +1 -1
  18. package/dist/formatter/formatter.d.ts.map +1 -1
  19. package/dist/formatter/formatter.js +66 -0
  20. package/dist/formatter/formatter.js.map +1 -1
  21. package/dist/lineage/builder.d.ts +35 -0
  22. package/dist/lineage/builder.d.ts.map +1 -1
  23. package/dist/lineage/builder.js +96 -8
  24. package/dist/lineage/builder.js.map +1 -1
  25. package/dist/lineage/index.d.ts +1 -1
  26. package/dist/lineage/index.d.ts.map +1 -1
  27. package/dist/lineage/index.js.map +1 -1
  28. package/dist/lineage/query.d.ts +30 -1
  29. package/dist/lineage/query.d.ts.map +1 -1
  30. package/dist/lineage/query.js +248 -1
  31. package/dist/lineage/query.js.map +1 -1
  32. package/dist/manifest/builder.d.ts +6 -1
  33. package/dist/manifest/builder.d.ts.map +1 -1
  34. package/dist/manifest/builder.js +359 -13
  35. package/dist/manifest/builder.js.map +1 -1
  36. package/dist/manifest/types.d.ts +52 -0
  37. package/dist/manifest/types.d.ts.map +1 -1
  38. package/dist/parser/parser.d.ts +4 -0
  39. package/dist/parser/parser.d.ts.map +1 -1
  40. package/dist/parser/parser.js +262 -20
  41. package/dist/parser/parser.js.map +1 -1
  42. package/dist/semantic/analyzer.d.ts.map +1 -1
  43. package/dist/semantic/analyzer.js +4 -0
  44. package/dist/semantic/analyzer.js.map +1 -1
  45. package/dist/semantic/providers/dbt-provider.js +130 -4
  46. package/dist/semantic/providers/dbt-provider.js.map +1 -1
  47. package/dist/semantic/providers/registry.js +68 -4
  48. package/dist/semantic/providers/registry.js.map +1 -1
  49. package/dist/semantic/providers/snowflake-provider.d.ts.map +1 -1
  50. package/dist/semantic/providers/snowflake-provider.js +60 -8
  51. package/dist/semantic/providers/snowflake-provider.js.map +1 -1
  52. package/dist/semantic/semantic-layer.d.ts.map +1 -1
  53. package/dist/semantic/semantic-layer.js +11 -20
  54. package/dist/semantic/semantic-layer.js.map +1 -1
  55. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { DiagnosticReporter, DQLSyntaxError } from '../errors/reporter.js';
2
2
  import { Lexer } from '../lexer/lexer.js';
3
- import { TokenType, CHART_TYPES, FILTER_TYPES, normalizeChartType } from '../lexer/token.js';
3
+ import { TokenType, CHART_TYPES, FILTER_TYPES, SQL_START_KEYWORDS, normalizeChartType } from '../lexer/token.js';
4
4
  import { NodeKind, } from '../ast/nodes.js';
5
5
  const DRAFT_STRING_METADATA_FIELDS = new Set([
6
6
  'first_asked',
@@ -81,6 +81,9 @@ export class Parser {
81
81
  if (this.check(TokenType.BlockKeyword)) {
82
82
  return this.parseBlockDecl(decorators);
83
83
  }
84
+ if (this.check(TokenType.DomainKeyword)) {
85
+ return this.parseDomainDecl(decorators);
86
+ }
84
87
  if (this.check(TokenType.TermKeyword)) {
85
88
  return this.parseTermDecl(decorators);
86
89
  }
@@ -96,7 +99,7 @@ export class Parser {
96
99
  if (this.check(TokenType.EOF)) {
97
100
  return null;
98
101
  }
99
- this.error(`Unexpected token '${this.current().value}'. Expected 'dashboard', 'digest', 'workbook', 'chart', 'block', 'term', 'business_view', or 'import'.`);
102
+ this.error(`Unexpected token '${this.current().value}'. Expected 'dashboard', 'digest', 'workbook', 'chart', 'domain', 'block', 'term', 'business_view', or 'import'.`);
100
103
  return null;
101
104
  }
102
105
  parseFilterCall() {
@@ -631,6 +634,90 @@ export class Parser {
631
634
  makeSpan(start, end) {
632
635
  return { start: start.start, end: end.end };
633
636
  }
637
+ // ---- Domain Declaration ----
638
+ parseDomainDecl(decorators) {
639
+ const start = decorators.length > 0 ? decorators[0].span : this.currentSpan();
640
+ this.expect(TokenType.DomainKeyword);
641
+ const nameToken = this.expect(TokenType.StringLiteral);
642
+ this.expect(TokenType.LeftBrace);
643
+ let owner;
644
+ let businessOwner;
645
+ let boundedContext;
646
+ let sourceSystems;
647
+ let primaryTerms;
648
+ let reviewCadence;
649
+ let tags;
650
+ let businessOutcome;
651
+ let description;
652
+ while (!this.check(TokenType.RightBrace) && !this.isAtEnd()) {
653
+ if (this.check(TokenType.OwnerKeyword)) {
654
+ this.advance();
655
+ this.expect(TokenType.Equals);
656
+ owner = this.expect(TokenType.StringLiteral).value;
657
+ }
658
+ else if (this.check(TokenType.DescriptionKeyword)) {
659
+ this.advance();
660
+ this.expect(TokenType.Equals);
661
+ description = this.expectStringLike().value;
662
+ }
663
+ else if (this.check(TokenType.TagsKeyword)) {
664
+ this.advance();
665
+ this.expect(TokenType.Equals);
666
+ tags = this.parseStringArrayValues();
667
+ }
668
+ else if (this.check(TokenType.Identifier)
669
+ && (this.current().value === 'businessOwner'
670
+ || this.current().value === 'boundedContext'
671
+ || this.current().value === 'sourceSystems'
672
+ || this.current().value === 'primaryTerms'
673
+ || this.current().value === 'reviewCadence'
674
+ || this.current().value === 'businessOutcome')) {
675
+ const keyToken = this.advance();
676
+ this.expect(TokenType.Equals);
677
+ if (keyToken.value === 'businessOwner') {
678
+ businessOwner = this.expect(TokenType.StringLiteral).value;
679
+ }
680
+ else if (keyToken.value === 'boundedContext') {
681
+ boundedContext = this.expectStringLike().value;
682
+ }
683
+ else if (keyToken.value === 'sourceSystems') {
684
+ sourceSystems = this.parseStringArrayValues();
685
+ }
686
+ else if (keyToken.value === 'primaryTerms') {
687
+ primaryTerms = this.parseStringArrayValues();
688
+ }
689
+ else if (keyToken.value === 'reviewCadence') {
690
+ reviewCadence = this.expect(TokenType.StringLiteral).value;
691
+ }
692
+ else if (keyToken.value === 'businessOutcome') {
693
+ businessOutcome = this.expect(TokenType.StringLiteral).value;
694
+ }
695
+ }
696
+ else if (this.check(TokenType.RightBrace)) {
697
+ break;
698
+ }
699
+ else {
700
+ this.error(`Unexpected token '${this.current().value}' inside domain. Expected 'owner', 'businessOwner', 'boundedContext', 'sourceSystems', 'primaryTerms', 'reviewCadence', 'tags', 'businessOutcome', 'description', or '}'.`);
701
+ this.advance();
702
+ }
703
+ }
704
+ this.expect(TokenType.RightBrace);
705
+ return {
706
+ kind: NodeKind.DomainDecl,
707
+ name: nameToken.value,
708
+ owner,
709
+ businessOwner,
710
+ boundedContext,
711
+ sourceSystems,
712
+ primaryTerms,
713
+ reviewCadence,
714
+ tags,
715
+ businessOutcome,
716
+ description,
717
+ decorators,
718
+ span: this.makeSpan(start, this.previousSpan()),
719
+ };
720
+ }
634
721
  // ---- Business Term Declaration ----
635
722
  parseTermDecl(decorators) {
636
723
  const start = decorators.length > 0 ? decorators[0].span : this.currentSpan();
@@ -928,11 +1015,20 @@ export class Parser {
928
1015
  let blockType;
929
1016
  let metricRef;
930
1017
  let metricsRef;
931
- let dimensionsRef;
1018
+ let parsedDimensions;
932
1019
  let description;
933
1020
  let tags;
934
1021
  let owner;
935
1022
  let termRefs;
1023
+ let pattern;
1024
+ let grain;
1025
+ let entities;
1026
+ let outputs;
1027
+ let allowedFilters;
1028
+ let parameterPolicy;
1029
+ let filterBindings;
1030
+ let sourceSystems;
1031
+ let replacementFor;
936
1032
  let params;
937
1033
  let query;
938
1034
  let visualization;
@@ -1015,7 +1111,7 @@ export class Parser {
1015
1111
  this.expect(TokenType.Equals);
1016
1112
  const arrExpr = this.parseArrayLiteral();
1017
1113
  if (arrExpr.kind === NodeKind.ArrayLiteral) {
1018
- dimensionsRef = arrExpr.elements
1114
+ parsedDimensions = arrExpr.elements
1019
1115
  .filter((e) => e.kind === NodeKind.StringLiteral)
1020
1116
  .map((e) => e.value);
1021
1117
  }
@@ -1047,25 +1143,72 @@ export class Parser {
1047
1143
  this.expect(TokenType.Equals);
1048
1144
  termRefs = this.parseStringArrayValues();
1049
1145
  }
1146
+ else if (this.check(TokenType.Identifier)
1147
+ && (this.current().value === 'grain'
1148
+ || this.current().value === 'pattern'
1149
+ || this.current().value === 'entities'
1150
+ || this.current().value === 'outputs'
1151
+ || this.current().value === 'allowedFilters'
1152
+ || this.current().value === 'sourceSystems'
1153
+ || this.current().value === 'replacementFor')) {
1154
+ const keyToken = this.advance();
1155
+ this.expect(TokenType.Equals);
1156
+ if (keyToken.value === 'pattern') {
1157
+ const val = this.expect(TokenType.StringLiteral);
1158
+ pattern = val.value;
1159
+ }
1160
+ else if (keyToken.value === 'grain') {
1161
+ const val = this.expect(TokenType.StringLiteral);
1162
+ grain = val.value;
1163
+ }
1164
+ else if (keyToken.value === 'entities') {
1165
+ entities = this.parseStringArrayValues();
1166
+ }
1167
+ else if (keyToken.value === 'outputs') {
1168
+ outputs = this.parseStringArrayValues();
1169
+ }
1170
+ else if (keyToken.value === 'allowedFilters') {
1171
+ allowedFilters = this.parseStringArrayValues();
1172
+ }
1173
+ else if (keyToken.value === 'sourceSystems') {
1174
+ sourceSystems = this.parseStringArrayValues();
1175
+ }
1176
+ else if (keyToken.value === 'replacementFor') {
1177
+ replacementFor = this.parseStringArrayValues();
1178
+ }
1179
+ }
1050
1180
  else if (this.check(TokenType.ParamsKeyword)) {
1051
1181
  params = this.parseBlockParams();
1052
1182
  }
1183
+ else if (this.check(TokenType.Identifier)
1184
+ && this.current().value === 'parameterPolicy') {
1185
+ parameterPolicy = this.parseBlockParameterPolicy();
1186
+ }
1187
+ else if (this.check(TokenType.Identifier)
1188
+ && this.current().value === 'filterBindings') {
1189
+ filterBindings = this.parseBlockFilterBindings();
1190
+ }
1053
1191
  else if (this.check(TokenType.QueryKeyword)) {
1054
1192
  this.advance();
1055
- this.expect(TokenType.Equals);
1056
- if (this.check(TokenType.TripleQuoteString)) {
1057
- const sqlToken = this.advance();
1058
- const interpolations = this.extractInterpolations(sqlToken.value, sqlToken.span);
1059
- query = {
1060
- kind: NodeKind.SQLQuery,
1061
- rawSQL: sqlToken.value,
1062
- interpolations,
1063
- span: sqlToken.span,
1064
- };
1193
+ if (this.check(TokenType.LeftBrace)) {
1194
+ query = this.parseLegacyBlockQuerySection();
1065
1195
  }
1066
1196
  else {
1067
- // Fallback: parse as inline SQL
1068
- query = this.parseSQLQuery();
1197
+ this.expect(TokenType.Equals);
1198
+ if (this.check(TokenType.TripleQuoteString)) {
1199
+ const sqlToken = this.advance();
1200
+ const interpolations = this.extractInterpolations(sqlToken.value, sqlToken.span);
1201
+ query = {
1202
+ kind: NodeKind.SQLQuery,
1203
+ rawSQL: sqlToken.value,
1204
+ interpolations,
1205
+ span: sqlToken.span,
1206
+ };
1207
+ }
1208
+ else {
1209
+ // Fallback: parse as inline SQL
1210
+ query = this.parseSQLQuery();
1211
+ }
1069
1212
  }
1070
1213
  }
1071
1214
  else if (this.check(TokenType.VisualizationKeyword)) {
@@ -1264,13 +1407,13 @@ export class Parser {
1264
1407
  this.skipBalancedBlock();
1265
1408
  continue;
1266
1409
  }
1267
- this.error(`Unexpected token '${this.current().value}' inside block. Expected 'domain', 'type', 'status', 'datalex_contract', 'metric', 'metrics', 'dimensions', 'description', 'tags', 'owner', 'terms', 'params', 'query', 'visualization', 'tests', 'llmContext', 'invariants', 'examples', 'businessOutcome', 'businessOwner', 'decisionUse', 'reviewCadence', 'businessRules', 'caveats', Tier-2 draft metadata fields, or '}'.`);
1410
+ this.error(`Unexpected token '${this.current().value}' inside block. Expected 'domain', 'type', 'status', 'datalex_contract', 'metric', 'metrics', 'dimensions', 'description', 'tags', 'owner', 'terms', 'pattern', 'grain', 'entities', 'outputs', 'allowedFilters', 'parameterPolicy', 'filterBindings', 'sourceSystems', 'replacementFor', 'params', 'query', 'visualization', 'tests', 'llmContext', 'invariants', 'examples', 'businessOutcome', 'businessOwner', 'decisionUse', 'reviewCadence', 'businessRules', 'caveats', Tier-2 draft metadata fields, or '}'.`);
1268
1411
  this.advance();
1269
1412
  }
1270
1413
  }
1271
1414
  if (!blockType) {
1272
- this.error(`Block "${nameToken.value}" is missing a required type declaration. Add type = "semantic" for dbt metric blocks or type = "custom" for SQL blocks.`);
1273
- blockType = 'custom'; // error-recovery default keeps downstream analysis working
1415
+ blockType = 'custom';
1416
+ this.reporter.warning(`Block "${nameToken.value}" is missing a type declaration. DQL treated it as type = "custom" for backward compatibility.`, nameToken.span);
1274
1417
  }
1275
1418
  this.expect(TokenType.RightBrace);
1276
1419
  return {
@@ -1280,11 +1423,21 @@ export class Parser {
1280
1423
  blockType,
1281
1424
  metricRef,
1282
1425
  metricsRef,
1283
- dimensionsRef,
1426
+ dimensionsRef: blockType === 'semantic' ? parsedDimensions : undefined,
1284
1427
  description,
1285
1428
  tags,
1286
1429
  owner,
1287
1430
  termRefs,
1431
+ pattern,
1432
+ grain,
1433
+ entities,
1434
+ outputs,
1435
+ dimensions: blockType === 'semantic' ? undefined : parsedDimensions,
1436
+ allowedFilters,
1437
+ parameterPolicy,
1438
+ filterBindings,
1439
+ sourceSystems,
1440
+ replacementFor,
1288
1441
  params,
1289
1442
  query,
1290
1443
  visualization,
@@ -1319,6 +1472,43 @@ export class Parser {
1319
1472
  span: this.makeSpan(start, this.previousSpan()),
1320
1473
  };
1321
1474
  }
1475
+ parseLegacyBlockQuerySection() {
1476
+ const start = this.currentSpan();
1477
+ this.expect(TokenType.LeftBrace);
1478
+ let query;
1479
+ while (!this.check(TokenType.RightBrace) && !this.isAtEnd()) {
1480
+ if (this.check(TokenType.Identifier) && this.current().value === 'sql') {
1481
+ this.advance();
1482
+ this.expect(TokenType.Equals);
1483
+ if (this.check(TokenType.TripleQuoteString)) {
1484
+ const sqlToken = this.advance();
1485
+ const interpolations = this.extractInterpolations(sqlToken.value, sqlToken.span);
1486
+ query = {
1487
+ kind: NodeKind.SQLQuery,
1488
+ rawSQL: sqlToken.value,
1489
+ interpolations,
1490
+ span: sqlToken.span,
1491
+ };
1492
+ }
1493
+ else {
1494
+ query = this.parseSQLQuery();
1495
+ }
1496
+ }
1497
+ else if (this.check(TokenType.Identifier)
1498
+ && SQL_START_KEYWORDS.has(this.current().value.toUpperCase())) {
1499
+ query = this.parseSQLQuery();
1500
+ }
1501
+ else {
1502
+ this.error(`Unexpected token '${this.current().value}' inside query section. Expected 'sql' or inline SQL.`);
1503
+ this.advance();
1504
+ }
1505
+ }
1506
+ this.expect(TokenType.RightBrace);
1507
+ if (!query) {
1508
+ this.reporter.error('Query section must contain sql = """...""" or inline SQL.', this.makeSpan(start, this.previousSpan()));
1509
+ }
1510
+ return query;
1511
+ }
1322
1512
  parseBlockParams() {
1323
1513
  const start = this.currentSpan();
1324
1514
  this.expect(TokenType.ParamsKeyword);
@@ -1350,6 +1540,58 @@ export class Parser {
1350
1540
  span: this.makeSpan(start, this.previousSpan()),
1351
1541
  };
1352
1542
  }
1543
+ parseBlockParameterPolicy() {
1544
+ const entries = [];
1545
+ this.expect(TokenType.Identifier);
1546
+ this.expect(TokenType.LeftBrace);
1547
+ while (!this.check(TokenType.RightBrace) && !this.isAtEnd()) {
1548
+ const entryStart = this.currentSpan();
1549
+ const nameToken = this.current();
1550
+ if (nameToken.type === TokenType.Identifier || this.isBlockFieldKeyword(nameToken.type)) {
1551
+ this.advance();
1552
+ }
1553
+ else {
1554
+ this.error(`Expected parameter name inside parameterPolicy, got '${nameToken.value}'.`);
1555
+ this.advance();
1556
+ continue;
1557
+ }
1558
+ this.expect(TokenType.Equals);
1559
+ const policy = this.expectStringLike();
1560
+ entries.push({
1561
+ name: nameToken.value,
1562
+ policy: policy.value,
1563
+ span: this.makeSpan(entryStart, this.previousSpan()),
1564
+ });
1565
+ }
1566
+ this.expect(TokenType.RightBrace);
1567
+ return entries;
1568
+ }
1569
+ parseBlockFilterBindings() {
1570
+ const entries = [];
1571
+ this.expect(TokenType.Identifier);
1572
+ this.expect(TokenType.LeftBrace);
1573
+ while (!this.check(TokenType.RightBrace) && !this.isAtEnd()) {
1574
+ const entryStart = this.currentSpan();
1575
+ const nameToken = this.current();
1576
+ if (nameToken.type === TokenType.Identifier || this.isBlockFieldKeyword(nameToken.type)) {
1577
+ this.advance();
1578
+ }
1579
+ else {
1580
+ this.error(`Expected business filter name inside filterBindings, got '${nameToken.value}'.`);
1581
+ this.advance();
1582
+ continue;
1583
+ }
1584
+ this.expect(TokenType.Equals);
1585
+ const binding = this.expectStringLike();
1586
+ entries.push({
1587
+ filter: nameToken.value,
1588
+ binding: binding.value,
1589
+ span: this.makeSpan(entryStart, this.previousSpan()),
1590
+ });
1591
+ }
1592
+ this.expect(TokenType.RightBrace);
1593
+ return entries;
1594
+ }
1353
1595
  parseBlockVisualization() {
1354
1596
  const start = this.currentSpan();
1355
1597
  this.expect(TokenType.VisualizationKeyword);