admindb 2.1.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/README.md +41 -12
  2. package/dist/app.d.ts +1 -69
  3. package/dist/app.js +3 -30
  4. package/dist/auth/config.d.ts +1 -19
  5. package/dist/auth/config.js +0 -26
  6. package/dist/auth/crypto.d.ts +1 -27
  7. package/dist/auth/crypto.js +1 -26
  8. package/dist/auth/index.d.ts +1 -5
  9. package/dist/auth/index.js +0 -1
  10. package/dist/auth/middleware.d.ts +1 -14
  11. package/dist/auth/middleware.js +0 -15
  12. package/dist/auth/routes.d.ts +1 -7
  13. package/dist/auth/routes.js +0 -8
  14. package/dist/auth/types.d.ts +1 -35
  15. package/dist/auth/types.js +1 -5
  16. package/dist/cli/args.d.ts +1 -45
  17. package/dist/cli/args.js +3 -11
  18. package/dist/cli/config.d.ts +1 -56
  19. package/dist/cli/config.js +0 -5
  20. package/dist/cli/index.d.ts +1 -4
  21. package/dist/cli/index.js +0 -1
  22. package/dist/cli/runner.d.ts +1 -1
  23. package/dist/cli/runner.js +6 -17
  24. package/dist/cli.d.ts +1 -2
  25. package/dist/cli.js +0 -1
  26. package/dist/data/chain.d.ts +1 -0
  27. package/dist/data/chain.js +707 -0
  28. package/dist/data/datasets.d.ts +1 -17
  29. package/dist/data/datasets.js +0 -4
  30. package/dist/data/detector.d.ts +1 -30
  31. package/dist/data/detector.js +53 -14
  32. package/dist/data/engine.d.ts +1 -10
  33. package/dist/data/engine.js +82 -48
  34. package/dist/data/index.d.ts +1 -5
  35. package/dist/data/index.js +1 -1
  36. package/dist/data/strategies.d.ts +1 -30
  37. package/dist/data/strategies.js +4 -8
  38. package/dist/data/types.d.ts +1 -73
  39. package/dist/data/types.js +0 -3
  40. package/dist/db/database.d.ts +1 -119
  41. package/dist/db/database.js +20 -14
  42. package/dist/db/export.d.ts +1 -12
  43. package/dist/db/export.js +54 -6
  44. package/dist/db/filters.d.ts +1 -22
  45. package/dist/db/filters.js +0 -16
  46. package/dist/db/index.d.ts +1 -8
  47. package/dist/db/index.js +0 -1
  48. package/dist/db/introspection.d.ts +1 -6
  49. package/dist/db/introspection.js +0 -5
  50. package/dist/db/manager.d.ts +1 -81
  51. package/dist/db/manager.js +13 -59
  52. package/dist/db/migrations.d.ts +1 -5
  53. package/dist/db/migrations.js +2 -5
  54. package/dist/db/postgres.d.ts +1 -85
  55. package/dist/db/postgres.js +21 -31
  56. package/dist/db/types.d.ts +1 -206
  57. package/dist/db/types.js +0 -1
  58. package/dist/index.d.ts +1 -23
  59. package/dist/index.js +0 -14
  60. package/dist/public/css/app.css +1 -1
  61. package/dist/public/js/api.js +1 -47
  62. package/dist/public/js/app.js +1 -262
  63. package/dist/public/js/browse.js +1 -1371
  64. package/dist/public/js/common.js +1 -0
  65. package/dist/public/js/databases.js +1 -235
  66. package/dist/public/js/designer.js +1 -258
  67. package/dist/public/js/forms.js +1 -826
  68. package/dist/public/js/home.js +1 -0
  69. package/dist/public/js/inspector.js +1 -638
  70. package/dist/public/js/query.js +1 -322
  71. package/dist/public/js/schema.js +1 -447
  72. package/dist/public/js/seed.js +1 -1090
  73. package/dist/routes/api/erd.d.ts +1 -0
  74. package/dist/routes/api/erd.js +50 -0
  75. package/dist/routes/api/helpers.d.ts +1 -42
  76. package/dist/routes/api/helpers.js +33 -3
  77. package/dist/routes/api/import-export.d.ts +1 -3
  78. package/dist/routes/api/import-export.js +0 -2
  79. package/dist/routes/api/index.d.ts +1 -4
  80. package/dist/routes/api/index.js +15 -4
  81. package/dist/routes/api/query.d.ts +1 -3
  82. package/dist/routes/api/query.js +13 -11
  83. package/dist/routes/api/rows.d.ts +1 -3
  84. package/dist/routes/api/rows.js +0 -11
  85. package/dist/routes/api/seed.d.ts +1 -3
  86. package/dist/routes/api/seed.js +79 -8
  87. package/dist/routes/api/tables.d.ts +1 -3
  88. package/dist/routes/api/tables.js +77 -6
  89. package/dist/routes/databases.d.ts +1 -26
  90. package/dist/routes/databases.js +1 -3
  91. package/dist/routes/index.d.ts +1 -3
  92. package/dist/routes/index.js +0 -1
  93. package/dist/routes/pages.d.ts +1 -9
  94. package/dist/routes/pages.js +18 -11
  95. package/dist/serverless.d.ts +1 -40
  96. package/dist/serverless.js +3 -39
  97. package/dist/sql/classifier.d.ts +1 -19
  98. package/dist/sql/classifier.js +0 -13
  99. package/dist/sql/error-analyzer.d.ts +1 -0
  100. package/dist/sql/error-analyzer.js +550 -0
  101. package/dist/sql/generator.d.ts +1 -81
  102. package/dist/sql/generator.js +0 -29
  103. package/dist/sql/index.d.ts +1 -2
  104. package/dist/sql/index.js +0 -1
  105. package/dist/types/api.d.ts +1 -412
  106. package/dist/types/api.js +0 -7
  107. package/dist/utils/colors.d.ts +1 -40
  108. package/dist/utils/colors.js +0 -10
  109. package/dist/utils/common.d.ts +1 -50
  110. package/dist/utils/common.js +19 -23
  111. package/dist/utils/csv.d.ts +1 -15
  112. package/dist/utils/csv.js +0 -13
  113. package/dist/utils/datatype.d.ts +1 -49
  114. package/dist/utils/datatype.js +0 -41
  115. package/dist/utils/icons.d.ts +1 -11
  116. package/dist/utils/icons.js +25 -16
  117. package/dist/utils/index.d.ts +1 -4
  118. package/dist/utils/index.js +0 -1
  119. package/dist/utils/logger.d.ts +1 -10
  120. package/dist/utils/logger.js +0 -4
  121. package/dist/views/layouts/main.hbs +14 -5
  122. package/dist/views/pages/databases.hbs +0 -18
  123. package/dist/views/pages/designer.hbs +0 -10
  124. package/dist/views/pages/erd.hbs +1241 -0
  125. package/dist/views/pages/error.hbs +0 -2
  126. package/dist/views/pages/form.hbs +0 -5
  127. package/dist/views/pages/home.hbs +98 -10
  128. package/dist/views/pages/info.hbs +107 -0
  129. package/dist/views/pages/login.hbs +0 -13
  130. package/dist/views/pages/query.hbs +54 -21
  131. package/dist/views/pages/schema.hbs +0 -20
  132. package/dist/views/pages/seed.hbs +435 -202
  133. package/dist/views/pages/table.hbs +0 -14
  134. package/dist/views/partials/icon.hbs +0 -1
  135. package/dist/views/partials/navbar.hbs +0 -6
  136. package/dist/views/partials/sidebar.hbs +12 -8
  137. package/docs/API.md +6 -1
  138. package/docs/EXAMPLES.md +3 -0
  139. package/package.json +7 -5
  140. package/dist/app.js.map +0 -1
  141. package/dist/auth/config.js.map +0 -1
  142. package/dist/auth/crypto.js.map +0 -1
  143. package/dist/auth/index.js.map +0 -1
  144. package/dist/auth/middleware.js.map +0 -1
  145. package/dist/auth/routes.js.map +0 -1
  146. package/dist/auth/types.js.map +0 -1
  147. package/dist/cli/args.js.map +0 -1
  148. package/dist/cli/config.js.map +0 -1
  149. package/dist/cli/index.js.map +0 -1
  150. package/dist/cli/runner.js.map +0 -1
  151. package/dist/cli.js.map +0 -1
  152. package/dist/data/datasets.js.map +0 -1
  153. package/dist/data/detector.js.map +0 -1
  154. package/dist/data/engine.js.map +0 -1
  155. package/dist/data/index.js.map +0 -1
  156. package/dist/data/strategies.js.map +0 -1
  157. package/dist/data/types.js.map +0 -1
  158. package/dist/db/database.js.map +0 -1
  159. package/dist/db/export.js.map +0 -1
  160. package/dist/db/filters.js.map +0 -1
  161. package/dist/db/index.js.map +0 -1
  162. package/dist/db/introspection.js.map +0 -1
  163. package/dist/db/manager.js.map +0 -1
  164. package/dist/db/migrations.js.map +0 -1
  165. package/dist/db/postgres.js.map +0 -1
  166. package/dist/db/types.js.map +0 -1
  167. package/dist/index.js.map +0 -1
  168. package/dist/public/css/input.css +0 -302
  169. package/dist/public/js/icons.js +0 -51
  170. package/dist/routes/api/helpers.js.map +0 -1
  171. package/dist/routes/api/import-export.js.map +0 -1
  172. package/dist/routes/api/index.js.map +0 -1
  173. package/dist/routes/api/query.js.map +0 -1
  174. package/dist/routes/api/rows.js.map +0 -1
  175. package/dist/routes/api/seed.js.map +0 -1
  176. package/dist/routes/api/tables.js.map +0 -1
  177. package/dist/routes/databases.js.map +0 -1
  178. package/dist/routes/index.js.map +0 -1
  179. package/dist/routes/pages.js.map +0 -1
  180. package/dist/serverless.js.map +0 -1
  181. package/dist/sql/classifier.js.map +0 -1
  182. package/dist/sql/generator.js.map +0 -1
  183. package/dist/sql/index.js.map +0 -1
  184. package/dist/types/api.js.map +0 -1
  185. package/dist/utils/colors.js.map +0 -1
  186. package/dist/utils/common.js.map +0 -1
  187. package/dist/utils/csv.js.map +0 -1
  188. package/dist/utils/datatype.js.map +0 -1
  189. package/dist/utils/icons.js.map +0 -1
  190. package/dist/utils/index.js.map +0 -1
  191. package/dist/utils/logger.js.map +0 -1
