@duckcodeailabs/dql-cli 1.4.3 → 1.4.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.
Files changed (260) hide show
  1. package/apps-api.d.ts +77 -0
  2. package/apps-api.d.ts.map +1 -0
  3. package/apps-api.js +612 -0
  4. package/apps-api.js.map +1 -0
  5. package/apps-api.test.d.ts +2 -0
  6. package/apps-api.test.d.ts.map +1 -0
  7. package/apps-api.test.js +111 -0
  8. package/apps-api.test.js.map +1 -0
  9. package/args.d.ts +30 -0
  10. package/args.d.ts.map +1 -0
  11. package/args.js +105 -0
  12. package/args.js.map +1 -0
  13. package/args.test.d.ts +2 -0
  14. package/args.test.d.ts.map +1 -0
  15. package/args.test.js +33 -0
  16. package/args.test.js.map +1 -0
  17. package/assets/dql-notebook/assets/codemirror-DJYUkPr1.js +11 -0
  18. package/assets/dql-notebook/assets/index-DUTeFz5j.js +858 -0
  19. package/assets/dql-notebook/assets/index-DrhoZmtv.css +1 -0
  20. package/assets/dql-notebook/assets/react-CRB3T2We.js +32 -0
  21. package/assets/dql-notebook/index.html +18 -0
  22. package/assets/notebook-browser/app.js +548 -0
  23. package/assets/notebook-browser/index.html +83 -0
  24. package/assets/notebook-browser/styles.css +336 -0
  25. package/block-templates.d.ts +8 -0
  26. package/block-templates.d.ts.map +1 -0
  27. package/block-templates.js +60 -0
  28. package/block-templates.js.map +1 -0
  29. package/commands/agent.d.ts +19 -0
  30. package/commands/agent.d.ts.map +1 -0
  31. package/commands/agent.js +165 -0
  32. package/commands/agent.js.map +1 -0
  33. package/commands/app.d.ts +32 -0
  34. package/commands/app.d.ts.map +1 -0
  35. package/commands/app.js +307 -0
  36. package/commands/app.js.map +1 -0
  37. package/commands/build.d.ts +3 -0
  38. package/commands/build.d.ts.map +1 -0
  39. package/commands/build.js +69 -0
  40. package/commands/build.js.map +1 -0
  41. package/commands/build.test.d.ts +2 -0
  42. package/commands/build.test.d.ts.map +1 -0
  43. package/commands/build.test.js +44 -0
  44. package/commands/build.test.js.map +1 -0
  45. package/commands/certify.d.ts +3 -0
  46. package/commands/certify.d.ts.map +1 -0
  47. package/commands/certify.js +228 -0
  48. package/commands/certify.js.map +1 -0
  49. package/commands/compile.d.ts +21 -0
  50. package/commands/compile.d.ts.map +1 -0
  51. package/commands/compile.js +198 -0
  52. package/commands/compile.js.map +1 -0
  53. package/commands/compile.test.d.ts +2 -0
  54. package/commands/compile.test.d.ts.map +1 -0
  55. package/commands/compile.test.js +115 -0
  56. package/commands/compile.test.js.map +1 -0
  57. package/commands/diff.d.ts +3 -0
  58. package/commands/diff.d.ts.map +1 -0
  59. package/commands/diff.js +52 -0
  60. package/commands/diff.js.map +1 -0
  61. package/commands/doctor.d.ts +3 -0
  62. package/commands/doctor.d.ts.map +1 -0
  63. package/commands/doctor.js +191 -0
  64. package/commands/doctor.js.map +1 -0
  65. package/commands/doctor.test.d.ts +2 -0
  66. package/commands/doctor.test.d.ts.map +1 -0
  67. package/commands/doctor.test.js +43 -0
  68. package/commands/doctor.test.js.map +1 -0
  69. package/commands/fmt.d.ts +3 -0
  70. package/commands/fmt.d.ts.map +1 -0
  71. package/commands/fmt.js +53 -0
  72. package/commands/fmt.js.map +1 -0
  73. package/commands/info.d.ts +3 -0
  74. package/commands/info.d.ts.map +1 -0
  75. package/commands/info.js +56 -0
  76. package/commands/info.js.map +1 -0
  77. package/commands/init.d.ts +3 -0
  78. package/commands/init.d.ts.map +1 -0
  79. package/commands/init.js +250 -0
  80. package/commands/init.js.map +1 -0
  81. package/commands/init.test.d.ts +2 -0
  82. package/commands/init.test.d.ts.map +1 -0
  83. package/commands/init.test.js +118 -0
  84. package/commands/init.test.js.map +1 -0
  85. package/commands/lineage.d.ts +24 -0
  86. package/commands/lineage.d.ts.map +1 -0
  87. package/commands/lineage.js +634 -0
  88. package/commands/lineage.js.map +1 -0
  89. package/commands/mcp.d.ts +7 -0
  90. package/commands/mcp.d.ts.map +1 -0
  91. package/commands/mcp.js +16 -0
  92. package/commands/mcp.js.map +1 -0
  93. package/commands/migrate.d.ts +12 -0
  94. package/commands/migrate.d.ts.map +1 -0
  95. package/commands/migrate.js +192 -0
  96. package/commands/migrate.js.map +1 -0
  97. package/commands/new.d.ts +3 -0
  98. package/commands/new.d.ts.map +1 -0
  99. package/commands/new.js +490 -0
  100. package/commands/new.js.map +1 -0
  101. package/commands/new.test.d.ts +2 -0
  102. package/commands/new.test.d.ts.map +1 -0
  103. package/commands/new.test.js +191 -0
  104. package/commands/new.test.js.map +1 -0
  105. package/commands/notebook.d.ts +3 -0
  106. package/commands/notebook.d.ts.map +1 -0
  107. package/commands/notebook.js +46 -0
  108. package/commands/notebook.js.map +1 -0
  109. package/commands/parse.d.ts +3 -0
  110. package/commands/parse.d.ts.map +1 -0
  111. package/commands/parse.js +63 -0
  112. package/commands/parse.js.map +1 -0
  113. package/commands/preview.d.ts +3 -0
  114. package/commands/preview.d.ts.map +1 -0
  115. package/commands/preview.js +42 -0
  116. package/commands/preview.js.map +1 -0
  117. package/commands/schedule.d.ts +3 -0
  118. package/commands/schedule.d.ts.map +1 -0
  119. package/commands/schedule.js +215 -0
  120. package/commands/schedule.js.map +1 -0
  121. package/commands/semantic.d.ts +12 -0
  122. package/commands/semantic.d.ts.map +1 -0
  123. package/commands/semantic.js +356 -0
  124. package/commands/semantic.js.map +1 -0
  125. package/commands/serve.d.ts +3 -0
  126. package/commands/serve.d.ts.map +1 -0
  127. package/commands/serve.js +30 -0
  128. package/commands/serve.js.map +1 -0
  129. package/commands/slack.d.ts +13 -0
  130. package/commands/slack.d.ts.map +1 -0
  131. package/commands/slack.js +53 -0
  132. package/commands/slack.js.map +1 -0
  133. package/commands/sync.d.ts +3 -0
  134. package/commands/sync.d.ts.map +1 -0
  135. package/commands/sync.js +192 -0
  136. package/commands/sync.js.map +1 -0
  137. package/commands/sync.test.d.ts +2 -0
  138. package/commands/sync.test.d.ts.map +1 -0
  139. package/commands/sync.test.js +147 -0
  140. package/commands/sync.test.js.map +1 -0
  141. package/commands/test.d.ts +3 -0
  142. package/commands/test.d.ts.map +1 -0
  143. package/commands/test.js +167 -0
  144. package/commands/test.js.map +1 -0
  145. package/commands/validate.d.ts +3 -0
  146. package/commands/validate.d.ts.map +1 -0
  147. package/commands/validate.js +116 -0
  148. package/commands/validate.js.map +1 -0
  149. package/commands/verify.d.ts +11 -0
  150. package/commands/verify.d.ts.map +1 -0
  151. package/commands/verify.js +74 -0
  152. package/commands/verify.js.map +1 -0
  153. package/digest.d.ts +10 -0
  154. package/digest.d.ts.map +1 -0
  155. package/digest.js +83 -0
  156. package/digest.js.map +1 -0
  157. package/git-service.d.ts +17 -0
  158. package/git-service.d.ts.map +1 -0
  159. package/git-service.js +54 -0
  160. package/git-service.js.map +1 -0
  161. package/governance-runtime.d.ts +15 -0
  162. package/governance-runtime.d.ts.map +1 -0
  163. package/governance-runtime.js +50 -0
  164. package/governance-runtime.js.map +1 -0
  165. package/index.d.ts +3 -0
  166. package/index.d.ts.map +1 -0
  167. package/index.js.map +1 -0
  168. package/llm/index.d.ts +4 -0
  169. package/llm/index.d.ts.map +1 -0
  170. package/llm/index.js +19 -0
  171. package/llm/index.js.map +1 -0
  172. package/llm/providers/claude-agent-sdk.d.ts +3 -0
  173. package/llm/providers/claude-agent-sdk.d.ts.map +1 -0
  174. package/llm/providers/claude-agent-sdk.js +174 -0
  175. package/llm/providers/claude-agent-sdk.js.map +1 -0
  176. package/llm/providers/claude-code.d.ts +8 -0
  177. package/llm/providers/claude-code.d.ts.map +1 -0
  178. package/llm/providers/claude-code.js +171 -0
  179. package/llm/providers/claude-code.js.map +1 -0
  180. package/llm/providers/dql-agent-provider.d.ts +5 -0
  181. package/llm/providers/dql-agent-provider.d.ts.map +1 -0
  182. package/llm/providers/dql-agent-provider.js +99 -0
  183. package/llm/providers/dql-agent-provider.js.map +1 -0
  184. package/llm/tools.d.ts +9 -0
  185. package/llm/tools.d.ts.map +1 -0
  186. package/llm/tools.js +112 -0
  187. package/llm/tools.js.map +1 -0
  188. package/llm/types.d.ts +70 -0
  189. package/llm/types.d.ts.map +1 -0
  190. package/llm/types.js +2 -0
  191. package/llm/types.js.map +1 -0
  192. package/local-runtime.d.ts +142 -0
  193. package/local-runtime.d.ts.map +1 -0
  194. package/local-runtime.js +4357 -0
  195. package/local-runtime.js.map +1 -0
  196. package/local-runtime.test.d.ts +2 -0
  197. package/local-runtime.test.d.ts.map +1 -0
  198. package/local-runtime.test.js +241 -0
  199. package/local-runtime.test.js.map +1 -0
  200. package/metricflow.d.ts +35 -0
  201. package/metricflow.d.ts.map +1 -0
  202. package/metricflow.js +122 -0
  203. package/metricflow.js.map +1 -0
  204. package/metricflow.test.d.ts +2 -0
  205. package/metricflow.test.d.ts.map +1 -0
  206. package/metricflow.test.js +54 -0
  207. package/metricflow.test.js.map +1 -0
  208. package/open-browser.d.ts +2 -0
  209. package/open-browser.d.ts.map +1 -0
  210. package/open-browser.js +29 -0
  211. package/open-browser.js.map +1 -0
  212. package/package.json +10 -13
  213. package/schedule/alerts.d.ts +5 -0
  214. package/schedule/alerts.d.ts.map +1 -0
  215. package/schedule/alerts.js +54 -0
  216. package/schedule/alerts.js.map +1 -0
  217. package/schedule/discovery.d.ts +4 -0
  218. package/schedule/discovery.d.ts.map +1 -0
  219. package/schedule/discovery.js +36 -0
  220. package/schedule/discovery.js.map +1 -0
  221. package/schedule/notifiers/email.d.ts +3 -0
  222. package/schedule/notifiers/email.d.ts.map +1 -0
  223. package/schedule/notifiers/email.js +76 -0
  224. package/schedule/notifiers/email.js.map +1 -0
  225. package/schedule/notifiers/file.d.ts +3 -0
  226. package/schedule/notifiers/file.d.ts.map +1 -0
  227. package/schedule/notifiers/file.js +50 -0
  228. package/schedule/notifiers/file.js.map +1 -0
  229. package/schedule/notifiers/index.d.ts +10 -0
  230. package/schedule/notifiers/index.d.ts.map +1 -0
  231. package/schedule/notifiers/index.js +33 -0
  232. package/schedule/notifiers/index.js.map +1 -0
  233. package/schedule/notifiers/slack.d.ts +3 -0
  234. package/schedule/notifiers/slack.d.ts.map +1 -0
  235. package/schedule/notifiers/slack.js +58 -0
  236. package/schedule/notifiers/slack.js.map +1 -0
  237. package/schedule/runner.d.ts +14 -0
  238. package/schedule/runner.d.ts.map +1 -0
  239. package/schedule/runner.js +221 -0
  240. package/schedule/runner.js.map +1 -0
  241. package/schedule/runs.d.ts +5 -0
  242. package/schedule/runs.d.ts.map +1 -0
  243. package/schedule/runs.js +41 -0
  244. package/schedule/runs.js.map +1 -0
  245. package/schedule/service.d.ts +13 -0
  246. package/schedule/service.d.ts.map +1 -0
  247. package/schedule/service.js +87 -0
  248. package/schedule/service.js.map +1 -0
  249. package/schedule/types.d.ts +70 -0
  250. package/schedule/types.d.ts.map +1 -0
  251. package/schedule/types.js +2 -0
  252. package/schedule/types.js.map +1 -0
  253. package/semantic-import.d.ts +135 -0
  254. package/semantic-import.d.ts.map +1 -0
  255. package/semantic-import.js +979 -0
  256. package/semantic-import.js.map +1 -0
  257. package/semantic-import.test.d.ts +2 -0
  258. package/semantic-import.test.d.ts.map +1 -0
  259. package/semantic-import.test.js +95 -0
  260. package/semantic-import.test.js.map +1 -0
