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
@@ -0,0 +1,550 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildErrorSnippet = buildErrorSnippet;
4
+ exports.offsetToLineCol = offsetToLineCol;
5
+ exports.maskCommentsAndStrings = maskCommentsAndStrings;
6
+ exports.findTokenPosition = findTokenPosition;
7
+ exports.analyzeSqlError = analyzeSqlError;
8
+ function levenshteinDistance(a, b) {
9
+ const an = a.length;
10
+ const bn = b.length;
11
+ if (an === 0)
12
+ return bn;
13
+ if (bn === 0)
14
+ return an;
15
+ const matrix = [];
16
+ for (let i = 0; i <= bn; i++)
17
+ matrix[i] = [i];
18
+ for (let j = 0; j <= an; j++)
19
+ matrix[0][j] = j;
20
+ for (let i = 1; i <= bn; i++) {
21
+ for (let j = 1; j <= an; j++) {
22
+ if (b.charAt(i - 1).toLowerCase() === a.charAt(j - 1).toLowerCase()) {
23
+ matrix[i][j] = matrix[i - 1][j - 1];
24
+ }
25
+ else {
26
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
27
+ }
28
+ }
29
+ }
30
+ return matrix[bn][an];
31
+ }
32
+ function findClosestMatch(target, candidates, maxDist = 3) {
33
+ if (!candidates.length || !target)
34
+ return null;
35
+ let best = null;
36
+ let bestDist = maxDist + 1;
37
+ for (const c of candidates) {
38
+ if (c.toLowerCase() === target.toLowerCase())
39
+ continue;
40
+ const dist = levenshteinDistance(target, c);
41
+ if (dist < bestDist) {
42
+ bestDist = dist;
43
+ best = c;
44
+ }
45
+ }
46
+ return bestDist <= maxDist ? best : null;
47
+ }
48
+ function buildErrorSnippet(sql, line, column) {
49
+ const lines = sql.split(/\r?\n/);
50
+ const targetLineIdx = Math.max(0, Math.min(lines.length - 1, line - 1));
51
+ const startIdx = Math.max(0, targetLineIdx - 1);
52
+ const endIdx = Math.min(lines.length - 1, targetLineIdx + 1);
53
+ const maxLineNumLen = Math.max(2, String(endIdx + 1).length);
54
+ const out = [];
55
+ for (let i = startIdx; i <= endIdx; i++) {
56
+ const lineNum = String(i + 1).padStart(maxLineNumLen, ' ');
57
+ const isError = i === targetLineIdx;
58
+ const prefix = isError ? `> ${lineNum} | ` : ` ${lineNum} | `;
59
+ out.push(`${prefix}${lines[i]}`);
60
+ if (isError) {
61
+ const colPad = Math.max(0, column - 1);
62
+ const indent = ' '.repeat(maxLineNumLen + 5 + colPad);
63
+ out.push(`${indent}^`);
64
+ }
65
+ }
66
+ return out.join('\n');
67
+ }
68
+ function offsetToLineCol(sql, offset) {
69
+ let line = 1;
70
+ let col = 1;
71
+ const target = Math.max(1, Math.min(sql.length, offset));
72
+ for (let i = 0; i < target - 1; i++) {
73
+ if (sql[i] === '\n') {
74
+ line++;
75
+ col = 1;
76
+ }
77
+ else {
78
+ col++;
79
+ }
80
+ }
81
+ return { line, column: col };
82
+ }
83
+ function maskCommentsAndStrings(sql) {
84
+ let out = '';
85
+ let inSingleQuote = false;
86
+ let inDoubleQuote = false;
87
+ let inLineComment = false;
88
+ let inBlockComment = false;
89
+ for (let i = 0; i < sql.length; i++) {
90
+ const ch = sql[i];
91
+ const next = sql[i + 1];
92
+ if (inLineComment) {
93
+ if (ch === '\n') {
94
+ inLineComment = false;
95
+ out += '\n';
96
+ }
97
+ else {
98
+ out += ' ';
99
+ }
100
+ continue;
101
+ }
102
+ if (inBlockComment) {
103
+ if (ch === '*' && next === '/') {
104
+ inBlockComment = false;
105
+ out += ' ';
106
+ i++;
107
+ }
108
+ else if (ch === '\n') {
109
+ out += '\n';
110
+ }
111
+ else {
112
+ out += ' ';
113
+ }
114
+ continue;
115
+ }
116
+ if (inSingleQuote) {
117
+ if (ch === "'") {
118
+ if (next === "'") {
119
+ out += ' ';
120
+ i++;
121
+ }
122
+ else {
123
+ inSingleQuote = false;
124
+ out += ' ';
125
+ }
126
+ }
127
+ else if (ch === '\n') {
128
+ out += '\n';
129
+ }
130
+ else {
131
+ out += ' ';
132
+ }
133
+ continue;
134
+ }
135
+ if (inDoubleQuote) {
136
+ if (ch === '"') {
137
+ if (next === '"') {
138
+ out += ' ';
139
+ i++;
140
+ }
141
+ else {
142
+ inDoubleQuote = false;
143
+ out += ' ';
144
+ }
145
+ }
146
+ else if (ch === '\n') {
147
+ out += '\n';
148
+ }
149
+ else {
150
+ out += ' ';
151
+ }
152
+ continue;
153
+ }
154
+ if (ch === '-' && next === '-') {
155
+ inLineComment = true;
156
+ out += ' ';
157
+ i++;
158
+ continue;
159
+ }
160
+ if (ch === '/' && next === '*') {
161
+ inBlockComment = true;
162
+ out += ' ';
163
+ i++;
164
+ continue;
165
+ }
166
+ if (ch === "'") {
167
+ inSingleQuote = true;
168
+ out += ' ';
169
+ continue;
170
+ }
171
+ if (ch === '"') {
172
+ inDoubleQuote = true;
173
+ out += ' ';
174
+ continue;
175
+ }
176
+ out += ch;
177
+ }
178
+ return out;
179
+ }
180
+ function findTokenPosition(sql, token) {
181
+ if (!token)
182
+ return null;
183
+ const masked = maskCommentsAndStrings(sql);
184
+ const maskedLines = masked.split(/\r?\n/);
185
+ const originalLines = sql.split(/\r?\n/);
186
+ const isWordToken = /^[A-Za-z0-9_$]+$/.test(token);
187
+ if (token === '(') {
188
+ for (let l = 0; l < maskedLines.length; l++) {
189
+ const lineText = maskedLines[l];
190
+ const dfMatch = lineText.match(/DEFAULT\s+[a-zA-Z0-9_]+\s*(\()/i);
191
+ if (dfMatch && dfMatch.index != null) {
192
+ const parenCol = lineText.indexOf('(', dfMatch.index + 7);
193
+ if (parenCol !== -1)
194
+ return { line: l + 1, column: parenCol + 1 };
195
+ }
196
+ }
197
+ for (let l = 0; l < maskedLines.length; l++) {
198
+ const lineText = maskedLines[l];
199
+ if (/\bFROM\s+\(/i.test(lineText) || /,\s*\(/i.test(lineText)) {
200
+ const parenCol = lineText.indexOf('(');
201
+ if (parenCol !== -1)
202
+ return { line: l + 1, column: parenCol + 1 };
203
+ }
204
+ }
205
+ }
206
+ if (isWordToken) {
207
+ const wordRegex = new RegExp(`\\b${token}\\b`, 'i');
208
+ for (let l = 0; l < maskedLines.length; l++) {
209
+ const m = maskedLines[l].match(wordRegex);
210
+ if (m && m.index != null) {
211
+ return { line: l + 1, column: m.index + 1 };
212
+ }
213
+ }
214
+ }
215
+ for (let l = 0; l < maskedLines.length; l++) {
216
+ const colIdx = maskedLines[l].toLowerCase().indexOf(token.toLowerCase());
217
+ if (colIdx !== -1) {
218
+ return { line: l + 1, column: colIdx + 1 };
219
+ }
220
+ }
221
+ for (let l = 0; l < originalLines.length; l++) {
222
+ const colIdx = originalLines[l].toLowerCase().indexOf(token.toLowerCase());
223
+ if (colIdx !== -1) {
224
+ return { line: l + 1, column: colIdx + 1 };
225
+ }
226
+ }
227
+ return null;
228
+ }
229
+ async function analyzeSqlError(sql, rawError, db) {
230
+ const rawMsg = rawError instanceof Error ? rawError.message : String(rawError ?? 'Unknown error');
231
+ const details = {
232
+ message: rawMsg,
233
+ raw: rawMsg,
234
+ };
235
+ const cleanSql = sql.trim();
236
+ const lines = sql.split(/\r?\n/);
237
+ const pgPos = rawError?.position;
238
+ if (pgPos != null) {
239
+ const numPos = Number(pgPos);
240
+ if (Number.isFinite(numPos) && numPos > 0) {
241
+ const pos = offsetToLineCol(sql, numPos);
242
+ details.line = pos.line;
243
+ details.column = pos.column;
244
+ }
245
+ }
246
+ let openParens = 0;
247
+ let closeParens = 0;
248
+ let singleQuotes = 0;
249
+ let doubleQuotes = 0;
250
+ let inSingleQuote = false;
251
+ let inDoubleQuote = false;
252
+ let inComment = false;
253
+ for (let i = 0; i < sql.length; i++) {
254
+ const ch = sql[i];
255
+ const next = sql[i + 1];
256
+ if (!inSingleQuote && !inDoubleQuote) {
257
+ if (ch === '-' && next === '-') {
258
+ while (i < sql.length && sql[i] !== '\n')
259
+ i++;
260
+ continue;
261
+ }
262
+ if (ch === '/' && next === '*') {
263
+ i += 2;
264
+ while (i < sql.length - 1 && !(sql[i] === '*' && sql[i + 1] === '/'))
265
+ i++;
266
+ i++;
267
+ continue;
268
+ }
269
+ if (ch === '(')
270
+ openParens++;
271
+ if (ch === ')')
272
+ closeParens++;
273
+ }
274
+ if (ch === "'" && !inDoubleQuote) {
275
+ singleQuotes++;
276
+ inSingleQuote = !inSingleQuote;
277
+ }
278
+ if (ch === '"' && !inSingleQuote) {
279
+ doubleQuotes++;
280
+ inDoubleQuote = !inDoubleQuote;
281
+ }
282
+ }
283
+ const nearMatch = rawMsg.match(/near\s+"([^"]+)":\s*syntax error/i) || rawMsg.match(/syntax error at or near\s+"?([^"\s]+)"?/i);
284
+ if (nearMatch) {
285
+ const token = nearMatch[1];
286
+ details.token = token;
287
+ if (!details.line) {
288
+ const pos = findTokenPosition(sql, token);
289
+ if (pos) {
290
+ details.line = pos.line;
291
+ details.column = pos.column;
292
+ }
293
+ }
294
+ if (token === '(') {
295
+ const defaultFnMatch = sql.match(/DEFAULT\s+([a-zA-Z0-9_]+)\s*\(/i);
296
+ if (defaultFnMatch) {
297
+ const fnName = defaultFnMatch[1];
298
+ if (fnName.toLowerCase() === 'gen_random_uuid') {
299
+ details.cause = `In SQLite, default expressions calling functions must be enclosed in parentheses "DEFAULT (expr)". Additionally, "gen_random_uuid()" is a PostgreSQL function not built into SQLite.`;
300
+ details.suggestion = `For SQLite, use "DEFAULT (lower(hex(randomblob(16))))" or assign UUIDs in your application. Also replace "NOW()" with "CURRENT_TIMESTAMP".`;
301
+ }
302
+ else if (fnName.toLowerCase() === 'now') {
303
+ details.cause = `In SQLite, "NOW()" is not a built-in function (PostgreSQL only), and function default expressions must be enclosed in parentheses.`;
304
+ details.suggestion = `Replace "DEFAULT NOW()" with "DEFAULT CURRENT_TIMESTAMP" or "DEFAULT (datetime('now'))".`;
305
+ }
306
+ else {
307
+ details.cause = `In SQLite, function calls and expressions in a DEFAULT constraint must be enclosed in parentheses, e.g. "DEFAULT (${fnName}(...))" instead of "DEFAULT ${fnName}(...)".`;
308
+ details.suggestion = `Wrap the default function expression in parentheses: DEFAULT (${fnName}(...))`;
309
+ }
310
+ }
311
+ else if (/\bfrom\s+\(/i.test(sql) && !/\bfrom\s+\([^)]+\)\s+as\s+[a-z0-9_]+/i.test(sql) && !/\bfrom\s+\([^)]+\)\s+[a-z0-9_]+/i.test(sql)) {
312
+ details.cause = 'Subqueries in the FROM clause require a table alias.';
313
+ details.suggestion = 'Add a subquery alias after the closing parenthesis, e.g. FROM (SELECT ...) AS sub_table';
314
+ }
315
+ else if (openParens !== closeParens) {
316
+ details.cause = `Mismatched parentheses: Found ${openParens} opening '(' and ${closeParens} closing ')'.`;
317
+ details.suggestion = openParens > closeParens
318
+ ? `Add ${openParens - closeParens} closing ')' to balance the expression.`
319
+ : `Remove ${closeParens - openParens} extra closing ')' from the query.`;
320
+ }
321
+ else if (/,\s*\(/i.test(sql) && !/\bvalues\s*\(/i.test(sql)) {
322
+ details.cause = 'Unexpected parenthesis following a comma in the expression list.';
323
+ details.suggestion = 'Check for extra commas or ensure the parenthesized expression is part of a valid function or subquery.';
324
+ }
325
+ else {
326
+ details.cause = 'An unexpected opening parenthesis "(" was found where an identifier or expression was expected.';
327
+ details.suggestion = 'Check for missing column/table names before "(", or verify subquery syntax.';
328
+ }
329
+ }
330
+ else if (token === ')') {
331
+ if (openParens !== closeParens) {
332
+ details.cause = `Mismatched parentheses: Found ${openParens} opening '(' and ${closeParens} closing ')'.`;
333
+ details.suggestion = `Remove the extra closing parenthesis ')' or add a matching opening '('.`;
334
+ }
335
+ else if (/,\s*\)/i.test(sql)) {
336
+ details.cause = 'Trailing comma detected before the closing parenthesis ")".';
337
+ details.suggestion = 'Remove the trailing comma before ")", e.g. (col1, col2) instead of (col1, col2,).';
338
+ }
339
+ else {
340
+ details.cause = 'An unexpected closing parenthesis ")" was encountered.';
341
+ details.suggestion = 'Verify that every opening parenthesis has a corresponding closing parenthesis and valid enclosed expressions.';
342
+ }
343
+ }
344
+ else if (token === ',') {
345
+ details.cause = 'Unexpected comma in SQL statement.';
346
+ details.suggestion = 'Remove duplicate or misplaced commas (e.g. trailing commas before FROM, WHERE, or clauses).';
347
+ }
348
+ else if (token === ';') {
349
+ details.cause = 'Premature semicolon or empty statement.';
350
+ details.suggestion = 'Check if a clause before the semicolon is incomplete, or remove extra semicolons.';
351
+ }
352
+ else if (token.toUpperCase() === 'FROM' && /,\s*FROM/i.test(sql)) {
353
+ details.cause = 'Trailing comma before the FROM keyword.';
354
+ details.suggestion = 'Remove the trailing comma right before FROM in your SELECT column list.';
355
+ }
356
+ else if (token.toUpperCase() === 'WHERE' && /,\s*WHERE/i.test(sql)) {
357
+ details.cause = 'Trailing comma before the WHERE keyword.';
358
+ details.suggestion = 'Remove the trailing comma before WHERE (e.g. in UPDATE ... SET col = val, WHERE).';
359
+ }
360
+ else {
361
+ details.cause = `Syntax error near keyword or identifier "${token}".`;
362
+ details.suggestion = `Check the syntax immediately preceding "${token}" for missing keywords, operators, or commas.`;
363
+ }
364
+ }
365
+ const noTableMatch = rawMsg.match(/no such table:\s*([^\s:]+)/i) || rawMsg.match(/relation "([^"]+)" does not exist/i);
366
+ if (noTableMatch) {
367
+ const tableName = noTableMatch[1].replace(/["']/g, '');
368
+ details.token = tableName;
369
+ if (!details.line) {
370
+ const pos = findTokenPosition(sql, tableName);
371
+ if (pos) {
372
+ details.line = pos.line;
373
+ details.column = pos.column;
374
+ }
375
+ }
376
+ details.cause = `Table "${tableName}" does not exist in the database.`;
377
+ let suggestion = `Verify the table name spelling or create the table first.`;
378
+ if (db) {
379
+ try {
380
+ const tablesResult = await db.listTables();
381
+ if (tablesResult.success && tablesResult.data) {
382
+ const knownTables = tablesResult.data.map((t) => t.name);
383
+ const match = findClosestMatch(tableName, knownTables);
384
+ if (match) {
385
+ suggestion = `Did you mean table "${match}"?`;
386
+ }
387
+ }
388
+ }
389
+ catch { }
390
+ }
391
+ details.suggestion = suggestion;
392
+ }
393
+ const tableExistsMatch = rawMsg.match(/table\s+([^\s:]+)\s+already exists/i) || rawMsg.match(/relation "([^"]+)" already exists/i);
394
+ if (tableExistsMatch) {
395
+ const tableName = tableExistsMatch[1].replace(/["']/g, '');
396
+ details.token = tableName;
397
+ for (let l = 0; l < lines.length; l++) {
398
+ const lineText = lines[l];
399
+ const reg = new RegExp(`CREATE\\s+TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?["'\`]?${tableName}["'\`]?`, 'i');
400
+ if (reg.test(lineText)) {
401
+ details.line = l + 1;
402
+ details.column = lineText.toLowerCase().indexOf(tableName.toLowerCase()) + 1;
403
+ break;
404
+ }
405
+ }
406
+ if (!details.line) {
407
+ const pos = findTokenPosition(sql, tableName);
408
+ if (pos) {
409
+ details.line = pos.line;
410
+ details.column = pos.column;
411
+ }
412
+ }
413
+ details.cause = `A table named "${tableName}" already exists in this database.`;
414
+ details.suggestion = `Use "CREATE TABLE IF NOT EXISTS ${tableName} ..." or drop the existing table first using "DROP TABLE IF EXISTS ${tableName};".`;
415
+ }
416
+ const indexExistsMatch = rawMsg.match(/index\s+([^\s:]+)\s+already exists/i);
417
+ if (indexExistsMatch) {
418
+ const indexName = indexExistsMatch[1].replace(/["']/g, '');
419
+ details.token = indexName;
420
+ const pos = findTokenPosition(sql, indexName);
421
+ if (pos) {
422
+ details.line = pos.line;
423
+ details.column = pos.column;
424
+ }
425
+ details.cause = `An index named "${indexName}" already exists in the database.`;
426
+ details.suggestion = `Use "CREATE INDEX IF NOT EXISTS ${indexName} ..." or drop the existing index with "DROP INDEX IF EXISTS ${indexName};".`;
427
+ }
428
+ if (/non-deterministic functions prohibited in generated columns/i.test(rawMsg)) {
429
+ for (let l = 0; l < lines.length; l++) {
430
+ const lineText = lines[l];
431
+ if (/GENERATED\s+ALWAYS\s+AS/i.test(lineText) && /CURRENT_DATE|CURRENT_TIMESTAMP|datetime|random|now/i.test(lineText)) {
432
+ details.line = l + 1;
433
+ details.column = lineText.search(/CURRENT_DATE|CURRENT_TIMESTAMP|datetime|random|now/i) + 1;
434
+ break;
435
+ }
436
+ }
437
+ details.cause = `SQLite does not permit non-deterministic expressions (like CURRENT_DATE, CURRENT_TIMESTAMP, or datetime('now')) in GENERATED (computed) column definitions because their output varies over time.`;
438
+ details.suggestion = `Remove CURRENT_DATE from the GENERATED ALWAYS AS clause, or evaluate active status dynamically in queries (e.g. SELECT (end_date IS NULL OR end_date > CURRENT_DATE) AS is_active).`;
439
+ }
440
+ const noColMatch = rawMsg.match(/no such column:\s*([^\s:]+)/i) || rawMsg.match(/column "([^"]+)" does not exist/i);
441
+ if (noColMatch) {
442
+ const colName = noColMatch[1].replace(/["']/g, '');
443
+ details.token = colName;
444
+ if (!details.line) {
445
+ const pos = findTokenPosition(sql, colName);
446
+ if (pos) {
447
+ details.line = pos.line;
448
+ details.column = pos.column;
449
+ }
450
+ }
451
+ details.cause = `Column "${colName}" was not found in the referenced tables.`;
452
+ details.suggestion = `Check for typos in column name "${colName}", or use single quotes '${colName}' if this was intended as a text string.`;
453
+ }
454
+ const tableHasNoColMatch = rawMsg.match(/table\s+([^\s]+)\s+has no column named\s+([^\s:]+)/i);
455
+ if (tableHasNoColMatch) {
456
+ const targetTable = tableHasNoColMatch[1];
457
+ const targetCol = tableHasNoColMatch[2];
458
+ details.token = targetCol;
459
+ if (!details.line) {
460
+ const pos = findTokenPosition(sql, targetCol);
461
+ if (pos) {
462
+ details.line = pos.line;
463
+ details.column = pos.column;
464
+ }
465
+ }
466
+ details.cause = `Table "${targetTable}" has no column named "${targetCol}".`;
467
+ details.suggestion = `Check the column names defined on "${targetTable}" or alter the table to add "${targetCol}".`;
468
+ }
469
+ if (/incomplete input/i.test(rawMsg)) {
470
+ details.line = lines.length;
471
+ details.column = lines[lines.length - 1].length + 1;
472
+ if (inSingleQuote) {
473
+ details.cause = 'Unclosed single quote string literal.';
474
+ details.suggestion = 'Add a closing single quote (\') to terminate the string literal.';
475
+ }
476
+ else if (openParens > closeParens) {
477
+ details.cause = `Unclosed parenthesis: Found ${openParens} opening '(' but only ${closeParens} closing ')'.`;
478
+ details.suggestion = `Add ${openParens - closeParens} closing ')' at the end of the statement.`;
479
+ }
480
+ else {
481
+ details.cause = 'The SQL statement is incomplete and ended prematurely.';
482
+ details.suggestion = 'Complete the statement by providing the required clause arguments or closing brackets/quotes.';
483
+ }
484
+ }
485
+ const uniqueMatch = rawMsg.match(/UNIQUE constraint failed:\s*(.+)/i) || rawMsg.match(/duplicate key value violates unique constraint\s*"?([^"\s]+)?"?/i);
486
+ if (uniqueMatch) {
487
+ const rawTarget = (uniqueMatch[1] || '').trim();
488
+ const parts = rawTarget.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean);
489
+ const firstPart = parts[0] || '';
490
+ const colName = firstPart.includes('.') ? firstPart.split('.').pop() : firstPart;
491
+ const tableName = firstPart.includes('.') ? firstPart.split('.')[0] : '';
492
+ if (colName && !details.line) {
493
+ const pos = findTokenPosition(sql, colName);
494
+ if (pos) {
495
+ details.line = pos.line;
496
+ details.column = pos.column;
497
+ details.token = colName;
498
+ }
499
+ }
500
+ if (tableName && colName) {
501
+ details.cause = `A UNIQUE constraint on column "${colName}" in table "${tableName}" was violated because one of the rows you are inserting has an email/value that already exists in the database (or is duplicated in your batch).`;
502
+ }
503
+ else {
504
+ details.cause = `A unique constraint was violated (${rawTarget || 'duplicate key'}). A row with these values already exists in the database.`;
505
+ }
506
+ details.suggestion = `• To skip duplicate rows and insert only new records, use "INSERT OR IGNORE INTO ${tableName || 'table'} ..."\n• To overwrite existing records, use "INSERT OR REPLACE INTO ${tableName || 'table'} ..."\n• Or use DELETE FROM ${tableName || 'table'}; to clear previous data before re-inserting.`;
507
+ }
508
+ const fkMatch = rawMsg.match(/FOREIGN KEY constraint failed/i) || rawMsg.match(/violates foreign key constraint/i);
509
+ if (fkMatch) {
510
+ details.cause = 'Foreign key constraint violation: A foreign key column references a parent ID that does not exist in the referenced table.';
511
+ details.suggestion = '• Ensure referenced parent records exist before inserting child records.\n• If you deleted and re-inserted rows in SQLite, AUTOINCREMENT assigns new IDs (e.g. 17+ instead of 1). Either supply explicit IDs in your INSERTs (e.g. INSERT INTO users (id, ...)), or reset sequence counters with "DELETE FROM sqlite_sequence;" before re-seeding.';
512
+ }
513
+ const notNullMatch = rawMsg.match(/NOT NULL constraint failed:\s*(.+)/i) || rawMsg.match(/null value in column "([^"]+)" violates not-null constraint/i);
514
+ if (notNullMatch) {
515
+ const col = notNullMatch[1];
516
+ details.cause = `Column "${col}" has a NOT NULL constraint and cannot be NULL.`;
517
+ details.suggestion = `Provide a non-NULL value for "${col}" or define a DEFAULT value on the column.`;
518
+ }
519
+ const checkMatch = rawMsg.match(/CHECK constraint failed:\s*(.+)/i) || rawMsg.match(/violates check constraint/i);
520
+ if (checkMatch) {
521
+ details.cause = `CHECK constraint condition failed (${checkMatch[1] || 'condition not satisfied'}).`;
522
+ details.suggestion = 'Ensure the inserted/updated values satisfy all CHECK constraint expressions on the table.';
523
+ }
524
+ const ambigMatch = rawMsg.match(/ambiguous column name:\s*(.+)/i);
525
+ if (ambigMatch) {
526
+ const col = ambigMatch[1];
527
+ details.token = col;
528
+ if (!details.line) {
529
+ const pos = findTokenPosition(sql, col);
530
+ if (pos) {
531
+ details.line = pos.line;
532
+ details.column = pos.column;
533
+ }
534
+ }
535
+ details.cause = `Column name "${col}" is ambiguous because it exists in multiple joined tables.`;
536
+ details.suggestion = `Qualify the column with its table or alias name (e.g. "table_name.${col}").`;
537
+ }
538
+ if (!details.line) {
539
+ details.line = 1;
540
+ details.column = 1;
541
+ }
542
+ if (details.line && details.column) {
543
+ details.snippet = buildErrorSnippet(sql, details.line, details.column);
544
+ }
545
+ if (!details.cause) {
546
+ details.cause = 'SQL execution error.';
547
+ details.suggestion = 'Review the SQL syntax and ensure all table names, column names, and clauses are valid.';
548
+ }
549
+ return details;
550
+ }
@@ -1,81 +1 @@
1
- /**
2
- * Pure SQL generators. Kept completely free of any I/O so they are easy to
3
- * unit-test. Both the "run" and the "generate" (get-query) variants of every
4
- * mutating operation share these functions.
5
- */
6
- export declare const SQLITE_DESIGNER_TYPES: readonly ["TEXT", "INTEGER", "REAL", "BLOB", "BOOLEAN", "DATE", "DATETIME", "NUMERIC", "VARCHAR(255)", "JSON"];
7
- export declare const POSTGRES_DESIGNER_TYPES: readonly ["TEXT", "VARCHAR(255)", "INTEGER", "BIGINT", "SMALLINT", "SERIAL", "BIGSERIAL", "REAL", "DECIMAL(10,2)", "NUMERIC", "BOOLEAN", "DATE", "DATETIME", "TIMESTAMP", "TIMESTAMPTZ", "TIME", "INTERVAL", "JSON", "JSONB", "UUID", "BYTEA", "INET"];
8
- export declare const DESIGNER_TYPES: readonly ["TEXT", "VARCHAR(255)", "INTEGER", "BIGINT", "SMALLINT", "SERIAL", "BIGSERIAL", "REAL", "DECIMAL(10,2)", "NUMERIC", "BOOLEAN", "DATE", "DATETIME", "TIMESTAMP", "TIMESTAMPTZ", "TIME", "INTERVAL", "JSON", "JSONB", "UUID", "BYTEA", "BLOB", "INET"];
9
- export declare function getDesignerTypes(dialect?: 'sqlite' | 'postgres'): readonly string[];
10
- export type DesignerColumnType = (typeof DESIGNER_TYPES)[number];
11
- export interface ColumnDef {
12
- name: string;
13
- type: string;
14
- primaryKey?: boolean;
15
- notNull?: boolean;
16
- unique?: boolean;
17
- /** Raw SQL expression used after DEFAULT (e.g. `0`, `'x'`, `CURRENT_TIMESTAMP`). */
18
- defaultValue?: string | null;
19
- foreignKey?: {
20
- table: string;
21
- column: string;
22
- } | null;
23
- }
24
- export interface FieldValue {
25
- column: string;
26
- value: unknown;
27
- }
28
- /** Quote an SQL identifier (table/column name), escaping embedded double quotes. */
29
- export declare function quoteIdentifier(identifier: string): string;
30
- /** Escape a string for use inside a single-quoted SQL literal. */
31
- export declare function escapeString(value: string): string;
32
- /** Render a JS value as a safe SQL literal. */
33
- export declare function sqlValue(value: unknown): string;
34
- export interface MappedType {
35
- sqlType: string;
36
- /** Auto default applied when the user provides none (used for DATE). */
37
- defaultAuto?: string;
38
- }
39
- /**
40
- * Map a designer-friendly or custom SQL type to a real database column type.
41
- * DATE-style types map to a datetime column with a current-timestamp default.
42
- */
43
- export declare function mapColumnType(type: string): MappedType;
44
- export interface RenderOptions {
45
- /** Constrain DDL to what `ALTER TABLE ADD COLUMN` supports. */
46
- forAddColumn?: boolean;
47
- }
48
- /** Format a raw user-provided default value safely for SQL DDL. */
49
- export declare function formatSqlDefault(raw: string, colType?: string): string;
50
- /**
51
- * Render a single column definition.
52
- * When `forAddColumn` is set, enforces SQLite's ADD COLUMN restrictions
53
- * (no PRIMARY KEY / UNIQUE, NOT NULL requires a default, FK requires NULL default).
54
- */
55
- export declare function renderColumnDef(col: ColumnDef, opts?: RenderOptions): string;
56
- /** Build a `CREATE TABLE` statement from structured column definitions. */
57
- export declare function generateCreateTable(tableName: string, columns: ColumnDef[]): string;
58
- /** Build an `ALTER TABLE … ADD COLUMN` statement. */
59
- export declare function generateAddColumn(tableName: string, col: ColumnDef): string;
60
- /** Build an `ALTER TABLE … RENAME TO …` statement. */
61
- export declare function generateRenameTable(tableName: string, newName: string): string;
62
- /** Build an `ALTER TABLE … RENAME COLUMN … TO …` statement. */
63
- export declare function generateRenameColumn(tableName: string, oldName: string, newName: string): string;
64
- /** Build an `ALTER TABLE … DROP COLUMN …` statement. */
65
- export declare function generateDropColumn(tableName: string, column: string): string;
66
- /** Build a `DROP TABLE …` statement. */
67
- export declare function generateDropTable(tableName: string): string;
68
- export interface IndexDef {
69
- /** Optional explicit index name; a default `idx_<table>_<cols>` is generated when omitted. */
70
- name?: string;
71
- columns: string[];
72
- unique?: boolean;
73
- }
74
- /** Build a `CREATE INDEX` (or `CREATE UNIQUE INDEX`) statement. */
75
- export declare function generateCreateIndex(tableName: string, index: IndexDef): string;
76
- /** Build a `DROP INDEX …` statement. */
77
- export declare function generateDropIndex(indexName: string): string;
78
- /** Build an `INSERT` statement from column/value pairs. */
79
- export declare function generateInsert(table: string, fields: FieldValue[]): string;
80
- /** Build an `UPDATE` statement from SET fields and a WHERE clause. */
81
- export declare function generateUpdate(table: string, fields: FieldValue[], where: FieldValue[]): string;
1
+ export declare const SQLITE_DESIGNER_TYPES:readonly ["TEXT","INTEGER","REAL","BLOB","BOOLEAN","DATE","DATETIME","NUMERIC","VARCHAR(255)","JSON"];export declare const POSTGRES_DESIGNER_TYPES:readonly ["TEXT","VARCHAR(255)","INTEGER","BIGINT","SMALLINT","SERIAL","BIGSERIAL","REAL","DECIMAL(10,2)","NUMERIC","BOOLEAN","DATE","DATETIME","TIMESTAMP","TIMESTAMPTZ","TIME","INTERVAL","JSON","JSONB","UUID","BYTEA","INET"];export declare const DESIGNER_TYPES:readonly ["TEXT","VARCHAR(255)","INTEGER","BIGINT","SMALLINT","SERIAL","BIGSERIAL","REAL","DECIMAL(10,2)","NUMERIC","BOOLEAN","DATE","DATETIME","TIMESTAMP","TIMESTAMPTZ","TIME","INTERVAL","JSON","JSONB","UUID","BYTEA","BLOB","INET"];export declare function getDesignerTypes(dialect?:'sqlite'|'postgres'):readonly string[];export type DesignerColumnType=( typeof DESIGNER_TYPES)[number];export interface ColumnDef{name:string;type:string;primaryKey?:boolean;notNull?:boolean;unique?:boolean;defaultValue?:string|null;foreignKey?:{table:string;column:string;}|null;}export interface FieldValue{column:string;value:unknown;}export declare function quoteIdentifier(identifier:string):string;export declare function escapeString(value:string):string;export declare function sqlValue(value:unknown):string;export interface MappedType{sqlType:string;defaultAuto?:string;}export declare function mapColumnType(type:string):MappedType;export interface RenderOptions{forAddColumn?:boolean;}export declare function formatSqlDefault(raw:string,colType?:string):string;export declare function renderColumnDef(col:ColumnDef,opts?:RenderOptions):string;export declare function generateCreateTable(tableName:string,columns:ColumnDef[]):string;export declare function generateAddColumn(tableName:string,col:ColumnDef):string;export declare function generateRenameTable(tableName:string,newName:string):string;export declare function generateRenameColumn(tableName:string,oldName:string,newName:string):string;export declare function generateDropColumn(tableName:string,column:string):string;export declare function generateDropTable(tableName:string):string;export interface IndexDef{name?:string;columns:string[];unique?:boolean;}export declare function generateCreateIndex(tableName:string,index:IndexDef):string;export declare function generateDropIndex(indexName:string):string;export declare function generateInsert(table:string,fields:FieldValue[]):string;export declare function generateUpdate(table:string,fields:FieldValue[],where:FieldValue[]):string;