@duckcodeailabs/dql-cli 1.4.3 → 1.5.0

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 (283) hide show
  1. package/LICENSE +123 -0
  2. package/README.md +72 -0
  3. package/dist/apps-api.d.ts +79 -0
  4. package/dist/apps-api.d.ts.map +1 -0
  5. package/dist/apps-api.js +934 -0
  6. package/dist/apps-api.js.map +1 -0
  7. package/dist/apps-api.test.d.ts +2 -0
  8. package/dist/apps-api.test.d.ts.map +1 -0
  9. package/dist/apps-api.test.js +111 -0
  10. package/dist/apps-api.test.js.map +1 -0
  11. package/dist/args.d.ts +30 -0
  12. package/dist/args.d.ts.map +1 -0
  13. package/dist/args.js +105 -0
  14. package/dist/args.js.map +1 -0
  15. package/dist/args.test.d.ts +2 -0
  16. package/dist/args.test.d.ts.map +1 -0
  17. package/dist/args.test.js +41 -0
  18. package/dist/args.test.js.map +1 -0
  19. package/dist/assets/dql-notebook/assets/codemirror-DJYUkPr1.js +11 -0
  20. package/dist/assets/dql-notebook/assets/index-R3UrqjLQ.css +1 -0
  21. package/dist/assets/dql-notebook/assets/index-mlfOQ2me.js +857 -0
  22. package/dist/assets/dql-notebook/assets/react-CRB3T2We.js +32 -0
  23. package/dist/assets/dql-notebook/index.html +18 -0
  24. package/dist/assets/notebook-browser/app.js +548 -0
  25. package/dist/assets/notebook-browser/index.html +83 -0
  26. package/dist/assets/notebook-browser/styles.css +336 -0
  27. package/dist/block-studio-import.d.ts +58 -0
  28. package/dist/block-studio-import.d.ts.map +1 -0
  29. package/dist/block-studio-import.js +390 -0
  30. package/dist/block-studio-import.js.map +1 -0
  31. package/dist/block-studio-import.test.d.ts +2 -0
  32. package/dist/block-studio-import.test.d.ts.map +1 -0
  33. package/dist/block-studio-import.test.js +106 -0
  34. package/dist/block-studio-import.test.js.map +1 -0
  35. package/dist/block-templates.d.ts +8 -0
  36. package/dist/block-templates.d.ts.map +1 -0
  37. package/dist/block-templates.js +60 -0
  38. package/dist/block-templates.js.map +1 -0
  39. package/dist/commands/agent.d.ts +19 -0
  40. package/dist/commands/agent.d.ts.map +1 -0
  41. package/dist/commands/agent.js +258 -0
  42. package/dist/commands/agent.js.map +1 -0
  43. package/dist/commands/app.d.ts +32 -0
  44. package/dist/commands/app.d.ts.map +1 -0
  45. package/dist/commands/app.js +307 -0
  46. package/dist/commands/app.js.map +1 -0
  47. package/dist/commands/build.d.ts +3 -0
  48. package/dist/commands/build.d.ts.map +1 -0
  49. package/dist/commands/build.js +69 -0
  50. package/dist/commands/build.js.map +1 -0
  51. package/dist/commands/build.test.d.ts +2 -0
  52. package/dist/commands/build.test.d.ts.map +1 -0
  53. package/dist/commands/build.test.js +44 -0
  54. package/dist/commands/build.test.js.map +1 -0
  55. package/dist/commands/certify.d.ts +3 -0
  56. package/dist/commands/certify.d.ts.map +1 -0
  57. package/dist/commands/certify.js +228 -0
  58. package/dist/commands/certify.js.map +1 -0
  59. package/dist/commands/compile.d.ts +21 -0
  60. package/dist/commands/compile.d.ts.map +1 -0
  61. package/dist/commands/compile.js +198 -0
  62. package/dist/commands/compile.js.map +1 -0
  63. package/dist/commands/compile.test.d.ts +2 -0
  64. package/dist/commands/compile.test.d.ts.map +1 -0
  65. package/dist/commands/compile.test.js +115 -0
  66. package/dist/commands/compile.test.js.map +1 -0
  67. package/dist/commands/diff.d.ts +3 -0
  68. package/dist/commands/diff.d.ts.map +1 -0
  69. package/dist/commands/diff.js +52 -0
  70. package/dist/commands/diff.js.map +1 -0
  71. package/dist/commands/doctor.d.ts +3 -0
  72. package/dist/commands/doctor.d.ts.map +1 -0
  73. package/dist/commands/doctor.js +191 -0
  74. package/dist/commands/doctor.js.map +1 -0
  75. package/dist/commands/doctor.test.d.ts +2 -0
  76. package/dist/commands/doctor.test.d.ts.map +1 -0
  77. package/dist/commands/doctor.test.js +43 -0
  78. package/dist/commands/doctor.test.js.map +1 -0
  79. package/dist/commands/fmt.d.ts +3 -0
  80. package/dist/commands/fmt.d.ts.map +1 -0
  81. package/dist/commands/fmt.js +53 -0
  82. package/dist/commands/fmt.js.map +1 -0
  83. package/dist/commands/import.d.ts +3 -0
  84. package/dist/commands/import.d.ts.map +1 -0
  85. package/dist/commands/import.js +50 -0
  86. package/dist/commands/import.js.map +1 -0
  87. package/dist/commands/info.d.ts +3 -0
  88. package/dist/commands/info.d.ts.map +1 -0
  89. package/dist/commands/info.js +56 -0
  90. package/dist/commands/info.js.map +1 -0
  91. package/dist/commands/init.d.ts +3 -0
  92. package/dist/commands/init.d.ts.map +1 -0
  93. package/dist/commands/init.js +250 -0
  94. package/dist/commands/init.js.map +1 -0
  95. package/dist/commands/init.test.d.ts +2 -0
  96. package/dist/commands/init.test.d.ts.map +1 -0
  97. package/dist/commands/init.test.js +118 -0
  98. package/dist/commands/init.test.js.map +1 -0
  99. package/dist/commands/lineage.d.ts +24 -0
  100. package/dist/commands/lineage.d.ts.map +1 -0
  101. package/dist/commands/lineage.js +634 -0
  102. package/dist/commands/lineage.js.map +1 -0
  103. package/dist/commands/mcp.d.ts +7 -0
  104. package/dist/commands/mcp.d.ts.map +1 -0
  105. package/dist/commands/mcp.js +16 -0
  106. package/dist/commands/mcp.js.map +1 -0
  107. package/dist/commands/migrate.d.ts +12 -0
  108. package/dist/commands/migrate.d.ts.map +1 -0
  109. package/dist/commands/migrate.js +197 -0
  110. package/dist/commands/migrate.js.map +1 -0
  111. package/dist/commands/new.d.ts +3 -0
  112. package/dist/commands/new.d.ts.map +1 -0
  113. package/dist/commands/new.js +490 -0
  114. package/dist/commands/new.js.map +1 -0
  115. package/dist/commands/new.test.d.ts +2 -0
  116. package/dist/commands/new.test.d.ts.map +1 -0
  117. package/dist/commands/new.test.js +191 -0
  118. package/dist/commands/new.test.js.map +1 -0
  119. package/dist/commands/notebook.d.ts +3 -0
  120. package/dist/commands/notebook.d.ts.map +1 -0
  121. package/dist/commands/notebook.js +46 -0
  122. package/dist/commands/notebook.js.map +1 -0
  123. package/dist/commands/parse.d.ts +3 -0
  124. package/dist/commands/parse.d.ts.map +1 -0
  125. package/dist/commands/parse.js +63 -0
  126. package/dist/commands/parse.js.map +1 -0
  127. package/dist/commands/preview.d.ts +3 -0
  128. package/dist/commands/preview.d.ts.map +1 -0
  129. package/dist/commands/preview.js +42 -0
  130. package/dist/commands/preview.js.map +1 -0
  131. package/dist/commands/schedule.d.ts +3 -0
  132. package/dist/commands/schedule.d.ts.map +1 -0
  133. package/dist/commands/schedule.js +215 -0
  134. package/dist/commands/schedule.js.map +1 -0
  135. package/dist/commands/semantic.d.ts +12 -0
  136. package/dist/commands/semantic.d.ts.map +1 -0
  137. package/dist/commands/semantic.js +356 -0
  138. package/dist/commands/semantic.js.map +1 -0
  139. package/dist/commands/serve.d.ts +3 -0
  140. package/dist/commands/serve.d.ts.map +1 -0
  141. package/dist/commands/serve.js +30 -0
  142. package/dist/commands/serve.js.map +1 -0
  143. package/dist/commands/slack.d.ts +13 -0
  144. package/dist/commands/slack.d.ts.map +1 -0
  145. package/dist/commands/slack.js +53 -0
  146. package/dist/commands/slack.js.map +1 -0
  147. package/dist/commands/sync.d.ts +3 -0
  148. package/dist/commands/sync.d.ts.map +1 -0
  149. package/dist/commands/sync.js +192 -0
  150. package/dist/commands/sync.js.map +1 -0
  151. package/dist/commands/sync.test.d.ts +2 -0
  152. package/dist/commands/sync.test.d.ts.map +1 -0
  153. package/dist/commands/sync.test.js +147 -0
  154. package/dist/commands/sync.test.js.map +1 -0
  155. package/dist/commands/test.d.ts +3 -0
  156. package/dist/commands/test.d.ts.map +1 -0
  157. package/dist/commands/test.js +167 -0
  158. package/dist/commands/test.js.map +1 -0
  159. package/dist/commands/validate.d.ts +3 -0
  160. package/dist/commands/validate.d.ts.map +1 -0
  161. package/dist/commands/validate.js +163 -0
  162. package/dist/commands/validate.js.map +1 -0
  163. package/dist/commands/validate.test.d.ts +2 -0
  164. package/dist/commands/validate.test.d.ts.map +1 -0
  165. package/dist/commands/validate.test.js +55 -0
  166. package/dist/commands/validate.test.js.map +1 -0
  167. package/dist/commands/verify.d.ts +11 -0
  168. package/dist/commands/verify.d.ts.map +1 -0
  169. package/dist/commands/verify.js +74 -0
  170. package/dist/commands/verify.js.map +1 -0
  171. package/dist/digest.d.ts +10 -0
  172. package/dist/digest.d.ts.map +1 -0
  173. package/dist/digest.js +83 -0
  174. package/dist/digest.js.map +1 -0
  175. package/dist/git-service.d.ts +17 -0
  176. package/dist/git-service.d.ts.map +1 -0
  177. package/dist/git-service.js +54 -0
  178. package/dist/git-service.js.map +1 -0
  179. package/dist/governance-runtime.d.ts +15 -0
  180. package/dist/governance-runtime.d.ts.map +1 -0
  181. package/dist/governance-runtime.js +50 -0
  182. package/dist/governance-runtime.js.map +1 -0
  183. package/dist/index.d.ts +3 -0
  184. package/dist/index.d.ts.map +1 -0
  185. package/{index.js → dist/index.js} +5 -0
  186. package/dist/index.js.map +1 -0
  187. package/dist/llm/index.d.ts +4 -0
  188. package/dist/llm/index.d.ts.map +1 -0
  189. package/dist/llm/index.js +20 -0
  190. package/dist/llm/index.js.map +1 -0
  191. package/dist/llm/providers/claude-agent-sdk.d.ts +3 -0
  192. package/dist/llm/providers/claude-agent-sdk.d.ts.map +1 -0
  193. package/dist/llm/providers/claude-agent-sdk.js +174 -0
  194. package/dist/llm/providers/claude-agent-sdk.js.map +1 -0
  195. package/dist/llm/providers/claude-code.d.ts +8 -0
  196. package/dist/llm/providers/claude-code.d.ts.map +1 -0
  197. package/dist/llm/providers/claude-code.js +171 -0
  198. package/dist/llm/providers/claude-code.js.map +1 -0
  199. package/dist/llm/providers/dql-agent-provider.d.ts +5 -0
  200. package/dist/llm/providers/dql-agent-provider.d.ts.map +1 -0
  201. package/dist/llm/providers/dql-agent-provider.js +287 -0
  202. package/dist/llm/providers/dql-agent-provider.js.map +1 -0
  203. package/dist/llm/tools.d.ts +9 -0
  204. package/dist/llm/tools.d.ts.map +1 -0
  205. package/dist/llm/tools.js +112 -0
  206. package/dist/llm/tools.js.map +1 -0
  207. package/dist/llm/types.d.ts +72 -0
  208. package/dist/llm/types.d.ts.map +1 -0
  209. package/dist/llm/types.js +2 -0
  210. package/dist/llm/types.js.map +1 -0
  211. package/dist/local-runtime.d.ts +142 -0
  212. package/dist/local-runtime.d.ts.map +1 -0
  213. package/dist/local-runtime.js +4859 -0
  214. package/dist/local-runtime.js.map +1 -0
  215. package/dist/local-runtime.test.d.ts +2 -0
  216. package/dist/local-runtime.test.d.ts.map +1 -0
  217. package/dist/local-runtime.test.js +241 -0
  218. package/dist/local-runtime.test.js.map +1 -0
  219. package/dist/metricflow.d.ts +35 -0
  220. package/dist/metricflow.d.ts.map +1 -0
  221. package/dist/metricflow.js +122 -0
  222. package/dist/metricflow.js.map +1 -0
  223. package/dist/metricflow.test.d.ts +2 -0
  224. package/dist/metricflow.test.d.ts.map +1 -0
  225. package/dist/metricflow.test.js +54 -0
  226. package/dist/metricflow.test.js.map +1 -0
  227. package/dist/open-browser.d.ts +2 -0
  228. package/dist/open-browser.d.ts.map +1 -0
  229. package/dist/open-browser.js +29 -0
  230. package/dist/open-browser.js.map +1 -0
  231. package/dist/schedule/alerts.d.ts +5 -0
  232. package/dist/schedule/alerts.d.ts.map +1 -0
  233. package/dist/schedule/alerts.js +54 -0
  234. package/dist/schedule/alerts.js.map +1 -0
  235. package/dist/schedule/discovery.d.ts +4 -0
  236. package/dist/schedule/discovery.d.ts.map +1 -0
  237. package/dist/schedule/discovery.js +36 -0
  238. package/dist/schedule/discovery.js.map +1 -0
  239. package/dist/schedule/notifiers/email.d.ts +3 -0
  240. package/dist/schedule/notifiers/email.d.ts.map +1 -0
  241. package/dist/schedule/notifiers/email.js +76 -0
  242. package/dist/schedule/notifiers/email.js.map +1 -0
  243. package/dist/schedule/notifiers/file.d.ts +3 -0
  244. package/dist/schedule/notifiers/file.d.ts.map +1 -0
  245. package/dist/schedule/notifiers/file.js +50 -0
  246. package/dist/schedule/notifiers/file.js.map +1 -0
  247. package/dist/schedule/notifiers/index.d.ts +10 -0
  248. package/dist/schedule/notifiers/index.d.ts.map +1 -0
  249. package/dist/schedule/notifiers/index.js +33 -0
  250. package/dist/schedule/notifiers/index.js.map +1 -0
  251. package/dist/schedule/notifiers/slack.d.ts +3 -0
  252. package/dist/schedule/notifiers/slack.d.ts.map +1 -0
  253. package/dist/schedule/notifiers/slack.js +58 -0
  254. package/dist/schedule/notifiers/slack.js.map +1 -0
  255. package/dist/schedule/runner.d.ts +14 -0
  256. package/dist/schedule/runner.d.ts.map +1 -0
  257. package/dist/schedule/runner.js +225 -0
  258. package/dist/schedule/runner.js.map +1 -0
  259. package/dist/schedule/runs.d.ts +5 -0
  260. package/dist/schedule/runs.d.ts.map +1 -0
  261. package/dist/schedule/runs.js +41 -0
  262. package/dist/schedule/runs.js.map +1 -0
  263. package/dist/schedule/service.d.ts +13 -0
  264. package/dist/schedule/service.d.ts.map +1 -0
  265. package/dist/schedule/service.js +87 -0
  266. package/dist/schedule/service.js.map +1 -0
  267. package/dist/schedule/types.d.ts +70 -0
  268. package/dist/schedule/types.d.ts.map +1 -0
  269. package/dist/schedule/types.js +2 -0
  270. package/dist/schedule/types.js.map +1 -0
  271. package/dist/semantic-import.d.ts +135 -0
  272. package/dist/semantic-import.d.ts.map +1 -0
  273. package/dist/semantic-import.js +979 -0
  274. package/dist/semantic-import.js.map +1 -0
  275. package/dist/semantic-import.test.d.ts +2 -0
  276. package/dist/semantic-import.test.d.ts.map +1 -0
  277. package/dist/semantic-import.test.js +95 -0
  278. package/dist/semantic-import.test.js.map +1 -0
  279. package/dist/settings/provider-settings.d.ts +33 -0
  280. package/dist/settings/provider-settings.d.ts.map +1 -0
  281. package/dist/settings/provider-settings.js +91 -0
  282. package/dist/settings/provider-settings.js.map +1 -0
  283. package/package.json +29 -21