@@ -0,0 +1,979 @@
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ import { CubejsProvider, DbtProvider, SnowflakeSemanticProvider, resolveRepoSource, } from '@duckcodeailabs/dql-core';
4
+ const MANIFEST_RELATIVE_PATH = 'semantic-layer/imports/manifest.json';
5
+ export async function performSemanticImport(opts) {
6
+ const targetProjectRoot = resolve(opts.targetProjectRoot);
7
+ const previousManifest = loadSemanticImportManifest(targetProjectRoot);
8
+ const previousManaged = new Set(previousManifest?.generatedFiles ?? []);
9
+ const source = resolveImportSource(opts.targetProjectRoot, opts.sourceConfig);
10
+ const layer = await loadLayerForImport(opts.provider, source.localPath, source.config, opts.executeQuery);
11
+ const warnings = [...source.warnings, ...collectImportWarnings(opts.provider, layer)];
12
+ const objects = collectObjects(layer);
13
+ const generatedFiles = [];
14
+ const manifestObjects = [];
15
+ for (const relPath of previousManaged) {
16
+ const absPath = join(targetProjectRoot, relPath);
17
+ if (existsSync(absPath)) {
18
+ rmSync(absPath, { force: true });
19
+ }
20
+ }
21
+ for (const object of objects) {
22
+ const normalizedDomain = normalizeDomain(object.domain);
23
+ const filePath = buildSemanticFilePath(object.kind, normalizedDomain, object.name);
24
+ const absPath = join(targetProjectRoot, filePath);
25
+ if (existsSync(absPath) && !previousManaged.has(filePath)) {
26
+ throw new Error(`Import conflict: ${filePath} already exists and is not managed by semantic import.`);
27
+ }
28
+ mkdirSync(dirname(absPath), { recursive: true });
29
+ writeFileSync(absPath, serializeSemanticObject(object), 'utf-8');
30
+ generatedFiles.push(filePath);
31
+ manifestObjects.push({
32
+ id: objectId(object.kind, object.name),
33
+ kind: object.kind,
34
+ name: object.name,
35
+ label: object.label,
36
+ domain: normalizedDomain,
37
+ cube: 'cube' in object ? object.cube : undefined,
38
+ filePath,
39
+ source: object.source,
40
+ });
41
+ }
42
+ const manifest = {
43
+ version: 1,
44
+ mode: 'imported',
45
+ provider: opts.provider,
46
+ importedAt: new Date().toISOString(),
47
+ source: {
48
+ projectPath: opts.sourceConfig.projectPath,
49
+ repoUrl: opts.sourceConfig.repoUrl,
50
+ branch: opts.sourceConfig.branch,
51
+ subPath: opts.sourceConfig.subPath,
52
+ connection: opts.sourceConfig.connection,
53
+ },
54
+ warnings,
55
+ generatedFiles: [...generatedFiles, MANIFEST_RELATIVE_PATH],
56
+ objects: manifestObjects,
57
+ };
58
+ const manifestPath = join(targetProjectRoot, MANIFEST_RELATIVE_PATH);
59
+ mkdirSync(dirname(manifestPath), { recursive: true });
60
+ writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
61
+ applyCanonicalSemanticConfig(targetProjectRoot);
62
+ return {
63
+ manifest,
64
+ counts: countObjects(manifestObjects),
65
+ };
66
+ }
67
+ export function syncSemanticImport(opts) {
68
+ const manifest = loadSemanticImportManifest(opts.targetProjectRoot);
69
+ if (!manifest) {
70
+ throw new Error('No semantic import manifest found. Run `dql semantic import <provider>` first.');
71
+ }
72
+ const sourceConfig = {
73
+ provider: manifest.provider,
74
+ projectPath: manifest.source.projectPath,
75
+ repoUrl: manifest.source.repoUrl,
76
+ branch: manifest.source.branch,
77
+ subPath: manifest.source.subPath,
78
+ connection: manifest.source.connection,
79
+ };
80
+ return performSemanticImport({
81
+ targetProjectRoot: opts.targetProjectRoot,
82
+ provider: manifest.provider,
83
+ sourceConfig,
84
+ executeQuery: opts.executeQuery,
85
+ });
86
+ }
87
+ export function loadSemanticImportManifest(projectRoot) {
88
+ const manifestPath = join(resolve(projectRoot), MANIFEST_RELATIVE_PATH);
89
+ if (!existsSync(manifestPath))
90
+ return null;
91
+ try {
92
+ return JSON.parse(readFileSync(manifestPath, 'utf-8'));
93
+ }
94
+ catch {
95
+ return null;
96
+ }
97
+ }
98
+ export function buildSemanticTree(layer, manifest) {
99
+ const providerName = manifest?.provider ?? 'dql';
100
+ const domains = layer.listDomains();
101
+ const cubes = layer.listCubes();
102
+ const metrics = layer.listMetrics();
103
+ const measures = layer.listMeasures();
104
+ const dimensions = layer.listDimensions();
105
+ const timeDimensions = layer.listTimeDimensions();
106
+ const entities = layer.listEntities();
107
+ const hierarchies = layer.listHierarchies();
108
+ const segments = layer.listSegments();
109
+ const preAggregations = layer.listPreAggregations();
110
+ const semanticModels = layer.listSemanticModels();
111
+ const savedQueries = layer.listSavedQueries();
112
+ const domainNodes = domains.map((domain) => {
113
+ const domainCubes = cubes.filter((cube) => cube.domain === domain);
114
+ const looseMetrics = metrics.filter((metric) => metric.domain === domain && !metric.cube);
115
+ const looseDimensions = dimensions.filter((dimension) => dimension.domain === domain && !dimension.cube);
116
+ const looseMeasures = measures.filter((measure) => measure.domain === domain && !measure.cube);
117
+ const looseEntities = entities.filter((entity) => entity.domain === domain && !entity.cube);
118
+ const domainHierarchies = hierarchies.filter((hierarchy) => hierarchy.domain === domain);
119
+ const domainSemanticModels = semanticModels.filter((model) => model.domain === domain);
120
+ const domainSavedQueries = savedQueries.filter((query) => query.domain === domain);
121
+ const cubeNodes = domainCubes.map((cube) => ({
122
+ id: objectId('cube', cube.name),
123
+ label: cube.label,
124
+ kind: 'cube',
125
+ count: cube.measures.length +
126
+ cube.dimensions.length +
127
+ cube.timeDimensions.length +
128
+ cube.segments.length +
129
+ cube.preAggregations.length,
130
+ meta: {
131
+ provider: cube.source?.provider ?? providerName,
132
+ domain: normalizeDomain(cube.domain),
133
+ cube: cube.name,
134
+ owner: cube.owner ?? null,
135
+ tags: (cube.tags ?? []).join(','),
136
+ table: cube.table,
137
+ },
138
+ children: [
139
+ buildGroupNode(`cube:${cube.name}`, 'measure', 'Measures', measures.filter((measure) => measure.cube === cube.name).map((measure) => toLeaf('measure', measure.name, measure.label, {
140
+ provider: measure.source?.provider ?? providerName,
141
+ domain: normalizeDomain(measure.domain),
142
+ cube: measure.cube ?? cube.name,
143
+ owner: measure.owner ?? cube.owner ?? null,
144
+ tags: (measure.tags ?? []).join(','),
145
+ table: measure.table,
146
+ agg: measure.agg,
147
+ }))),
148
+ buildGroupNode(`cube:${cube.name}`, 'metric', 'Measures', cube.measures.map((metric) => toLeaf('metric', metric.name, metric.label, {
149
+ provider: metric.source?.provider ?? providerName,
150
+ domain: normalizeDomain(metric.domain),
151
+ cube: metric.cube ?? cube.name,
152
+ owner: metric.owner ?? cube.owner ?? null,
153
+ tags: (metric.tags ?? []).join(','),
154
+ table: metric.table,
155
+ }))),
156
+ buildGroupNode(`cube:${cube.name}`, 'dimension', 'Dimensions', cube.dimensions.map((dimension) => toLeaf('dimension', dimension.name, dimension.label, {
157
+ provider: dimension.source?.provider ?? providerName,
158
+ domain: normalizeDomain(dimension.domain),
159
+ cube: dimension.cube ?? cube.name,
160
+ owner: dimension.owner ?? cube.owner ?? null,
161
+ tags: (dimension.tags ?? []).join(','),
162
+ table: dimension.table,
163
+ }))),
164
+ buildGroupNode(`cube:${cube.name}`, 'time_dimension', 'Time Dimensions', cube.timeDimensions.map((dimension) => toLeaf('time_dimension', dimension.name, dimension.label, {
165
+ provider: dimension.source?.provider ?? providerName,
166
+ domain: normalizeDomain(dimension.domain),
167
+ cube: dimension.cube ?? cube.name,
168
+ owner: dimension.owner ?? cube.owner ?? null,
169
+ tags: (dimension.tags ?? []).join(','),
170
+ table: dimension.table,
171
+ }))),
172
+ buildGroupNode(`cube:${cube.name}`, 'entity', 'Entities', entities.filter((entity) => entity.cube === cube.name).map((entity) => toLeaf('entity', entity.name, entity.label, {
173
+ provider: entity.source?.provider ?? providerName,
174
+ domain: normalizeDomain(entity.domain),
175
+ cube: entity.cube ?? cube.name,
176
+ owner: entity.owner ?? cube.owner ?? null,
177
+ tags: (entity.tags ?? []).join(','),
178
+ table: entity.table,
179
+ type: entity.type,
180
+ }))),
181
+ buildGroupNode(`cube:${cube.name}`, 'segment', 'Segments', cube.segments.map((segment) => toLeaf('segment', segment.name, segment.label, {
182
+ provider: segment.source?.provider ?? providerName,
183
+ domain: normalizeDomain(segment.domain),
184
+ cube: segment.cube || cube.name,
185
+ owner: segment.owner ?? cube.owner ?? null,
186
+ tags: (segment.tags ?? []).join(','),
187
+ }))),
188
+ buildGroupNode(`cube:${cube.name}`, 'pre_aggregation', 'Pre-aggregations', cube.preAggregations.map((preAggregation) => toLeaf('pre_aggregation', preAggregation.name, preAggregation.label, {
189
+ provider: preAggregation.source?.provider ?? providerName,
190
+ domain: normalizeDomain(preAggregation.domain),
191
+ cube: preAggregation.cube || cube.name,
192
+ owner: preAggregation.owner ?? cube.owner ?? null,
193
+ tags: (preAggregation.tags ?? []).join(','),
194
+ }))),
195
+ ].filter((node) => Boolean(node)),
196
+ }));
197
+ const children = [...cubeNodes];
198
+ const looseNodes = [
199
+ buildGroupNode(`domain:${domain}`, 'metric', 'Metrics', looseMetrics.map((metric) => toLeaf('metric', metric.name, metric.label, {
200
+ provider: metric.source?.provider ?? providerName,
201
+ domain: normalizeDomain(metric.domain),
202
+ cube: metric.cube ?? null,
203
+ owner: metric.owner ?? null,
204
+ tags: (metric.tags ?? []).join(','),
205
+ table: metric.table,
206
+ }))),
207
+ buildGroupNode(`domain:${domain}`, 'measure', 'Measures', looseMeasures.map((measure) => toLeaf('measure', measure.name, measure.label, {
208
+ provider: measure.source?.provider ?? providerName,
209
+ domain: normalizeDomain(measure.domain),
210
+ cube: measure.cube ?? null,
211
+ owner: measure.owner ?? null,
212
+ tags: (measure.tags ?? []).join(','),
213
+ table: measure.table,
214
+ }))),
215
+ buildGroupNode(`domain:${domain}`, 'dimension', 'Dimensions', looseDimensions.map((dimension) => toLeaf('dimension', dimension.name, dimension.label, {
216
+ provider: dimension.source?.provider ?? providerName,
217
+ domain: normalizeDomain(dimension.domain),
218
+ cube: dimension.cube ?? null,
219
+ owner: dimension.owner ?? null,
220
+ tags: (dimension.tags ?? []).join(','),
221
+ table: dimension.table,
222
+ }))),
223
+ buildGroupNode(`domain:${domain}`, 'time_dimension', 'Time Dimensions', timeDimensions.filter((dimension) => dimension.domain === domain && !dimension.cube).map((dimension) => toLeaf('time_dimension', dimension.name, dimension.label, {
224
+ provider: dimension.source?.provider ?? providerName,
225
+ domain: normalizeDomain(dimension.domain),
226
+ cube: dimension.cube ?? null,
227
+ owner: dimension.owner ?? null,
228
+ tags: (dimension.tags ?? []).join(','),
229
+ table: dimension.table,
230
+ }))),
231
+ buildGroupNode(`domain:${domain}`, 'entity', 'Entities', looseEntities.map((entity) => toLeaf('entity', entity.name, entity.label, {
232
+ provider: entity.source?.provider ?? providerName,
233
+ domain: normalizeDomain(entity.domain),
234
+ cube: entity.cube ?? null,
235
+ owner: entity.owner ?? null,
236
+ tags: (entity.tags ?? []).join(','),
237
+ table: entity.table,
238
+ }))),
239
+ buildGroupNode(`domain:${domain}`, 'semantic_model', 'Semantic Models', domainSemanticModels.map((model) => toLeaf('semantic_model', model.name, model.label, {
240
+ provider: model.source?.provider ?? providerName,
241
+ domain: normalizeDomain(model.domain),
242
+ owner: model.owner ?? null,
243
+ tags: (model.tags ?? []).join(','),
244
+ table: model.table,
245
+ }))),
246
+ buildGroupNode(`domain:${domain}`, 'saved_query', 'Saved Queries', domainSavedQueries.map((query) => toLeaf('saved_query', query.name, query.label, {
247
+ provider: query.source?.provider ?? providerName,
248
+ domain: normalizeDomain(query.domain),
249
+ owner: query.owner ?? null,
250
+ tags: (query.tags ?? []).join(','),
251
+ }))),
252
+ buildGroupNode(`domain:${domain}`, 'hierarchy', 'Hierarchies', domainHierarchies.map((hierarchy) => toLeaf('hierarchy', hierarchy.name, hierarchy.label, {
253
+ provider: hierarchy.source?.provider ?? providerName,
254
+ domain: normalizeDomain(hierarchy.domain),
255
+ owner: hierarchy.owner ?? null,
256
+ tags: (hierarchy.tags ?? []).join(','),
257
+ }))),
258
+ buildGroupNode(`domain:${domain}`, 'segment', 'Segments', segments.filter((segment) => segment.domain === domain && !segment.cube).map((segment) => toLeaf('segment', segment.name, segment.label, {
259
+ provider: segment.source?.provider ?? providerName,
260
+ domain: normalizeDomain(segment.domain),
261
+ cube: segment.cube || null,
262
+ owner: segment.owner ?? null,
263
+ tags: (segment.tags ?? []).join(','),
264
+ }))),
265
+ buildGroupNode(`domain:${domain}`, 'pre_aggregation', 'Pre-aggregations', preAggregations.filter((preAggregation) => preAggregation.domain === domain && !preAggregation.cube).map((preAggregation) => toLeaf('pre_aggregation', preAggregation.name, preAggregation.label, {
266
+ provider: preAggregation.source?.provider ?? providerName,
267
+ domain: normalizeDomain(preAggregation.domain),
268
+ cube: preAggregation.cube || null,
269
+ owner: preAggregation.owner ?? null,
270
+ tags: (preAggregation.tags ?? []).join(','),
271
+ }))),
272
+ ].filter((node) => Boolean(node));
273
+ children.push(...looseNodes);
274
+ return {
275
+ id: `domain:${domain}`,
276
+ label: domain,
277
+ kind: 'domain',
278
+ count: children.reduce((sum, node) => sum + (node.count ?? 0), 0),
279
+ meta: {
280
+ provider: providerName,
281
+ domain,
282
+ },
283
+ children,
284
+ };
285
+ });
286
+ return {
287
+ id: `provider:${manifest?.provider ?? 'dql'}`,
288
+ label: manifest?.provider ? `${manifest.provider} import` : 'semantic layer',
289
+ kind: 'provider',
290
+ count: domains.length,
291
+ meta: {
292
+ provider: providerName,
293
+ importedAt: manifest?.importedAt ?? null,
294
+ warnings: manifest?.warnings.length ?? 0,
295
+ },
296
+ children: domainNodes,
297
+ };
298
+ }
299
+ export function buildSemanticObjectDetail(layer, manifest, id) {
300
+ const [kind, ...rest] = id.split(':');
301
+ const name = rest.join(':');
302
+ const manifestObject = manifest?.objects.find((object) => object.id === id) ?? null;
303
+ const importedAt = manifest?.importedAt ?? null;
304
+ if (kind === 'cube') {
305
+ const cube = layer.getCube(name);
306
+ if (!cube)
307
+ return null;
308
+ return {
309
+ id,
310
+ kind: 'cube',
311
+ name: cube.name,
312
+ label: cube.label,
313
+ description: cube.description,
314
+ domain: cube.domain || normalizeDomain(undefined),
315
+ table: cube.table,
316
+ sql: cube.sql,
317
+ tags: cube.tags ?? [],
318
+ owner: cube.owner ?? null,
319
+ source: cube.source ?? manifestObject?.source ?? null,
320
+ filePath: manifestObject?.filePath ?? null,
321
+ importedAt,
322
+ joins: cube.joins,
323
+ };
324
+ }
325
+ if (kind === 'metric') {
326
+ const metric = layer.getMetric(name);
327
+ if (!metric)
328
+ return null;
329
+ return {
330
+ id,
331
+ kind: 'metric',
332
+ name: metric.name,
333
+ label: metric.label,
334
+ description: metric.description,
335
+ domain: normalizeDomain(metric.domain),
336
+ cube: metric.cube,
337
+ table: metric.table,
338
+ sql: metric.sql,
339
+ type: metric.type,
340
+ metricType: metric.metricType,
341
+ typeParams: metric.typeParams,
342
+ filter: metric.filter,
343
+ tags: metric.tags ?? [],
344
+ owner: metric.owner ?? null,
345
+ source: metric.source ?? manifestObject?.source ?? null,
346
+ filePath: manifestObject?.filePath ?? null,
347
+ importedAt,
348
+ };
349
+ }
350
+ if (kind === 'measure') {
351
+ const measure = layer.getMeasure(name);
352
+ if (!measure)
353
+ return null;
354
+ return {
355
+ id,
356
+ kind: 'measure',
357
+ name: measure.name,
358
+ label: measure.label,
359
+ description: measure.description,
360
+ domain: normalizeDomain(measure.domain),
361
+ cube: measure.cube,
362
+ table: measure.table,
363
+ sql: measure.expr,
364
+ type: measure.agg,
365
+ agg: measure.agg,
366
+ expr: measure.expr,
367
+ filter: measure.filter,
368
+ typeParams: {
369
+ agg_time_dimension: measure.aggTimeDimension,
370
+ create_metric: measure.createMetric,
371
+ non_additive_dimension: measure.nonAdditiveDimension,
372
+ },
373
+ tags: measure.tags ?? [],
374
+ owner: measure.owner ?? null,
375
+ source: measure.source ?? manifestObject?.source ?? null,
376
+ filePath: manifestObject?.filePath ?? null,
377
+ importedAt,
378
+ };
379
+ }
380
+ if (kind === 'dimension') {
381
+ const dimension = layer.getDimension(name);
382
+ if (!dimension)
383
+ return null;
384
+ return {
385
+ id,
386
+ kind: 'dimension',
387
+ name: dimension.name,
388
+ label: dimension.label,
389
+ description: dimension.description,
390
+ domain: normalizeDomain(dimension.domain),
391
+ cube: dimension.cube,
392
+ table: dimension.table,
393
+ sql: dimension.sql,
394
+ type: dimension.type,
395
+ tags: dimension.tags ?? [],
396
+ owner: dimension.owner ?? null,
397
+ source: dimension.source ?? manifestObject?.source ?? null,
398
+ filePath: manifestObject?.filePath ?? null,
399
+ importedAt,
400
+ };
401
+ }
402
+ if (kind === 'time_dimension') {
403
+ const dimension = layer.getDimension(name);
404
+ if (!dimension)
405
+ return null;
406
+ return {
407
+ id,
408
+ kind: 'time_dimension',
409
+ name: dimension.name,
410
+ label: dimension.label,
411
+ description: dimension.description,
412
+ domain: normalizeDomain(dimension.domain),
413
+ cube: dimension.cube,
414
+ table: dimension.table,
415
+ sql: dimension.sql,
416
+ type: dimension.type,
417
+ typeParams: dimension.typeParams,
418
+ tags: dimension.tags ?? [],
419
+ owner: dimension.owner ?? null,
420
+ source: dimension.source ?? manifestObject?.source ?? null,
421
+ filePath: manifestObject?.filePath ?? null,
422
+ importedAt,
423
+ };
424
+ }
425
+ if (kind === 'entity') {
426
+ const entity = layer.getEntity(name);
427
+ if (!entity)
428
+ return null;
429
+ return {
430
+ id,
431
+ kind: 'entity',
432
+ name: entity.name,
433
+ label: entity.label,
434
+ description: entity.description,
435
+ domain: normalizeDomain(entity.domain),
436
+ cube: entity.cube,
437
+ table: entity.table,
438
+ sql: entity.expr,
439
+ type: entity.type,
440
+ tags: entity.tags ?? [],
441
+ owner: entity.owner ?? null,
442
+ source: entity.source ?? manifestObject?.source ?? null,
443
+ filePath: manifestObject?.filePath ?? null,
444
+ importedAt,
445
+ };
446
+ }
447
+ if (kind === 'semantic_model') {
448
+ const model = layer.getSemanticModel(name);
449
+ if (!model)
450
+ return null;
451
+ return {
452
+ id,
453
+ kind: 'semantic_model',
454
+ name: model.name,
455
+ label: model.label,
456
+ description: model.description,
457
+ domain: normalizeDomain(model.domain),
458
+ table: model.table,
459
+ sql: model.model,
460
+ tags: model.tags ?? [],
461
+ owner: model.owner ?? null,
462
+ source: model.source ?? manifestObject?.source ?? null,
463
+ filePath: manifestObject?.filePath ?? null,
464
+ importedAt,
465
+ entities: model.entities,
466
+ measures: model.measures,
467
+ dimensions: model.dimensions,
468
+ timeDimension: model.timeDimensions.join(', '),
469
+ typeParams: model.defaults,
470
+ };
471
+ }
472
+ if (kind === 'saved_query') {
473
+ const savedQuery = layer.getSavedQuery(name);
474
+ if (!savedQuery)
475
+ return null;
476
+ return {
477
+ id,
478
+ kind: 'saved_query',
479
+ name: savedQuery.name,
480
+ label: savedQuery.label,
481
+ description: savedQuery.description,
482
+ domain: normalizeDomain(savedQuery.domain),
483
+ tags: savedQuery.tags ?? [],
484
+ owner: savedQuery.owner ?? null,
485
+ source: savedQuery.source ?? manifestObject?.source ?? null,
486
+ filePath: manifestObject?.filePath ?? null,
487
+ importedAt,
488
+ savedQueryMetrics: savedQuery.metrics,
489
+ dimensions: savedQuery.dimensions,
490
+ timeDimension: savedQuery.timeDimension,
491
+ granularity: savedQuery.granularity,
492
+ filter: savedQuery.filters,
493
+ exports: savedQuery.exports,
494
+ };
495
+ }
496
+ if (kind === 'hierarchy') {
497
+ const hierarchy = layer.getHierarchy(name);
498
+ if (!hierarchy)
499
+ return null;
500
+ return {
501
+ id,
502
+ kind: 'hierarchy',
503
+ name: hierarchy.name,
504
+ label: hierarchy.label,
505
+ description: hierarchy.description,
506
+ domain: normalizeDomain(hierarchy.domain),
507
+ tags: hierarchy.tags ?? [],
508
+ owner: hierarchy.owner ?? null,
509
+ source: hierarchy.source ?? manifestObject?.source ?? null,
510
+ filePath: manifestObject?.filePath ?? null,
511
+ importedAt,
512
+ levels: hierarchy.levels,
513
+ };
514
+ }
515
+ if (kind === 'segment') {
516
+ const segment = layer.getSegment(name);
517
+ if (!segment)
518
+ return null;
519
+ return {
520
+ id,
521
+ kind: 'segment',
522
+ name: segment.name,
523
+ label: segment.label,
524
+ description: segment.description,
525
+ domain: normalizeDomain(segment.domain),
526
+ cube: segment.cube,
527
+ sql: segment.sql,
528
+ tags: segment.tags ?? [],
529
+ owner: segment.owner ?? null,
530
+ source: segment.source ?? manifestObject?.source ?? null,
531
+ filePath: manifestObject?.filePath ?? null,
532
+ importedAt,
533
+ };
534
+ }
535
+ if (kind === 'pre_aggregation') {
536
+ const preAggregation = layer.getPreAggregation(name);
537
+ if (!preAggregation)
538
+ return null;
539
+ return {
540
+ id,
541
+ kind: 'pre_aggregation',
542
+ name: preAggregation.name,
543
+ label: preAggregation.label,
544
+ description: preAggregation.description,
545
+ domain: normalizeDomain(preAggregation.domain),
546
+ cube: preAggregation.cube,
547
+ sql: preAggregation.sql,
548
+ tags: preAggregation.tags ?? [],
549
+ owner: preAggregation.owner ?? null,
550
+ source: preAggregation.source ?? manifestObject?.source ?? null,
551
+ filePath: manifestObject?.filePath ?? null,
552
+ importedAt,
553
+ measures: preAggregation.measures,
554
+ dimensions: preAggregation.dimensions,
555
+ timeDimension: preAggregation.timeDimension,
556
+ granularity: preAggregation.granularity,
557
+ refreshKey: preAggregation.refreshKey,
558
+ };
559
+ }
560
+ return null;
561
+ }
562
+ /**
563
+ * Preview a semantic import without writing any files.
564
+ * Returns counts, domains, warnings, and object summaries.
565
+ */
566
+ export async function previewSemanticImport(opts) {
567
+ const targetProjectRoot = resolve(opts.targetProjectRoot);
568
+ const source = resolveImportSource(targetProjectRoot, opts.sourceConfig);
569
+ const layer = await loadLayerForImport(opts.provider, source.localPath, source.config, opts.executeQuery);
570
+ const warnings = [...source.warnings, ...collectImportWarnings(opts.provider, layer)];
571
+ const objects = collectObjects(layer);
572
+ const summaries = objects.map((obj) => ({
573
+ kind: obj.kind,
574
+ name: obj.name,
575
+ label: obj.label,
576
+ domain: normalizeDomain(obj.domain),
577
+ }));
578
+ const domains = [...new Set(summaries.map((o) => o.domain))].sort();
579
+ return {
580
+ provider: opts.provider,
581
+ counts: countObjects(summaries.map((o) => ({ ...o, id: objectId(o.kind, o.name), filePath: '' }))),
582
+ domains,
583
+ warnings,
584
+ objects: summaries,
585
+ };
586
+ }
587
+ /**
588
+ * Compute the diff between the current manifest and a fresh import, without applying changes.
589
+ */
590
+ export async function computeSyncDiff(opts) {
591
+ const manifest = loadSemanticImportManifest(opts.targetProjectRoot);
592
+ if (!manifest) {
593
+ throw new Error('No semantic import manifest found. Run `dql semantic import <provider>` first.');
594
+ }
595
+ const sourceConfig = {
596
+ provider: manifest.provider,
597
+ projectPath: manifest.source.projectPath,
598
+ repoUrl: manifest.source.repoUrl,
599
+ branch: manifest.source.branch,
600
+ subPath: manifest.source.subPath,
601
+ connection: manifest.source.connection,
602
+ };
603
+ const targetProjectRoot = resolve(opts.targetProjectRoot);
604
+ const source = resolveImportSource(targetProjectRoot, sourceConfig);
605
+ const layer = await loadLayerForImport(manifest.provider, source.localPath, source.config, opts.executeQuery);
606
+ const newObjects = collectObjects(layer);
607
+ const newById = new Map(newObjects.map((obj) => [objectId(obj.kind, obj.name), obj]));
608
+ const oldById = new Map(manifest.objects.map((obj) => [obj.id, obj]));
609
+ const added = [];
610
+ const removed = [];
611
+ const changed = [];
612
+ let unchanged = 0;
613
+ for (const [id, obj] of newById) {
614
+ const old = oldById.get(id);
615
+ if (!old) {
616
+ added.push({ kind: obj.kind, name: obj.name, label: obj.label, domain: normalizeDomain(obj.domain) });
617
+ }
618
+ else {
619
+ // Compare domain and label to detect changes
620
+ const oldDomain = old.domain;
621
+ const newDomain = normalizeDomain(obj.domain);
622
+ if (old.label !== obj.label || oldDomain !== newDomain) {
623
+ changed.push({ kind: obj.kind, name: obj.name, label: obj.label, domain: newDomain });
624
+ }
625
+ else {
626
+ unchanged++;
627
+ }
628
+ }
629
+ }
630
+ for (const [id, old] of oldById) {
631
+ if (!newById.has(id)) {
632
+ removed.push({ kind: old.kind, name: old.name, label: old.label, domain: old.domain });
633
+ }
634
+ }
635
+ return { added, removed, changed, unchanged };
636
+ }
637
+ function resolveImportSource(targetProjectRoot, sourceConfig) {
638
+ const projectRoot = resolve(targetProjectRoot);
639
+ if (sourceConfig.provider === 'snowflake') {
640
+ return {
641
+ localPath: projectRoot,
642
+ config: sourceConfig,
643
+ warnings: [],
644
+ };
645
+ }
646
+ if (sourceConfig.projectPath && !sourceConfig.repoUrl && sourceConfig.source !== 'github' && sourceConfig.source !== 'gitlab') {
647
+ return {
648
+ localPath: resolve(projectRoot, sourceConfig.projectPath),
649
+ config: { ...sourceConfig, projectPath: undefined },
650
+ warnings: [],
651
+ };
652
+ }
653
+ const resolved = resolveRepoSource(sourceConfig, projectRoot);
654
+ return {
655
+ localPath: resolved.localPath,
656
+ config: { ...sourceConfig, projectPath: undefined, source: 'local' },
657
+ warnings: resolved.warnings,
658
+ };
659
+ }
660
+ async function loadLayerForImport(provider, sourceRoot, config, executeQuery) {
661
+ if (provider === 'dbt') {
662
+ return new DbtProvider().load({ ...config, provider }, sourceRoot);
663
+ }
664
+ if (provider === 'cubejs') {
665
+ return new CubejsProvider().load({ ...config, provider }, sourceRoot);
666
+ }
667
+ if (!executeQuery) {
668
+ throw new Error('Snowflake semantic import requires an active query executor.');
669
+ }
670
+ return new SnowflakeSemanticProvider(executeQuery).loadAsync({ ...config, provider }, sourceRoot);
671
+ }
672
+ function collectObjects(layer) {
673
+ const timeDimensionNames = new Set(layer.listTimeDimensions().map((dimension) => dimension.name));
674
+ return [
675
+ ...layer.listCubes().map((cube) => ({ ...cube, kind: 'cube' })),
676
+ ...layer.listMetrics().map((metric) => ({ ...metric, kind: 'metric' })),
677
+ ...layer.listMeasures().map((measure) => ({ ...measure, kind: 'measure' })),
678
+ ...layer.listDimensions().filter((dimension) => !timeDimensionNames.has(dimension.name)).map((dimension) => ({ ...dimension, kind: 'dimension' })),
679
+ ...layer.listTimeDimensions().map((dimension) => ({ ...dimension, kind: 'time_dimension' })),
680
+ ...layer.listEntities().map((entity) => ({ ...entity, kind: 'entity' })),
681
+ ...layer.listHierarchies().map((hierarchy) => ({ ...hierarchy, kind: 'hierarchy' })),
682
+ ...layer.listSegments().map((segment) => ({ ...segment, kind: 'segment' })),
683
+ ...layer.listPreAggregations().map((preAggregation) => ({ ...preAggregation, kind: 'pre_aggregation' })),
684
+ ...layer.listSemanticModels().map((model) => ({ ...model, kind: 'semantic_model' })),
685
+ ...layer.listSavedQueries().map((query) => ({ ...query, kind: 'saved_query' })),
686
+ ];
687
+ }
688
+ function buildSemanticFilePath(kind, domain, name) {
689
+ const folder = kind === 'pre_aggregation'
690
+ ? 'pre_aggregations'
691
+ : kind === 'time_dimension'
692
+ ? 'time_dimensions'
693
+ : kind === 'semantic_model'
694
+ ? 'semantic_models'
695
+ : kind === 'saved_query'
696
+ ? 'saved_queries'
697
+ : `${kind}s`.replace('hierarchys', 'hierarchies');
698
+ return join('semantic-layer', folder, slugifyPathSegment(domain), `${slugifyPathSegment(name)}.yaml`);
699
+ }
700
+ function serializeSemanticObject(object) {
701
+ const lines = [
702
+ `name: ${yamlScalar(object.name)}`,
703
+ `label: ${yamlScalar(object.label)}`,
704
+ `description: ${yamlScalar(object.description)}`,
705
+ `domain: ${yamlScalar(object.domain || normalizeDomain(undefined))}`,
706
+ ];
707
+ if ('table' in object && object.table)
708
+ lines.push(`table: ${yamlScalar(object.table)}`);
709
+ if ('cube' in object && object.cube)
710
+ lines.push(`cube: ${yamlScalar(object.cube)}`);
711
+ if ('sql' in object && typeof object.sql === 'string')
712
+ lines.push(`sql: ${yamlBlockScalar(object.sql)}`);
713
+ if ('type' in object && typeof object.type === 'string')
714
+ lines.push(`type: ${yamlScalar(object.type)}`);
715
+ if ('aggregation' in object && object.aggregation)
716
+ lines.push(`aggregation: ${yamlScalar(object.aggregation)}`);
717
+ if ('agg' in object && object.agg)
718
+ lines.push(`agg: ${yamlScalar(object.agg)}`);
719
+ if ('expr' in object && object.expr)
720
+ lines.push(`expr: ${yamlScalar(object.expr)}`);
721
+ if ('metricType' in object && object.metricType)
722
+ lines.push(`metricType: ${yamlScalar(object.metricType)}`);
723
+ if ('aggTimeDimension' in object && object.aggTimeDimension)
724
+ lines.push(`aggTimeDimension: ${yamlScalar(object.aggTimeDimension)}`);
725
+ if ('owner' in object && object.owner)
726
+ lines.push(`owner: ${yamlScalar(object.owner)}`);
727
+ if ('tags' in object && object.tags && object.tags.length > 0) {
728
+ lines.push('tags:');
729
+ for (const tag of object.tags)
730
+ lines.push(` - ${yamlScalar(tag)}`);
731
+ }
732
+ if (object.source) {
733
+ lines.push('source:');
734
+ lines.push(` provider: ${yamlScalar(object.source.provider)}`);
735
+ lines.push(` objectType: ${yamlScalar(object.source.objectType)}`);
736
+ lines.push(` objectId: ${yamlScalar(object.source.objectId)}`);
737
+ if (object.source.objectName)
738
+ lines.push(` objectName: ${yamlScalar(object.source.objectName)}`);
739
+ if (object.source.importedAt)
740
+ lines.push(` importedAt: ${yamlScalar(object.source.importedAt)}`);
741
+ if (object.source.extra && Object.keys(object.source.extra).length > 0) {
742
+ lines.push(' extra:');
743
+ for (const [key, value] of Object.entries(object.source.extra)) {
744
+ lines.push(` ${key}: ${yamlScalar(String(value))}`);
745
+ }
746
+ }
747
+ }
748
+ if (object.kind === 'hierarchy') {
749
+ lines.push('levels:');
750
+ for (const level of object.levels) {
751
+ lines.push(` - name: ${yamlScalar(level.name)}`);
752
+ lines.push(` label: ${yamlScalar(level.label)}`);
753
+ lines.push(` description: ${yamlScalar(level.description)}`);
754
+ lines.push(` dimension: ${yamlScalar(level.dimension)}`);
755
+ lines.push(` order: ${level.order}`);
756
+ }
757
+ }
758
+ if (object.kind === 'cube') {
759
+ lines.push('measures:');
760
+ for (const measure of object.measures) {
761
+ lines.push(` - name: ${yamlScalar(measure.name)}`);
762
+ lines.push(` label: ${yamlScalar(measure.label)}`);
763
+ lines.push(` description: ${yamlScalar(measure.description)}`);
764
+ lines.push(` sql: ${yamlBlockScalar(measure.sql, 4)}`);
765
+ lines.push(` type: ${yamlScalar(measure.type)}`);
766
+ if (measure.aggregation)
767
+ lines.push(` aggregation: ${yamlScalar(measure.aggregation)}`);
768
+ }
769
+ lines.push('dimensions:');
770
+ for (const dimension of object.dimensions) {
771
+ lines.push(` - name: ${yamlScalar(dimension.name)}`);
772
+ lines.push(` label: ${yamlScalar(dimension.label)}`);
773
+ lines.push(` description: ${yamlScalar(dimension.description)}`);
774
+ lines.push(` sql: ${yamlBlockScalar(dimension.sql, 4)}`);
775
+ lines.push(` type: ${yamlScalar(dimension.type)}`);
776
+ }
777
+ if (object.timeDimensions.length > 0) {
778
+ lines.push('time_dimensions:');
779
+ for (const dimension of object.timeDimensions) {
780
+ lines.push(` - name: ${yamlScalar(dimension.name)}`);
781
+ lines.push(` label: ${yamlScalar(dimension.label)}`);
782
+ lines.push(` description: ${yamlScalar(dimension.description)}`);
783
+ lines.push(` sql: ${yamlBlockScalar(dimension.sql, 4)}`);
784
+ lines.push(' granularities:');
785
+ for (const granularity of dimension.granularities) {
786
+ lines.push(` - ${yamlScalar(granularity)}`);
787
+ }
788
+ if (dimension.primaryTime)
789
+ lines.push(' primary_time: true');
790
+ }
791
+ }
792
+ if (object.joins.length > 0) {
793
+ lines.push('joins:');
794
+ for (const joinDef of object.joins) {
795
+ lines.push(` - name: ${yamlScalar(joinDef.name)}`);
796
+ lines.push(` right: ${yamlScalar(joinDef.right)}`);
797
+ lines.push(` type: ${yamlScalar(joinDef.type)}`);
798
+ lines.push(` sql: ${yamlBlockScalar(joinDef.sql, 4)}`);
799
+ }
800
+ }
801
+ if (object.segments.length > 0) {
802
+ lines.push('segments:');
803
+ for (const segment of object.segments) {
804
+ lines.push(` - name: ${yamlScalar(segment.name)}`);
805
+ lines.push(` label: ${yamlScalar(segment.label)}`);
806
+ lines.push(` description: ${yamlScalar(segment.description)}`);
807
+ lines.push(` sql: ${yamlBlockScalar(segment.sql, 4)}`);
808
+ }
809
+ }
810
+ if (object.preAggregations.length > 0) {
811
+ lines.push('pre_aggregations:');
812
+ for (const preAggregation of object.preAggregations) {
813
+ lines.push(` - name: ${yamlScalar(preAggregation.name)}`);
814
+ lines.push(` label: ${yamlScalar(preAggregation.label)}`);
815
+ lines.push(` description: ${yamlScalar(preAggregation.description)}`);
816
+ if (preAggregation.measures?.length) {
817
+ lines.push(' measures:');
818
+ for (const measure of preAggregation.measures)
819
+ lines.push(` - ${yamlScalar(measure)}`);
820
+ }
821
+ if (preAggregation.dimensions?.length) {
822
+ lines.push(' dimensions:');
823
+ for (const dimension of preAggregation.dimensions)
824
+ lines.push(` - ${yamlScalar(dimension)}`);
825
+ }
826
+ if (preAggregation.timeDimension)
827
+ lines.push(` timeDimension: ${yamlScalar(preAggregation.timeDimension)}`);
828
+ if (preAggregation.granularity)
829
+ lines.push(` granularity: ${yamlScalar(preAggregation.granularity)}`);
830
+ if (preAggregation.refreshKey)
831
+ lines.push(` refreshKey: ${yamlScalar(preAggregation.refreshKey)}`);
832
+ }
833
+ }
834
+ }
835
+ if (object.kind === 'pre_aggregation') {
836
+ if (object.measures?.length) {
837
+ lines.push('measures:');
838
+ for (const measure of object.measures)
839
+ lines.push(` - ${yamlScalar(measure)}`);
840
+ }
841
+ if (object.dimensions?.length) {
842
+ lines.push('dimensions:');
843
+ for (const dimension of object.dimensions)
844
+ lines.push(` - ${yamlScalar(dimension)}`);
845
+ }
846
+ if (object.timeDimension)
847
+ lines.push(`timeDimension: ${yamlScalar(object.timeDimension)}`);
848
+ if (object.granularity)
849
+ lines.push(`granularity: ${yamlScalar(object.granularity)}`);
850
+ if (object.refreshKey)
851
+ lines.push(`refreshKey: ${yamlScalar(object.refreshKey)}`);
852
+ }
853
+ if (object.kind === 'semantic_model') {
854
+ if (object.model)
855
+ lines.push(`model: ${yamlScalar(object.model)}`);
856
+ if (object.entities.length > 0) {
857
+ lines.push('entities:');
858
+ for (const entity of object.entities)
859
+ lines.push(` - ${yamlScalar(entity)}`);
860
+ }
861
+ if (object.measures.length > 0) {
862
+ lines.push('measures:');
863
+ for (const measure of object.measures)
864
+ lines.push(` - ${yamlScalar(measure)}`);
865
+ }
866
+ if (object.dimensions.length > 0) {
867
+ lines.push('dimensions:');
868
+ for (const dimension of object.dimensions)
869
+ lines.push(` - ${yamlScalar(dimension)}`);
870
+ }
871
+ if (object.timeDimensions.length > 0) {
872
+ lines.push('timeDimensions:');
873
+ for (const dimension of object.timeDimensions)
874
+ lines.push(` - ${yamlScalar(dimension)}`);
875
+ }
876
+ }
877
+ if (object.kind === 'saved_query') {
878
+ if (object.metrics.length > 0) {
879
+ lines.push('metrics:');
880
+ for (const metric of object.metrics)
881
+ lines.push(` - ${yamlScalar(metric)}`);
882
+ }
883
+ if (object.dimensions.length > 0) {
884
+ lines.push('dimensions:');
885
+ for (const dimension of object.dimensions)
886
+ lines.push(` - ${yamlScalar(dimension)}`);
887
+ }
888
+ if (object.timeDimension)
889
+ lines.push(`timeDimension: ${yamlScalar(object.timeDimension)}`);
890
+ if (object.granularity)
891
+ lines.push(`granularity: ${yamlScalar(object.granularity)}`);
892
+ }
893
+ return lines.join('\n') + '\n';
894
+ }
895
+ function applyCanonicalSemanticConfig(projectRoot) {
896
+ const configPath = join(projectRoot, 'dql.config.json');
897
+ const raw = existsSync(configPath)
898
+ ? JSON.parse(readFileSync(configPath, 'utf-8'))
899
+ : {};
900
+ raw.semanticLayer = {
901
+ provider: 'dql',
902
+ path: './semantic-layer',
903
+ };
904
+ writeFileSync(configPath, JSON.stringify(raw, null, 2) + '\n', 'utf-8');
905
+ }
906
+ function collectImportWarnings(provider, layer) {
907
+ const warnings = [];
908
+ if (provider === 'dbt') {
909
+ if (layer.listHierarchies().length === 0) {
910
+ warnings.push('No dbt hierarchies were imported; dbt semantic models were normalized into cubes, measures, dimensions, and joins.');
911
+ }
912
+ }
913
+ if (provider === 'snowflake' && layer.listCubes().length === 0) {
914
+ warnings.push('Snowflake semantic import returned no semantic views.');
915
+ }
916
+ return warnings;
917
+ }
918
+ function countObjects(objects) {
919
+ return {
920
+ cube: objects.filter((object) => object.kind === 'cube').length,
921
+ metric: objects.filter((object) => object.kind === 'metric').length,
922
+ measure: objects.filter((object) => object.kind === 'measure').length,
923
+ dimension: objects.filter((object) => object.kind === 'dimension').length,
924
+ time_dimension: objects.filter((object) => object.kind === 'time_dimension').length,
925
+ entity: objects.filter((object) => object.kind === 'entity').length,
926
+ hierarchy: objects.filter((object) => object.kind === 'hierarchy').length,
927
+ segment: objects.filter((object) => object.kind === 'segment').length,
928
+ pre_aggregation: objects.filter((object) => object.kind === 'pre_aggregation').length,
929
+ semantic_model: objects.filter((object) => object.kind === 'semantic_model').length,
930
+ saved_query: objects.filter((object) => object.kind === 'saved_query').length,
931
+ };
932
+ }
933
+ function normalizeDomain(domain) {
934
+ return domain && domain.trim().length > 0 ? domain.trim() : 'uncategorized';
935
+ }
936
+ function slugifyPathSegment(value) {
937
+ return normalizeDomain(value)
938
+ .toLowerCase()
939
+ .replace(/[^a-z0-9/_-]+/g, '-')
940
+ .replace(/\/+/g, '/')
941
+ .replace(/^-+|-+$/g, '');
942
+ }
943
+ function objectId(kind, name) {
944
+ return `${kind}:${name}`;
945
+ }
946
+ function yamlScalar(value) {
947
+ if (/^[a-zA-Z0-9_.:/-]+$/.test(value))
948
+ return value;
949
+ return JSON.stringify(value);
950
+ }
951
+ function yamlBlockScalar(value, indent = 2) {
952
+ const indentText = ' '.repeat(indent);
953
+ if (!value.includes('\n'))
954
+ return yamlScalar(value);
955
+ return `|\n${value.split('\n').map((line) => `${indentText}${line}`).join('\n')}`;
956
+ }
957
+ function buildGroupNode(scope, kind, label, children) {
958
+ if (children.length === 0)
959
+ return null;
960
+ return {
961
+ id: `group:${scope}:${kind}:${label.toLowerCase().replace(/[^a-z0-9]+/g, '-')}`,
962
+ label,
963
+ kind: 'group',
964
+ count: children.length,
965
+ meta: {
966
+ objectKind: kind,
967
+ },
968
+ children,
969
+ };
970
+ }
971
+ function toLeaf(kind, name, label, meta) {
972
+ return {
973
+ id: objectId(kind, name),
974
+ label,
975
+ kind,
976
+ meta,
977
+ };
978
+ }
979
+ //# sourceMappingURL=semantic-import.js.map