@@ -1,322 +1 @@
1
- /* Query editor: run arbitrary SQL, save/load named queries, render results. */
2
- (function () {
3
- 'use strict';
4
- const editor = document.getElementById('query-sql');
5
- const gutter = document.getElementById('query-gutter');
6
- const runBtn = document.getElementById('run-query');
7
- const messageEl = document.getElementById('query-message');
8
- const resultEl = document.getElementById('query-result');
9
- const headEl = document.getElementById('query-result-head');
10
- const bodyEl = document.getElementById('query-result-body');
11
- const resultSummary = document.getElementById('result-summary');
12
- const saveBtns = Array.prototype.slice.call(document.querySelectorAll('#save-query, #save-query-inline'));
13
- const saveName = document.getElementById('save-query-name');
14
- const selectEl = document.getElementById('saved-query-select');
15
- const deleteBtn = document.getElementById('delete-query');
16
- const exportCsvBtn = document.getElementById('export-csv');
17
- const exportJsonBtn = document.getElementById('export-json');
18
-
19
- let queriesCache = [];
20
-
21
- function setExportEnabled(enabled) {
22
- if (exportCsvBtn) exportCsvBtn.disabled = !enabled;
23
- if (exportJsonBtn) exportJsonBtn.disabled = !enabled;
24
- }
25
-
26
- // ---- Export results ------------------------------------------------------
27
-
28
- async function exportResults(format) {
29
- const sql = editor.value.trim();
30
- if (!sql) return;
31
- try {
32
- const res = await fetch(Api.url('/api/query/export'), {
33
- method: 'POST',
34
- headers: { 'Content-Type': 'application/json' },
35
- body: JSON.stringify({ sql, format }),
36
- });
37
- if (!res.ok) {
38
- let msg = 'Export failed (' + res.status + ').';
39
- try {
40
- const data = await res.json();
41
- if (data && data.error) msg = data.error;
42
- } catch (e) { /* ignore */ }
43
- throw new Error(msg);
44
- }
45
- const blob = await res.blob();
46
- const url = URL.createObjectURL(blob);
47
- const a = document.createElement('a');
48
- const stamp = new Date().toISOString().replace(/[:.]/g, '-');
49
- a.href = url;
50
- a.download = 'query-' + stamp + '.' + format;
51
- document.body.appendChild(a);
52
- a.click();
53
- a.remove();
54
- setTimeout(() => URL.revokeObjectURL(url), 1000);
55
- UI.showToast('Exported as ' + format.toUpperCase() + '.', 'success');
56
- } catch (e) {
57
- setMessage(e.message, 'error');
58
- }
59
- }
60
-
61
- if (exportCsvBtn) exportCsvBtn.addEventListener('click', () => exportResults('csv'));
62
- if (exportJsonBtn) exportJsonBtn.addEventListener('click', () => exportResults('json'));
63
-
64
- function escapeHtml(s) {
65
- return (window.UI && UI.escapeHtml) ? UI.escapeHtml(s) : String(s == null ? '' : s);
66
- }
67
-
68
- function setMessage(text, type) {
69
- if (!text) {
70
- messageEl.innerHTML = '';
71
- return;
72
- }
73
- const palette = {
74
- error: 'border-error/40 bg-error/10 text-error',
75
- success: 'border-success/40 bg-success/10 text-success',
76
- info: 'border-info/40 bg-info/10 text-info',
77
- warning: 'border-warning/40 bg-warning/10 text-warning',
78
- };
79
- messageEl.innerHTML = '<div class="flex items-start gap-2.5 rounded-xl border px-4 py-3 text-sm font-medium ' +
80
- (palette[type] || palette.info) + '"><span>' + escapeHtml(text) + '</span></div>';
81
- }
82
-
83
- function isJsonStr(s) {
84
- if (typeof s !== 'string') return false;
85
- const t = s.trim();
86
- if (!((t.startsWith('{') && t.endsWith('}')) || (t.startsWith('[') && t.endsWith(']')))) return false;
87
- try { JSON.parse(t); return true; } catch { return false; }
88
- }
89
-
90
- function isHexBlobStr(s) {
91
- return typeof s === 'string' && /^0x[0-9a-f]{4,}$/i.test(s.trim());
92
- }
93
-
94
- function renderTable(columns, rows) {
95
- headEl.innerHTML = '<tr>' + columns.map((c) => '<th class="whitespace-nowrap font-semibold"><span class="font-mono normal-case">' + escapeHtml(c) + '</span></th>').join('') + '</tr>';
96
- if (!rows.length) {
97
- bodyEl.innerHTML = '<tr><td colspan="' + Math.max(1, columns.length) + '" class="py-10 text-center text-base-content/45">No rows returned.</td></tr>';
98
- return;
99
- }
100
- bodyEl.innerHTML = rows.map((row) =>
101
- '<tr class="hover:bg-primary/[0.04]">' + columns.map((c) => {
102
- const v = row[c];
103
- if (v === null || v === undefined) {
104
- return '<td class="align-middle"><span class="chip chip-null">NULL</span></td>';
105
- }
106
- const s = String(v);
107
- const json = isJsonStr(s);
108
- const blob = isHexBlobStr(s);
109
- const short = s.length > 140 ? s.slice(0, 140) + '…' : s;
110
-
111
- if (json) {
112
- return '<td class="max-w-xs align-middle js-query-cell" data-col="' + escapeHtml(c) + '" data-value="' + escapeHtml(s) + '" data-json="true">' +
113
- '<div class="flex items-center gap-1.5">' +
114
- '<span class="inline-flex items-center gap-1 rounded-md bg-info/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-info">{ } JSON</span>' +
115
- '<span class="block max-w-[120px] truncate font-mono text-[12px] text-base-content/70">' + escapeHtml(short) + '</span>' +
116
- '<button type="button" class="btn btn-ghost btn-xs btn-circle js-query-inspect" title="Inspect JSON"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button>' +
117
- '</div></td>';
118
- }
119
-
120
- if (blob) {
121
- return '<td class="max-w-xs align-middle js-query-cell" data-col="' + escapeHtml(c) + '" data-value="' + escapeHtml(s) + '" data-blob="true">' +
122
- '<div class="flex items-center gap-1.5">' +
123
- '<span class="inline-flex items-center gap-1 rounded-md bg-secondary/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-secondary">🗃️ BLOB</span>' +
124
- '<span class="block max-w-[120px] truncate font-mono text-[12px] text-base-content/70">' + escapeHtml(short) + '</span>' +
125
- '<button type="button" class="btn btn-ghost btn-xs btn-circle js-query-inspect" title="Inspect BLOB"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button>' +
126
- '</div></td>';
127
- }
128
-
129
- return '<td class="max-w-xs align-middle js-query-cell" data-col="' + escapeHtml(c) + '" data-value="' + escapeHtml(s) + '">' +
130
- '<div class="flex items-center justify-between gap-1 group/cell">' +
131
- '<span class="truncate font-mono text-[13px]" title="' + escapeHtml(s) + '">' + escapeHtml(short) + '</span>' +
132
- (s.length > 25 ? '<button type="button" class="btn btn-ghost btn-xs btn-circle opacity-0 group-hover/cell:opacity-100 transition-opacity js-query-inspect shrink-0" title="Inspect text"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button>' : '') +
133
- '</div></td>';
134
- }).join('') + '</tr>',
135
- ).join('');
136
- }
137
-
138
- // Delegate inspection click
139
- if (bodyEl) {
140
- bodyEl.addEventListener('click', function (e) {
141
- const inspectBtn = e.target.closest('.js-query-inspect');
142
- const cell = e.target.closest('.js-query-cell');
143
- if (!inspectBtn && (!cell || e.detail < 2)) return;
144
- const targetCell = inspectBtn ? inspectBtn.closest('.js-query-cell') : cell;
145
- if (!targetCell) return;
146
-
147
- const col = targetCell.dataset.col || 'result';
148
- const val = targetCell.dataset.value || '';
149
- const isBlob = targetCell.dataset.blob === 'true';
150
- const isJson = targetCell.dataset.json === 'true';
151
-
152
- if (window.Inspector && window.Inspector.open) {
153
- window.Inspector.open({
154
- col: col,
155
- value: val,
156
- isNull: false,
157
- isBlob: isBlob,
158
- isJson: isJson,
159
- readonly: true,
160
- });
161
- }
162
- });
163
- }
164
-
165
- function renderMessageRow(text, kindLabel) {
166
- headEl.innerHTML = '';
167
- bodyEl.innerHTML = '<tr><td class="px-4 py-8 text-center"><p class="text-2xl font-extrabold text-primary">' +
168
- escapeHtml(text) + '</p><p class="mt-1 text-sm text-base-content/45">' + escapeHtml(kindLabel) + '</p></td></tr>';
169
- }
170
-
171
- function setSummary(text) {
172
- if (resultSummary) resultSummary.textContent = text;
173
- }
174
-
175
- function isDestructiveSql(sql) {
176
- var cleaned = sql.replace(/--.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '').trim();
177
- if (/\b(drop\s+(table|view|index|trigger)|truncate\b)/i.test(cleaned)) return true;
178
- if (/\bdelete\s+from\b/i.test(cleaned) && !/\bwhere\b/i.test(cleaned)) return true;
179
- return false;
180
- }
181
-
182
- async function run() {
183
- const sql = editor.value.trim();
184
- if (!sql) {
185
- setMessage('Enter a SQL statement first.', 'warning');
186
- return;
187
- }
188
- if (isDestructiveSql(sql)) {
189
- const confirmed = await UI.confirm({
190
- title: 'Execute Destructive SQL',
191
- message: 'This SQL statement contains potentially destructive operations (e.g. DROP, TRUNCATE, or unrestricted DELETE) that may permanently erase database schemas or data.',
192
- detailsHtml: '<pre class="font-mono text-xs text-base-content/85 whitespace-pre-wrap break-all">' + escapeHtml(sql.length > 350 ? sql.slice(0, 350) + '…' : sql) + '</pre>',
193
- confirmText: 'Execute anyway',
194
- danger: true,
195
- });
196
- if (!confirmed) return;
197
- }
198
- messageEl.innerHTML = '';
199
- resultEl.classList.add('hidden');
200
- setExportEnabled(false);
201
- const started = performance.now();
202
- try {
203
- const data = await Api.post('/api/query', { sql });
204
- const elapsed = Math.max(1, Math.round(performance.now() - started));
205
- resultEl.classList.remove('hidden');
206
- if (data.kind === 'count') {
207
- renderMessageRow(String(data.count), 'count');
208
- setSummary('Count query · ' + elapsed + 'ms');
209
- } else if (data.kind === 'select') {
210
- renderTable(data.columns, data.rows);
211
- setSummary(data.rows.length + ' row(s) · ' + elapsed + 'ms');
212
- setExportEnabled(true);
213
- } else {
214
- renderMessageRow(data.message || 'Statement executed.', 'write');
215
- setSummary(data.changes != null ? data.changes + ' row(s) affected · ' + elapsed + 'ms' : elapsed + 'ms');
216
- }
217
- } catch (e) {
218
- resultEl.classList.add('hidden');
219
- setMessage(e.message, 'error');
220
- }
221
- }
222
-
223
- // ---- Line numbers ------------------------------------------------------
224
-
225
- function updateGutter() {
226
- if (!gutter) return;
227
- const lines = editor.value.split('\n').length;
228
- let html = '';
229
- for (let i = 1; i <= lines; i++) html += i + '\n';
230
- gutter.textContent = html;
231
- gutter.scrollTop = editor.scrollTop;
232
- }
233
-
234
- if (editor && gutter) {
235
- editor.addEventListener('scroll', () => {
236
- gutter.scrollTop = editor.scrollTop;
237
- });
238
- editor.addEventListener('input', updateGutter);
239
- editor.addEventListener('keydown', (e) => {
240
- if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
241
- e.preventDefault();
242
- run();
243
- }
244
- });
245
- updateGutter();
246
- }
247
-
248
- // ---- Saved queries -----------------------------------------------------
249
-
250
- async function refreshQueries(selectName) {
251
- try {
252
- queriesCache = await Api.get('/api/queries');
253
- } catch (e) {
254
- queriesCache = [];
255
- }
256
- selectEl.innerHTML = '<option value="">— select a saved query —</option>' + queriesCache.map((q) =>
257
- '<option value="' + q.id + '"' + (selectName && q.name === selectName ? ' selected' : '') + '>' + escapeHtml(q.name) + '</option>',
258
- ).join('');
259
- deleteBtn.disabled = true;
260
- }
261
-
262
- async function saveQuery() {
263
- const sql = editor.value.trim();
264
- const name = saveName.value.trim();
265
- if (!sql) {
266
- setMessage('Write some SQL before saving.', 'warning');
267
- return;
268
- }
269
- if (!name) {
270
- setMessage('Give the query a name.', 'warning');
271
- return;
272
- }
273
- try {
274
- await Api.post('/api/queries', { name, sql });
275
- UI.showToast('Query saved.', 'success');
276
- await refreshQueries(name);
277
- } catch (e) {
278
- setMessage(e.message, 'error');
279
- }
280
- }
281
-
282
- async function deleteQuery() {
283
- const id = selectEl.value;
284
- if (!id) return;
285
- const q = queriesCache.find((x) => String(x.id) === String(id));
286
- const queryName = q ? q.name : 'Saved query';
287
- const confirmed = await UI.confirm({
288
- title: 'Delete Saved Query',
289
- message: 'Are you sure you want to delete the saved query "' + queryName + '"?',
290
- item: queryName,
291
- itemType: 'Saved Query',
292
- confirmText: 'Delete query',
293
- danger: true,
294
- });
295
- if (!confirmed) return;
296
- try {
297
- await Api.del('/api/queries/' + id);
298
- UI.showToast('Query deleted.', 'success');
299
- await refreshQueries();
300
- } catch (e) {
301
- setMessage(e.message, 'error');
302
- }
303
- }
304
-
305
- if (runBtn) runBtn.addEventListener('click', run);
306
- saveBtns.forEach((btn) => btn.addEventListener('click', saveQuery));
307
- if (selectEl) {
308
- selectEl.addEventListener('change', () => {
309
- const id = selectEl.value;
310
- deleteBtn.disabled = !id;
311
- if (!id) return;
312
- const q = queriesCache.find((x) => String(x.id) === String(id));
313
- if (q) {
314
- editor.value = q.sql;
315
- saveName.value = q.name;
316
- updateGutter();
317
- setMessage('Loaded "' + q.name + '".', 'info');
318
- }
319
- });
320
- }
321
- if (deleteBtn) deleteBtn.addEventListener('click', deleteQuery);
322
- })();
1
+ !function(){"use strict";const t=document.getElementById("query-sql"),e=document.getElementById("query-gutter"),n=document.getElementById("editor-cursor-pos"),s=document.getElementById("editor-stats"),r=document.getElementById("run-query"),i=document.getElementById("format-query"),o=document.getElementById("copy-query"),a=document.getElementById("clear-query"),l=document.getElementById("query-message"),c=document.getElementById("query-result"),d=document.getElementById("query-result-head"),u=document.getElementById("query-result-body"),p=document.getElementById("result-summary"),m=Array.prototype.slice.call(document.querySelectorAll("#save-query, #save-query-inline")),f=document.getElementById("save-query-name"),g=document.getElementById("saved-query-select"),h=document.getElementById("delete-query"),x=document.getElementById("export-csv"),w=document.getElementById("export-json");let y=[],b=null,v=!1;const E=window.Utils?window.Utils.escapeHtml:function(t){return String(null==t?"":t)},L=window.Utils?window.Utils.highlightSql:function(t){return E(t)};const T=new class{constructor(t=100){this.stack=[],this.index=-1,this.limit=t,this.lastRecordTime=0}record(t,e,n=250){const s=Date.now(),r=this.index>=0?this.stack[this.index]:null;if(r&&r.text===t)r.selection=e;else{if(r&&s-this.lastRecordTime<n&&1===Math.abs(t.length-r.text.length)&&!/\s|[;(),]/.test(t.slice(-1)))return r.text=t,r.selection=e,void(this.lastRecordTime=s);this.stack=this.stack.slice(0,this.index+1),this.stack.push({text:t,selection:e}),this.stack.length>this.limit?this.stack.shift():this.index++,this.lastRecordTime=s}}undo(){return this.index>0?(this.index--,this.stack[this.index]):null}redo(){return this.index<this.stack.length-1?(this.index++,this.stack[this.index]):null}}(100);function I(){if(!t)return"";return(t.innerText||t.textContent||"").replace(/\r\n/g,"\n").replace(/\r/g,"\n")}function k(t){const e=window.getSelection();if(!e||!e.rangeCount)return{start:0,end:0};const n=e.getRangeAt(0),s=n.cloneRange();s.selectNodeContents(t),s.setEnd(n.startContainer,n.startOffset);const r=s.toString().length;return{start:r,end:r+n.toString().length}}function q(t,e){const n=window.getSelection();if(!n)return;let s=0;const r=document.createRange();r.setStart(t,0),r.collapse(!0);const i=[t];let o,a=!1,l=!1;for(;!l&&(o=i.pop());)if(3===o.nodeType){const t=s+o.length;!a&&e.start>=s&&e.start<=t&&(r.setStart(o,e.start-s),a=!0),a&&e.end>=s&&e.end<=t&&(r.setEnd(o,e.end-s),l=!0),s=t}else{let t=o.childNodes.length;for(;t--;)i.push(o.childNodes[t])}n.removeAllRanges(),n.addRange(r)}function S(e,n=!0){if(t&&(v=!0,t.innerHTML=e?L(e):"",v=!1,M(),O(),n)){const t=(e||"").length;T.record(e||"",{start:t,end:t},0)}}function U(e){if(!t)return;const n=I(),s=k(t),r=n.slice(0,s.start)+e+n.slice(s.end),i=s.start+e.length;v=!0,t.innerHTML=L(r),q(t,{start:i,end:i}),v=!1,M(),O(),T.record(r,{start:i,end:i},0)}function M(){if(!t||!e)return;const n=I().split("\n"),s=Math.max(1,n.length);let r="";for(let t=1;t<=s;t++){const e=null!=b&&t===b;r+='<div class="sql-gutter-line'+(e?" has-error":"")+'" data-line="'+t+'" title="'+(e?"Error on line "+t:"Line "+t)+'">'+t+"</div>"}e.innerHTML=r,e.scrollTop=t.scrollTop}function O(){if(!t||!n)return;const e=I(),r=k(t),i=e.slice(0,r.start).split("\n"),o=i.length,a=i[i.length-1].length+1;if(n.textContent="Ln "+o+", Col "+a,s){const t=e.length,n=e.split("\n").length,i=Math.abs(r.end-r.start);s.textContent=i>0?t+" chars ("+i+" selected) · "+n+" lines":t+" chars · "+n+" lines"}}function j(e,n){if(!t)return;const s=I().split("\n"),r=Math.max(1,Math.min(s.length,e||1));let i=0;for(let t=0;t<r-1;t++)i+=s[t].length+1;n&&n>1&&(i+=Math.min(s[r-1].length,n-1)),t.focus(),q(t,{start:i,end:i});t.scrollTop=Math.max(0,21*(r-3)),M(),O()}function B(t,e){if(!l)return;if(!t)return void(l.innerHTML="");const n={error:"border-error/40 bg-error/10 text-error",success:"border-success/40 bg-success/10 text-success",info:"border-info/40 bg-info/10 text-info",warning:"border-warning/40 bg-warning/10 text-warning"};l.innerHTML='<div class="flex items-start gap-2.5 rounded-xl border px-4 py-3 text-sm font-medium '+(n[e]||n.info)+'"><span>'+E(t)+"</span></div>"}function R(t){x&&(x.disabled=!t),w&&(w.disabled=!t)}async function N(t){const e=I().trim();if(e)try{const n=await fetch(Api.url("/api/query/export"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sql:e,format:t})});if(!n.ok){let t="Export failed ("+n.status+").";try{const e=await n.json();e&&e.error&&(t=e.error)}catch(t){}throw new Error(t)}const s=await n.blob(),r=(new Date).toISOString().replace(/[:.]/g,"-");window.Utils&&window.Utils.downloadBlob&&window.Utils.downloadBlob(s,"query-"+r+"."+t),window.UI&&UI.showToast&&UI.showToast("Exported as "+t.toUpperCase()+".","success")}catch(t){B(t.message,"error")}}i&&i.addEventListener("click",function(){const t=I();t.trim()&&(S(function(t){if(!t)return"";let e=t.trim().replace(/\r\n/g,"\n").replace(/\t/g," ");return["SELECT","FROM","WHERE","AND","OR","GROUP BY","HAVING","ORDER BY","LIMIT","OFFSET","LEFT JOIN","RIGHT JOIN","INNER JOIN","CROSS JOIN","FULL JOIN","JOIN","UNION ALL","UNION","INSERT INTO","VALUES","UPDATE","SET","DELETE FROM","CREATE TABLE","ALTER TABLE","DROP TABLE"].forEach(function(t){const n=new RegExp("\\b"+t.replace(" ","\\s+")+"\\b","gi");e=e.replace(n,"\n"+t.toUpperCase())}),e.split("\n").map(function(t){return t.trim()}).filter(function(t){return t.length>0}).map(function(t){const e=t.toUpperCase();return e.startsWith("WHERE")||e.startsWith("AND")||e.startsWith("OR")||e.startsWith("JOIN")||e.startsWith("LEFT")||e.startsWith("RIGHT")||e.startsWith("INNER")||e.startsWith("ON")||e.startsWith("SET")||e.startsWith("VALUES")||e.startsWith("HAVING")||e.startsWith("ORDER")||e.startsWith("GROUP")||e.startsWith("LIMIT")||e.startsWith("OFFSET")?" "+t:t}).join("\n")}(t)),window.UI&&UI.showToast&&UI.showToast("SQL formatted.","info"))}),o&&o.addEventListener("click",function(){const t=I();t.trim()&&navigator.clipboard.writeText(t).then(function(){window.UI&&UI.showToast&&UI.showToast("SQL copied to clipboard.","success")})}),a&&a.addEventListener("click",function(){S(""),b=null,B("","info"),t.focus()}),document.querySelectorAll(".js-snippet").forEach(function(e){e.addEventListener("click",function(n){n.preventDefault();const s=e.getAttribute("data-snippet");s&&(S(s.replace(/\\n/g,"\n")),b=null,B("","info"),t.focus())})}),e&&e.addEventListener("click",function(t){const e=t.target.closest(".sql-gutter-line");if(!e)return;const n=parseInt(e.dataset.line,10);n&&j(n,1)}),t&&(t.addEventListener("input",function(){b=null;const e=I(),n=k(t);T.record(e,n,0),function(){if(!t||v)return;v=!0;const e=k(t),n=I();n?(t.innerHTML=L(n),q(t,e)):t.innerHTML="",v=!1,M(),O()}()}),t.addEventListener("paste",function(t){t.preventDefault();const e=(t.clipboardData||window.clipboardData).getData("text/plain");e&&U(e)}),t.addEventListener("scroll",function(){e&&(e.scrollTop=t.scrollTop)}),t.addEventListener("click",function(){O()}),t.addEventListener("keyup",function(){O()}),t.addEventListener("keydown",function(e){if((e.ctrlKey||e.metaKey)&&"z"===e.key.toLowerCase()&&!e.shiftKey)return e.preventDefault(),void function(){const e=T.undo();e&&(v=!0,t.innerHTML=L(e.text),q(t,e.selection||{start:0,end:0}),v=!1,b=null,M(),O())}();if((e.ctrlKey||e.metaKey)&&"y"===e.key.toLowerCase()||(e.ctrlKey||e.metaKey)&&e.shiftKey&&"z"===e.key.toLowerCase())return e.preventDefault(),void function(){const e=T.redo();e&&(v=!0,t.innerHTML=L(e.text),q(t,e.selection||{start:0,end:0}),v=!1,b=null,M(),O())}();if((e.ctrlKey||e.metaKey)&&"Enter"===e.key)return e.preventDefault(),void W();if("Enter"===e.key){e.preventDefault();const n=I(),s=k(t),r=(n.slice(0,s.start).split("\n").pop()||"").match(/^(\s+)/);return void U("\n"+(r?r[1]:""))}if((e.ctrlKey||e.metaKey)&&"/"===e.key){e.preventDefault();const n=k(t),s=I(),r=s.lastIndexOf("\n",n.start-1)+1;let i=s.indexOf("\n",n.end);-1===i&&(i=s.length);const o=s.slice(r,i).split("\n"),a=o.every(function(t){return t.trim().startsWith("--")}),l=o.map(function(t){return a?t.replace(/^(\s*)--\s?/,"$1"):t?"-- "+t:t}).join("\n");return S(s.slice(0,r)+l+s.slice(i)),void q(t,{start:r,end:r+l.length})}if("Tab"===e.key){e.preventDefault();const n=k(t),s=I();if(n.start===n.end)e.shiftKey||U(" ");else{const r=s.lastIndexOf("\n",n.start-1)+1;let i=s.indexOf("\n",n.end);-1===i&&(i=s.length);const o=s.slice(r,i).split("\n").map(function(t){return e.shiftKey?t.replace(/^( |\t| )/,""):" "+t}).join("\n");S(s.slice(0,r)+o+s.slice(i)),q(t,{start:r,end:r+o.length})}return}})),x&&x.addEventListener("click",()=>N("csv")),w&&w.addEventListener("click",()=>N("json"));const C=window.Utils?window.Utils.isJson:function(t){return!1},A=window.Utils?window.Utils.isHexBlob:function(t){return!1};function H(t,e){d.innerHTML="",u.innerHTML='<tr><td class="px-4 py-8 text-center"><p class="text-2xl font-extrabold text-primary">'+E(t)+'</p><p class="mt-1 text-sm text-base-content/45">'+E(e)+"</p></td></tr>"}function D(t){p&&(p.textContent=t)}async function W(){const t=I().trim();if(!t)return void B("Enter a SQL statement first.","warning");if(function(t){var e=t.replace(/--.*$/gm,"").replace(/\/\*[\s\S]*?\*\//g,"").trim();return!!/\b(drop\s+(table|view|index|trigger)|truncate\b)/i.test(e)||!(!/\bdelete\s+from\b/i.test(e)||/\bwhere\b/i.test(e))}(t)){if(!await UI.confirm({title:"Execute Destructive SQL",message:"This SQL statement contains potentially destructive operations (e.g. DROP, TRUNCATE, or unrestricted DELETE) that may permanently erase database schemas or data.",detailsHtml:'<pre class="font-mono text-xs text-base-content/85 whitespace-pre-wrap break-all">'+E(t.length>350?t.slice(0,350)+"…":t)+"</pre>",confirmText:"Execute anyway",danger:!0}))return}b=null,M(),l.innerHTML="",c.classList.add("hidden"),R(!1);const e=performance.now();try{const r=await Api.post("/api/query",{sql:t}),i=Math.max(1,Math.round(performance.now()-e));c.classList.remove("hidden"),"count"===r.kind?(H(String(r.count),"count"),D("Count query · "+i+"ms")):"select"===r.kind?(n=r.columns,s=r.rows,d.innerHTML="<tr>"+n.map(t=>'<th class="whitespace-nowrap font-semibold"><span class="font-mono normal-case">'+E(t)+"</span></th>").join("")+"</tr>",s.length?u.innerHTML=s.map(t=>'<tr class="hover:bg-primary/[0.04]">'+n.map(e=>{const n=t[e];if(null==n)return'<td class="align-middle"><span class="chip chip-null">NULL</span></td>';const s=String(n),r=C(s),i=A(s),o=s.length>140?s.slice(0,140)+"…":s;return r?'<td class="max-w-xs align-middle js-query-cell" data-col="'+E(e)+'" data-value="'+E(s)+'" data-json="true"><div class="flex items-center gap-1.5"><span class="inline-flex items-center gap-1 rounded-md bg-info/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-info">{ } JSON</span><span class="block max-w-[120px] truncate font-mono text-[12px] text-base-content/70">'+E(o)+'</span><button type="button" class="btn btn-ghost btn-xs btn-circle js-query-inspect" title="Inspect JSON"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button></div></td>':i?'<td class="max-w-xs align-middle js-query-cell" data-col="'+E(e)+'" data-value="'+E(s)+'" data-blob="true"><div class="flex items-center gap-1.5"><span class="inline-flex items-center gap-1 rounded-md bg-secondary/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-secondary">🗃️ BLOB</span><span class="block max-w-[120px] truncate font-mono text-[12px] text-base-content/70">'+E(o)+'</span><button type="button" class="btn btn-ghost btn-xs btn-circle js-query-inspect" title="Inspect BLOB"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button></div></td>':'<td class="max-w-xs align-middle js-query-cell" data-col="'+E(e)+'" data-value="'+E(s)+'"><div class="flex items-center justify-between gap-1 group/cell"><span class="truncate font-mono text-[13px]" title="'+E(s)+'">'+E(o)+"</span>"+(s.length>25?'<button type="button" class="btn btn-ghost btn-xs btn-circle opacity-0 group-hover/cell:opacity-100 transition-opacity js-query-inspect shrink-0" title="Inspect text"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg></button>':"")+"</div></td>"}).join("")+"</tr>").join(""):u.innerHTML='<tr><td colspan="'+Math.max(1,n.length)+'" class="py-10 text-center text-base-content/45">No rows returned.</td></tr>',D(r.rows.length+" row(s) · "+i+"ms"),R(!0)):(H(r.message||"Statement executed.","write"),D(null!=r.changes?r.changes+" row(s) affected · "+i+"ms":i+"ms"))}catch(t){c.classList.add("hidden"),function(t){if(!l)return;const e=t&&t.details,n=t&&t.message||e&&e.message||"Query failed.";if(!e||!e.snippet&&!e.cause&&!e.suggestion)return void B(n,"error");b=e.line||null,M();let s='<div class="sql-error-card space-y-3">';s+='<div class="flex flex-wrap items-center justify-between gap-2 border-b border-error/20 pb-2.5">',s+='<div class="flex items-center gap-2">',s+='<span class="grid h-6 w-6 place-items-center rounded-lg bg-error/15 text-error font-bold">✕</span>',s+='<span class="font-bold text-sm text-error">'+E(n)+"</span>",s+="</div>",e.line&&(s+='<div class="flex items-center gap-1.5">',s+='<span class="badge badge-sm badge-error font-mono font-bold">Line '+e.line+(e.column?", Col "+e.column:"")+"</span>",s+='<button type="button" class="btn btn-xs btn-outline btn-error js-jump-err-line" data-line="'+e.line+'" data-col="'+(e.column||1)+'">Jump to line</button>',s+="</div>"),s+="</div>",e.snippet&&(s+="<div>",s+='<p class="text-[11px] font-bold uppercase tracking-wider text-base-content/45 mb-1.5">Code Snippet</p>',s+='<pre class="sql-error-snippet">'+E(e.snippet)+"</pre>",s+="</div>"),(e.cause||e.suggestion)&&(s+='<div class="grid grid-cols-1 md:grid-cols-2 gap-2.5 pt-1">',e.cause&&(s+='<div class="rounded-lg border border-base-300 bg-base-100/90 p-3 shadow-xs">',s+='<div class="flex items-center gap-1.5 text-xs font-bold text-base-content/80 mb-1">',s+="<span>💡</span><span>Possible Cause</span>",s+="</div>",s+='<p class="text-xs text-base-content/70 leading-relaxed">'+E(e.cause)+"</p>",s+="</div>"),e.suggestion&&(s+='<div class="rounded-lg border border-primary/30 bg-primary/5 p-3 shadow-xs">',s+='<div class="flex items-center gap-1.5 text-xs font-bold text-primary mb-1">',s+="<span>🔧</span><span>Suggested Fix</span>",s+="</div>",s+='<p class="text-xs text-base-content/80 font-medium leading-relaxed">'+E(e.suggestion)+"</p>",s+="</div>"),s+="</div>"),s+="</div>",l.innerHTML=s;const r=l.querySelector(".js-jump-err-line");r&&r.addEventListener("click",function(){j(parseInt(r.dataset.line,10),parseInt(r.dataset.col,10))})}(t)}var n,s}async function K(t){try{y=await Api.get("/api/queries")}catch(t){y=[]}g.innerHTML='<option value="">— select a saved query —</option>'+y.map(e=>'<option value="'+e.id+'"'+(t&&e.name===t?" selected":"")+">"+E(e.name)+"</option>").join(""),h.disabled=!0}async function J(){const t=I().trim(),e=f.value.trim();if(t)if(e)try{await Api.post("/api/queries",{name:e,sql:t}),window.UI&&UI.showToast&&UI.showToast("Query saved.","success"),await K(e)}catch(t){B(t.message,"error")}else B("Give the query a name.","warning");else B("Write some SQL before saving.","warning")}u&&u.addEventListener("click",function(t){const e=t.target.closest(".js-query-inspect"),n=t.target.closest(".js-query-cell");if(!e&&(!n||t.detail<2))return;const s=e?e.closest(".js-query-cell"):n;if(!s)return;const r=s.dataset.col||"result",i=s.dataset.value||"",o="true"===s.dataset.blob,a="true"===s.dataset.json;window.Inspector&&window.Inspector.open&&window.Inspector.open({col:r,value:i,isNull:!1,isBlob:o,isJson:a,readonly:!0})}),r&&r.addEventListener("click",W),m.forEach(t=>t.addEventListener("click",J)),g&&g.addEventListener("change",()=>{const t=g.value;if(h.disabled=!t,!t)return;const e=y.find(e=>String(e.id)===String(t));e&&(S(e.sql),f.value=e.name,b=null,B('Loaded "'+e.name+'".',"info"))}),h&&h.addEventListener("click",async function(){const t=g.value;if(!t)return;const e=y.find(e=>String(e.id)===String(t)),n=e?e.name:"Saved query";if(await UI.confirm({title:"Delete Saved Query",message:'Are you sure you want to delete the saved query "'+n+'"?',item:n,itemType:"Saved Query",confirmText:"Delete query",danger:!0}))try{await Api.del("/api/queries/"+t),window.UI&&UI.showToast&&UI.showToast("Query deleted.","success"),await K()}catch(t){B(t.message,"error")}});const Q=I();Q?S(Q,!0):T.record("",{start:0,end:0},0),M(),O()}();