@@ -0,0 +1,548 @@
1
+ const state = {
2
+ bootstrap: null,
3
+ notebook: null,
4
+ results: new Map(),
5
+ connectionForms: [],
6
+ draftConnection: null,
7
+ activeConnection: null,
8
+ };
9
+
10
+ const elements = {
11
+ projectName: document.getElementById('project-name'),
12
+ notebookTitle: document.getElementById('notebook-title'),
13
+ fileList: document.getElementById('file-list'),
14
+ cells: document.getElementById('cells'),
15
+ template: document.getElementById('cell-template'),
16
+ driverSelect: document.getElementById('driver-select'),
17
+ connectionFields: document.getElementById('connection-fields'),
18
+ connectionSummary: document.getElementById('connection-summary'),
19
+ connectionStatus: document.getElementById('connection-status'),
20
+ runAll: document.getElementById('run-all'),
21
+ exportNotebook: document.getElementById('export-notebook'),
22
+ saveConnection: document.getElementById('save-connection'),
23
+ testConnection: document.getElementById('test-connection'),
24
+ };
25
+
26
+ await bootstrap();
27
+
28
+ async function bootstrap() {
29
+ const response = await fetch('/api/notebook/bootstrap');
30
+ const payload = await response.json();
31
+ state.bootstrap = payload;
32
+ state.notebook = payload.notebook;
33
+ state.connectionForms = payload.connectorForms;
34
+ state.activeConnection = loadStoredConnection() || payload.defaultConnection;
35
+ state.draftConnection = { ...state.activeConnection };
36
+
37
+ elements.projectName.textContent = payload.project;
38
+ elements.notebookTitle.textContent = payload.notebook.metadata.title;
39
+
40
+ renderFiles(payload.files);
41
+ renderConnectionForm();
42
+ renderConnectionSummary();
43
+ renderCells();
44
+
45
+ document.querySelectorAll('[data-add]').forEach((button) => {
46
+ button.addEventListener('click', () => addCell(button.getAttribute('data-add')));
47
+ });
48
+
49
+ elements.runAll.addEventListener('click', runAllCells);
50
+ elements.exportNotebook.addEventListener('click', exportNotebook);
51
+ elements.driverSelect.addEventListener('change', onDriverChange);
52
+ elements.saveConnection.addEventListener('click', saveDraftConnection);
53
+ elements.testConnection.addEventListener('click', testDraftConnection);
54
+ }
55
+
56
+ function renderFiles(files) {
57
+ elements.fileList.innerHTML = '';
58
+ files.forEach((file) => {
59
+ const li = document.createElement('li');
60
+ const link = document.createElement('a');
61
+ link.href = `/api/notebook/file?path=${encodeURIComponent(file)}`;
62
+ link.target = '_blank';
63
+ link.rel = 'noreferrer';
64
+ link.textContent = file;
65
+ link.title = `Open ${file}`;
66
+ li.appendChild(link);
67
+ elements.fileList.appendChild(li);
68
+ });
69
+ }
70
+
71
+ function renderConnectionSummary() {
72
+ const connection = state.activeConnection || {};
73
+ elements.connectionSummary.innerHTML = `
74
+ <strong>${connection.driver || 'file'}</strong>
75
+ <div class="field-help">${connection.host || connection.filepath || connection.database || ':memory:'}</div>
76
+ `;
77
+ }
78
+
79
+ function renderConnectionForm() {
80
+ elements.driverSelect.innerHTML = '';
81
+ state.connectionForms.forEach((schema) => {
82
+ const option = document.createElement('option');
83
+ option.value = schema.driver;
84
+ option.textContent = schema.label;
85
+ if (schema.driver === state.draftConnection?.driver) {
86
+ option.selected = true;
87
+ }
88
+ elements.driverSelect.appendChild(option);
89
+ });
90
+
91
+ const schema = currentSchema();
92
+ if (!schema) return;
93
+
94
+ elements.connectionFields.innerHTML = '';
95
+ schema.fields.forEach((field) => {
96
+ const wrapper = document.createElement('div');
97
+ wrapper.className = 'field';
98
+
99
+ if (field.type === 'checkbox') {
100
+ wrapper.innerHTML = `
101
+ <label class="checkbox">
102
+ <input type="checkbox" data-connection-field="${field.key}" ${state.draftConnection?.[field.key] ? 'checked' : ''} />
103
+ <span>${field.label}</span>
104
+ </label>
105
+ `;
106
+ } else {
107
+ wrapper.innerHTML = `
108
+ <label class="field-label">${field.label}</label>
109
+ <input
110
+ type="${field.type}"
111
+ data-connection-field="${field.key}"
112
+ value="${escapeAttribute(state.draftConnection?.[field.key] ?? '')}"
113
+ placeholder="${escapeAttribute(field.placeholder || '')}"
114
+ />
115
+ `;
116
+ }
117
+
118
+ elements.connectionFields.appendChild(wrapper);
119
+ });
120
+
121
+ elements.connectionFields.querySelectorAll('[data-connection-field]').forEach((input) => {
122
+ input.addEventListener('input', collectConnectionDraft);
123
+ input.addEventListener('change', collectConnectionDraft);
124
+ });
125
+ }
126
+
127
+ function currentSchema() {
128
+ return state.connectionForms.find((schema) => schema.driver === (state.draftConnection?.driver || state.activeConnection?.driver));
129
+ }
130
+
131
+ function onDriverChange(event) {
132
+ state.draftConnection = { driver: event.target.value };
133
+ renderConnectionForm();
134
+ }
135
+
136
+ function collectConnectionDraft() {
137
+ const driver = elements.driverSelect.value;
138
+ const draft = { driver };
139
+
140
+ elements.connectionFields.querySelectorAll('[data-connection-field]').forEach((input) => {
141
+ const key = input.getAttribute('data-connection-field');
142
+ if (input.type === 'checkbox') {
143
+ draft[key] = input.checked;
144
+ } else if (input.type === 'number') {
145
+ draft[key] = input.value ? Number(input.value) : undefined;
146
+ } else {
147
+ draft[key] = input.value || undefined;
148
+ }
149
+ });
150
+
151
+ state.draftConnection = draft;
152
+ }
153
+
154
+ function saveDraftConnection() {
155
+ collectConnectionDraft();
156
+ state.activeConnection = { ...state.draftConnection };
157
+ localStorage.setItem(connectionStorageKey(), JSON.stringify(state.activeConnection));
158
+ renderConnectionSummary();
159
+ setConnectionStatus('Saved local notebook connection.', 'ok');
160
+ }
161
+
162
+ async function testDraftConnection() {
163
+ collectConnectionDraft();
164
+ setConnectionStatus('Testing connection…');
165
+ try {
166
+ const response = await fetch('/api/test-connection', {
167
+ method: 'POST',
168
+ headers: { 'Content-Type': 'application/json' },
169
+ body: JSON.stringify({ connection: state.draftConnection }),
170
+ });
171
+ const payload = await response.json();
172
+ if (!response.ok || !payload.ok) {
173
+ throw new Error(payload.error || 'Connection test failed.');
174
+ }
175
+ setConnectionStatus('Connection test passed.', 'ok');
176
+ } catch (error) {
177
+ setConnectionStatus(error.message, 'error');
178
+ }
179
+ }
180
+
181
+ function setConnectionStatus(message, kind = '') {
182
+ elements.connectionStatus.textContent = message;
183
+ elements.connectionStatus.className = `status ${kind}`.trim();
184
+ }
185
+
186
+ function renderCells() {
187
+ elements.cells.innerHTML = '';
188
+ state.notebook.cells.forEach((cell, index) => {
189
+ const fragment = elements.template.content.cloneNode(true);
190
+ const root = fragment.querySelector('.cell');
191
+ const type = fragment.querySelector('.cell-type');
192
+ const title = fragment.querySelector('.cell-title');
193
+ const source = fragment.querySelector('.cell-source');
194
+ const output = fragment.querySelector('.cell-output');
195
+ const status = fragment.querySelector('.cell-status');
196
+ const markdownPreview = fragment.querySelector('.markdown-preview');
197
+ const chartEditor = fragment.querySelector('.chart-editor');
198
+
199
+ root.dataset.cellId = cell.id;
200
+ type.textContent = cell.type;
201
+ title.value = cell.title || '';
202
+ source.value = cell.source || '';
203
+
204
+ title.addEventListener('input', () => {
205
+ cell.title = title.value;
206
+ if (index === 0) {
207
+ state.notebook.metadata.title = title.value || state.notebook.metadata.title;
208
+ }
209
+ });
210
+
211
+ if (cell.type === 'markdown') {
212
+ markdownPreview.classList.remove('hidden');
213
+ markdownPreview.innerHTML = renderMarkdown(cell.source);
214
+ source.addEventListener('input', () => {
215
+ cell.source = source.value;
216
+ markdownPreview.innerHTML = renderMarkdown(cell.source);
217
+ });
218
+ } else if (cell.type === 'chart') {
219
+ source.classList.add('hidden');
220
+ chartEditor.classList.remove('hidden');
221
+ renderChartEditor(chartEditor, cell);
222
+ } else {
223
+ source.addEventListener('input', () => {
224
+ cell.source = source.value;
225
+ });
226
+ }
227
+
228
+ const cached = state.results.get(cell.id);
229
+ if (cached) {
230
+ renderExecutionOutput(output, status, cell, cached);
231
+ }
232
+
233
+ fragment.querySelectorAll('[data-action]').forEach((button) => {
234
+ button.addEventListener('click', () => handleCellAction(cell.id, button.dataset.action));
235
+ });
236
+
237
+ elements.cells.appendChild(fragment);
238
+ });
239
+ }
240
+
241
+ function renderChartEditor(container, cell) {
242
+ const config = cell.config || {};
243
+ const sqlLikeCells = state.notebook.cells.filter((candidate) => candidate.id !== cell.id && candidate.type !== 'markdown' && candidate.type !== 'chart');
244
+ container.innerHTML = `
245
+ <div>
246
+ <label class="field-label">Source cell</label>
247
+ <select data-chart-field="sourceCellId">
248
+ ${sqlLikeCells.map((candidate) => `<option value="${candidate.id}" ${candidate.id === config.sourceCellId ? 'selected' : ''}>${escapeHtml(candidate.title || candidate.id)}</option>`).join('')}
249
+ </select>
250
+ </div>
251
+ <div>
252
+ <label class="field-label">Chart</label>
253
+ <select data-chart-field="chart">
254
+ ${['bar', 'line', 'table', 'kpi'].map((chart) => `<option value="${chart}" ${chart === (config.chart || 'bar') ? 'selected' : ''}>${chart}</option>`).join('')}
255
+ </select>
256
+ </div>
257
+ <div>
258
+ <label class="field-label">X field</label>
259
+ <input data-chart-field="x" value="${escapeAttribute(config.x || '')}" />
260
+ </div>
261
+ <div>
262
+ <label class="field-label">Y field</label>
263
+ <input data-chart-field="y" value="${escapeAttribute(config.y || '')}" />
264
+ </div>
265
+ <div>
266
+ <label class="field-label">Title</label>
267
+ <input data-chart-field="title" value="${escapeAttribute(config.title || '')}" />
268
+ </div>
269
+ `;
270
+
271
+ container.querySelectorAll('[data-chart-field]').forEach((input) => {
272
+ input.addEventListener('input', () => {
273
+ cell.config = cell.config || {};
274
+ cell.config[input.dataset.chartField] = input.value;
275
+ renderLinkedChartCell(cell.id);
276
+ });
277
+ input.addEventListener('change', () => {
278
+ cell.config = cell.config || {};
279
+ cell.config[input.dataset.chartField] = input.value;
280
+ renderLinkedChartCell(cell.id);
281
+ });
282
+ });
283
+ }
284
+
285
+ function handleCellAction(cellId, action) {
286
+ const index = state.notebook.cells.findIndex((cell) => cell.id === cellId);
287
+ if (index === -1) return;
288
+
289
+ if (action === 'delete') {
290
+ state.notebook.cells.splice(index, 1);
291
+ } else if (action === 'up' && index > 0) {
292
+ swapCells(index, index - 1);
293
+ } else if (action === 'down' && index < state.notebook.cells.length - 1) {
294
+ swapCells(index, index + 1);
295
+ } else if (action === 'run') {
296
+ runCell(state.notebook.cells[index]);
297
+ return;
298
+ }
299
+
300
+ renderCells();
301
+ }
302
+
303
+ function swapCells(a, b) {
304
+ const temp = state.notebook.cells[a];
305
+ state.notebook.cells[a] = state.notebook.cells[b];
306
+ state.notebook.cells[b] = temp;
307
+ }
308
+
309
+ function addCell(type) {
310
+ const nextIndex = state.notebook.cells.length + 1;
311
+ state.notebook.cells.push({
312
+ id: `cell-${nextIndex}`,
313
+ type,
314
+ title: `${type.toUpperCase()} Cell`,
315
+ source: type === 'markdown' ? '## New note' : type === 'sql' ? 'SELECT 1 AS value' : type === 'dql' ? 'block "New Block" {\n domain = "general"\n type = "custom"\n query = """SELECT 1 AS value"""\n}' : '',
316
+ config: type === 'chart' ? { chart: 'bar' } : undefined,
317
+ });
318
+ renderCells();
319
+ }
320
+
321
+ async function runAllCells() {
322
+ for (const cell of state.notebook.cells) {
323
+ if (cell.type === 'markdown') continue;
324
+ await runCell(cell);
325
+ }
326
+ }
327
+
328
+ async function runCell(cell) {
329
+ if (cell.type === 'chart') {
330
+ renderLinkedChartCell(cell.id);
331
+ return;
332
+ }
333
+
334
+ const card = document.querySelector(`[data-cell-id="${cell.id}"]`);
335
+ const status = card.querySelector('.cell-status');
336
+ const output = card.querySelector('.cell-output');
337
+ status.textContent = 'Running…';
338
+ try {
339
+ const response = await fetch('/api/notebook/execute', {
340
+ method: 'POST',
341
+ headers: { 'Content-Type': 'application/json' },
342
+ body: JSON.stringify({ cell, connection: state.activeConnection }),
343
+ });
344
+ const payload = await response.json();
345
+ if (!response.ok || payload.error) {
346
+ throw new Error(payload.error || 'Notebook execution failed.');
347
+ }
348
+
349
+ state.results.set(cell.id, payload);
350
+ renderExecutionOutput(output, status, cell, payload);
351
+ rerenderDependentCharts(cell.id);
352
+ } catch (error) {
353
+ status.textContent = error.message;
354
+ status.className = 'cell-status status error';
355
+ }
356
+ }
357
+
358
+ function renderExecutionOutput(container, status, cell, payload) {
359
+ status.textContent = payload.result ? `${payload.result.rowCount} row${payload.result.rowCount === 1 ? '' : 's'}` : 'Ready';
360
+ status.className = 'cell-status status ok';
361
+ container.innerHTML = '';
362
+
363
+ if (!payload.result) {
364
+ return;
365
+ }
366
+
367
+ const chartConfig = payload.chartConfig || cell.config || { chart: 'table' };
368
+ if (cell.type === 'dql' && chartConfig.chart && chartConfig.chart !== 'table') {
369
+ container.appendChild(renderChart(payload.result.rows, chartConfig, payload.title));
370
+ }
371
+
372
+ container.appendChild(renderTable(payload.result.rows));
373
+ }
374
+
375
+ function rerenderDependentCharts(sourceCellId) {
376
+ state.notebook.cells
377
+ .filter((cell) => cell.type === 'chart' && cell.config?.sourceCellId === sourceCellId)
378
+ .forEach((cell) => renderLinkedChartCell(cell.id));
379
+ }
380
+
381
+ function renderLinkedChartCell(cellId) {
382
+ const cell = state.notebook.cells.find((candidate) => candidate.id === cellId);
383
+ if (!cell) return;
384
+ const card = document.querySelector(`[data-cell-id="${cell.id}"]`);
385
+ const status = card.querySelector('.cell-status');
386
+ const output = card.querySelector('.cell-output');
387
+ output.innerHTML = '';
388
+
389
+ const sourceResult = state.results.get(cell.config?.sourceCellId || '');
390
+ if (!sourceResult?.result) {
391
+ status.textContent = 'Run the source SQL/DQL cell first.';
392
+ status.className = 'cell-status status';
393
+ return;
394
+ }
395
+
396
+ status.textContent = `Linked to ${cell.config.sourceCellId}`;
397
+ status.className = 'cell-status status ok';
398
+ output.appendChild(renderChart(sourceResult.result.rows, cell.config || {}, cell.title || 'Chart'));
399
+ output.appendChild(renderTable(sourceResult.result.rows));
400
+ }
401
+
402
+ function renderChart(rows, config, title) {
403
+ const chart = (config.chart || 'table').toLowerCase();
404
+ const shell = document.createElement('div');
405
+ shell.className = 'chart-card';
406
+ shell.innerHTML = `<strong>${escapeHtml(config.title || title || 'Chart')}</strong>`;
407
+
408
+ if (!rows.length) {
409
+ shell.innerHTML += '<p class="field-help">No rows returned.</p>';
410
+ return shell;
411
+ }
412
+
413
+ if (chart === 'kpi' || chart === 'metric') {
414
+ const yField = config.y || Object.keys(rows[0])[0];
415
+ const value = rows[0][yField];
416
+ shell.innerHTML += `<div class="kpi-value">${escapeHtml(String(value ?? '—'))}</div>`;
417
+ return shell;
418
+ }
419
+
420
+ if (chart === 'line') {
421
+ const xField = config.x || Object.keys(rows[0])[0];
422
+ const yField = config.y || Object.keys(rows[0])[1] || xField;
423
+ shell.appendChild(renderLineChart(rows, xField, yField));
424
+ return shell;
425
+ }
426
+
427
+ if (chart === 'table') {
428
+ shell.appendChild(renderTable(rows));
429
+ return shell;
430
+ }
431
+
432
+ const xField = config.x || Object.keys(rows[0])[0];
433
+ const yField = config.y || Object.keys(rows[0])[1] || xField;
434
+ const max = Math.max(...rows.map((row) => Number(row[yField]) || 0), 1);
435
+ rows.slice(0, 12).forEach((row) => {
436
+ const barRow = document.createElement('div');
437
+ barRow.className = 'bar-row';
438
+ const value = Number(row[yField]) || 0;
439
+ barRow.innerHTML = `
440
+ <span>${escapeHtml(String(row[xField] ?? ''))}</span>
441
+ <div class="bar-track"><div class="bar-fill" style="width:${(value / max) * 100}%"></div></div>
442
+ <strong>${escapeHtml(String(value))}</strong>
443
+ `;
444
+ shell.appendChild(barRow);
445
+ });
446
+ return shell;
447
+ }
448
+
449
+ function renderLineChart(rows, xField, yField) {
450
+ const wrapper = document.createElement('div');
451
+ const width = 640;
452
+ const height = 220;
453
+ const maxY = Math.max(...rows.map((row) => Number(row[yField]) || 0), 1);
454
+ const step = rows.length > 1 ? width / (rows.length - 1) : width;
455
+ const points = rows.map((row, index) => {
456
+ const x = index * step;
457
+ const y = height - ((Number(row[yField]) || 0) / maxY) * (height - 24) - 12;
458
+ return `${x},${y}`;
459
+ }).join(' ');
460
+ wrapper.innerHTML = `
461
+ <svg viewBox="0 0 ${width} ${height}" width="100%" height="220" role="img" aria-label="${escapeAttribute(yField)} over ${escapeAttribute(xField)}">
462
+ <rect x="0" y="0" width="${width}" height="${height}" fill="transparent"></rect>
463
+ <polyline fill="none" stroke="#59c2ff" stroke-width="3" points="${points}"></polyline>
464
+ ${rows.map((row, index) => {
465
+ const x = index * step;
466
+ const y = height - ((Number(row[yField]) || 0) / maxY) * (height - 24) - 12;
467
+ return `<circle cx="${x}" cy="${y}" r="4" fill="#8b5cf6"></circle>`;
468
+ }).join('')}
469
+ </svg>
470
+ `;
471
+ return wrapper;
472
+ }
473
+
474
+ function renderTable(rows) {
475
+ if (!rows.length) {
476
+ const empty = document.createElement('div');
477
+ empty.className = 'panel small';
478
+ empty.textContent = 'No rows returned.';
479
+ return empty;
480
+ }
481
+
482
+ const columns = Object.keys(rows[0]);
483
+ const wrapper = document.createElement('div');
484
+ wrapper.className = 'table-shell';
485
+ wrapper.innerHTML = `
486
+ <table>
487
+ <thead>
488
+ <tr>${columns.map((column) => `<th>${escapeHtml(column)}</th>`).join('')}</tr>
489
+ </thead>
490
+ <tbody>
491
+ ${rows.slice(0, 20).map((row) => `<tr>${columns.map((column) => `<td>${escapeHtml(String(row[column] ?? ''))}</td>`).join('')}</tr>`).join('')}
492
+ </tbody>
493
+ </table>
494
+ `;
495
+ return wrapper;
496
+ }
497
+
498
+ function exportNotebook() {
499
+ const blob = new Blob([JSON.stringify(state.notebook, null, 2)], { type: 'application/json' });
500
+ const url = URL.createObjectURL(blob);
501
+ const anchor = document.createElement('a');
502
+ anchor.href = url;
503
+ anchor.download = `${slugify(state.notebook.metadata.title || 'notebook')}.dqlnb`;
504
+ anchor.click();
505
+ URL.revokeObjectURL(url);
506
+ }
507
+
508
+ function renderMarkdown(markdown) {
509
+ return escapeHtml(markdown)
510
+ .replace(/^###\s+(.*)$/gm, '<h3>$1</h3>')
511
+ .replace(/^##\s+(.*)$/gm, '<h2>$1</h2>')
512
+ .replace(/^#\s+(.*)$/gm, '<h1>$1</h1>')
513
+ .replace(/^[-*]\s+(.*)$/gm, '<li>$1</li>')
514
+ .replace(/(?:\n|^)(<li>.*<\/li>)(?:\n|$)/gs, (_match, list) => `\n<ul>${list}</ul>\n`)
515
+ .replace(/`([^`]+)`/g, '<code>$1</code>')
516
+ .replace(/\n\n/g, '</p><p>');
517
+ }
518
+
519
+ function loadStoredConnection() {
520
+ try {
521
+ const raw = localStorage.getItem(connectionStorageKey());
522
+ return raw ? JSON.parse(raw) : null;
523
+ } catch {
524
+ return null;
525
+ }
526
+ }
527
+
528
+ function connectionStorageKey() {
529
+ const projectRoot = state.bootstrap?.projectRoot || state.bootstrap?.project || 'default';
530
+ return `dql-notebook-connection:${projectRoot}`;
531
+ }
532
+
533
+ function slugify(value) {
534
+ return String(value || 'notebook').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
535
+ }
536
+
537
+ function escapeHtml(value) {
538
+ return String(value)
539
+ .replaceAll('&', '&amp;')
540
+ .replaceAll('<', '&lt;')
541
+ .replaceAll('>', '&gt;')
542
+ .replaceAll('"', '&quot;')
543
+ .replaceAll("'", '&#39;');
544
+ }
545
+
546
+ function escapeAttribute(value) {
547
+ return escapeHtml(value).replaceAll('`', '&#96;');
548
+ }
@@ -0,0 +1,83 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>DQL Notebook</title>
7
+ <link rel="stylesheet" href="./styles.css" />
8
+ </head>
9
+ <body>
10
+ <div id="app" class="app-shell">
11
+ <aside class="sidebar">
12
+ <div class="brand">
13
+ <div class="brand-mark">DQL</div>
14
+ <div>
15
+ <strong>Notebook</strong>
16
+ <p id="project-name">Loading project…</p>
17
+ </div>
18
+ </div>
19
+ <section>
20
+ <div class="section-title">Files</div>
21
+ <ul id="file-list" class="file-list"></ul>
22
+ </section>
23
+ <section>
24
+ <div class="section-title">Connections</div>
25
+ <div id="connection-summary" class="panel small"></div>
26
+ <label class="field-label" for="driver-select">Add connection</label>
27
+ <select id="driver-select"></select>
28
+ <div id="connection-fields"></div>
29
+ <div class="row-actions">
30
+ <button id="save-connection" class="secondary">Save draft</button>
31
+ <button id="test-connection" class="secondary">Test</button>
32
+ </div>
33
+ <div id="connection-status" class="status"></div>
34
+ </section>
35
+ </aside>
36
+
37
+ <main class="main">
38
+ <header class="toolbar">
39
+ <div>
40
+ <h1 id="notebook-title">Notebook</h1>
41
+ <p class="subtitle">DQL cells, SQL cells, markdown, and linked charts.</p>
42
+ </div>
43
+ <div class="toolbar-actions">
44
+ <button data-add="dql">Add DQL</button>
45
+ <button data-add="sql">Add SQL</button>
46
+ <button data-add="markdown">Add Markdown</button>
47
+ <button data-add="chart">Add Chart</button>
48
+ <button id="run-all" class="primary">Run all</button>
49
+ <button id="export-notebook" class="primary">Export .dqlnb</button>
50
+ </div>
51
+ </header>
52
+
53
+ <section id="cells" class="cells"></section>
54
+ </main>
55
+ </div>
56
+
57
+ <template id="cell-template">
58
+ <article class="cell">
59
+ <div class="cell-header">
60
+ <div>
61
+ <span class="cell-type"></span>
62
+ <input class="cell-title" type="text" />
63
+ </div>
64
+ <div class="cell-actions">
65
+ <button data-action="up">↑</button>
66
+ <button data-action="down">↓</button>
67
+ <button data-action="run">Run</button>
68
+ <button data-action="delete">Delete</button>
69
+ </div>
70
+ </div>
71
+ <div class="cell-body">
72
+ <textarea class="cell-source" spellcheck="false"></textarea>
73
+ <div class="chart-editor hidden"></div>
74
+ <div class="markdown-preview hidden"></div>
75
+ <div class="cell-status"></div>
76
+ <div class="cell-output"></div>
77
+ </div>
78
+ </article>
79
+ </template>
80
+
81
+ <script type="module" src="./app.js"></script>
82
+ </body>
83
+ </html>