@vertz/db 0.2.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.
- package/README.md +923 -0
- package/dist/diagnostic/index.d.ts +41 -0
- package/dist/diagnostic/index.js +10 -0
- package/dist/index.d.ts +1346 -0
- package/dist/index.js +2010 -0
- package/dist/internals.d.ts +223 -0
- package/dist/internals.js +25 -0
- package/dist/plugin/index.d.ts +66 -0
- package/dist/plugin/index.js +66 -0
- package/dist/shared/chunk-3f2grpak.js +428 -0
- package/dist/shared/chunk-hrfdj0rr.js +13 -0
- package/dist/shared/chunk-wj026daz.js +86 -0
- package/dist/shared/chunk-xp022dyp.js +296 -0
- package/dist/sql/index.d.ts +213 -0
- package/dist/sql/index.js +64 -0
- package/package.json +72 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import {
|
|
2
|
+
camelToSnake
|
|
3
|
+
} from "./chunk-hrfdj0rr.js";
|
|
4
|
+
|
|
5
|
+
// src/sql/where.ts
|
|
6
|
+
var OPERATOR_KEYS = new Set([
|
|
7
|
+
"eq",
|
|
8
|
+
"ne",
|
|
9
|
+
"gt",
|
|
10
|
+
"gte",
|
|
11
|
+
"lt",
|
|
12
|
+
"lte",
|
|
13
|
+
"in",
|
|
14
|
+
"notIn",
|
|
15
|
+
"contains",
|
|
16
|
+
"startsWith",
|
|
17
|
+
"endsWith",
|
|
18
|
+
"isNull",
|
|
19
|
+
"arrayContains",
|
|
20
|
+
"arrayContainedBy",
|
|
21
|
+
"arrayOverlaps"
|
|
22
|
+
]);
|
|
23
|
+
function isOperatorObject(value) {
|
|
24
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const keys = Object.keys(value);
|
|
28
|
+
return keys.length > 0 && keys.every((k) => OPERATOR_KEYS.has(k));
|
|
29
|
+
}
|
|
30
|
+
function escapeSingleQuotes(str) {
|
|
31
|
+
return str.replace(/'/g, "''");
|
|
32
|
+
}
|
|
33
|
+
function escapeLikeValue(str) {
|
|
34
|
+
return str.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
35
|
+
}
|
|
36
|
+
function resolveColumnRef(key) {
|
|
37
|
+
if (key.includes("->")) {
|
|
38
|
+
const parts = key.split("->");
|
|
39
|
+
const baseCol = parts[0] ?? key;
|
|
40
|
+
const column = `"${camelToSnake(baseCol)}"`;
|
|
41
|
+
const jsonPath = parts.slice(1);
|
|
42
|
+
if (jsonPath.length === 1) {
|
|
43
|
+
return `${column}->>'${escapeSingleQuotes(jsonPath[0] ?? "")}'`;
|
|
44
|
+
}
|
|
45
|
+
const intermediate = jsonPath.slice(0, -1).map((p) => `->'${escapeSingleQuotes(p)}'`).join("");
|
|
46
|
+
const lastKey = jsonPath[jsonPath.length - 1] ?? "";
|
|
47
|
+
const final = `->>'${escapeSingleQuotes(lastKey)}'`;
|
|
48
|
+
return `${column}${intermediate}${final}`;
|
|
49
|
+
}
|
|
50
|
+
return `"${camelToSnake(key)}"`;
|
|
51
|
+
}
|
|
52
|
+
function buildOperatorCondition(columnRef, operators, paramIndex) {
|
|
53
|
+
const clauses = [];
|
|
54
|
+
const params = [];
|
|
55
|
+
let idx = paramIndex;
|
|
56
|
+
if (operators.eq !== undefined) {
|
|
57
|
+
clauses.push(`${columnRef} = $${idx + 1}`);
|
|
58
|
+
params.push(operators.eq);
|
|
59
|
+
idx++;
|
|
60
|
+
}
|
|
61
|
+
if (operators.ne !== undefined) {
|
|
62
|
+
clauses.push(`${columnRef} != $${idx + 1}`);
|
|
63
|
+
params.push(operators.ne);
|
|
64
|
+
idx++;
|
|
65
|
+
}
|
|
66
|
+
if (operators.gt !== undefined) {
|
|
67
|
+
clauses.push(`${columnRef} > $${idx + 1}`);
|
|
68
|
+
params.push(operators.gt);
|
|
69
|
+
idx++;
|
|
70
|
+
}
|
|
71
|
+
if (operators.gte !== undefined) {
|
|
72
|
+
clauses.push(`${columnRef} >= $${idx + 1}`);
|
|
73
|
+
params.push(operators.gte);
|
|
74
|
+
idx++;
|
|
75
|
+
}
|
|
76
|
+
if (operators.lt !== undefined) {
|
|
77
|
+
clauses.push(`${columnRef} < $${idx + 1}`);
|
|
78
|
+
params.push(operators.lt);
|
|
79
|
+
idx++;
|
|
80
|
+
}
|
|
81
|
+
if (operators.lte !== undefined) {
|
|
82
|
+
clauses.push(`${columnRef} <= $${idx + 1}`);
|
|
83
|
+
params.push(operators.lte);
|
|
84
|
+
idx++;
|
|
85
|
+
}
|
|
86
|
+
if (operators.contains !== undefined) {
|
|
87
|
+
clauses.push(`${columnRef} LIKE $${idx + 1}`);
|
|
88
|
+
params.push(`%${escapeLikeValue(operators.contains)}%`);
|
|
89
|
+
idx++;
|
|
90
|
+
}
|
|
91
|
+
if (operators.startsWith !== undefined) {
|
|
92
|
+
clauses.push(`${columnRef} LIKE $${idx + 1}`);
|
|
93
|
+
params.push(`${escapeLikeValue(operators.startsWith)}%`);
|
|
94
|
+
idx++;
|
|
95
|
+
}
|
|
96
|
+
if (operators.endsWith !== undefined) {
|
|
97
|
+
clauses.push(`${columnRef} LIKE $${idx + 1}`);
|
|
98
|
+
params.push(`%${escapeLikeValue(operators.endsWith)}`);
|
|
99
|
+
idx++;
|
|
100
|
+
}
|
|
101
|
+
if (operators.in !== undefined) {
|
|
102
|
+
if (operators.in.length === 0) {
|
|
103
|
+
clauses.push("FALSE");
|
|
104
|
+
} else {
|
|
105
|
+
const placeholders = operators.in.map((_, i) => `$${idx + 1 + i}`).join(", ");
|
|
106
|
+
clauses.push(`${columnRef} IN (${placeholders})`);
|
|
107
|
+
params.push(...operators.in);
|
|
108
|
+
idx += operators.in.length;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (operators.notIn !== undefined) {
|
|
112
|
+
if (operators.notIn.length === 0) {
|
|
113
|
+
clauses.push("TRUE");
|
|
114
|
+
} else {
|
|
115
|
+
const placeholders = operators.notIn.map((_, i) => `$${idx + 1 + i}`).join(", ");
|
|
116
|
+
clauses.push(`${columnRef} NOT IN (${placeholders})`);
|
|
117
|
+
params.push(...operators.notIn);
|
|
118
|
+
idx += operators.notIn.length;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (operators.isNull !== undefined) {
|
|
122
|
+
clauses.push(`${columnRef} ${operators.isNull ? "IS NULL" : "IS NOT NULL"}`);
|
|
123
|
+
}
|
|
124
|
+
if (operators.arrayContains !== undefined) {
|
|
125
|
+
clauses.push(`${columnRef} @> $${idx + 1}`);
|
|
126
|
+
params.push(operators.arrayContains);
|
|
127
|
+
idx++;
|
|
128
|
+
}
|
|
129
|
+
if (operators.arrayContainedBy !== undefined) {
|
|
130
|
+
clauses.push(`${columnRef} <@ $${idx + 1}`);
|
|
131
|
+
params.push(operators.arrayContainedBy);
|
|
132
|
+
idx++;
|
|
133
|
+
}
|
|
134
|
+
if (operators.arrayOverlaps !== undefined) {
|
|
135
|
+
clauses.push(`${columnRef} && $${idx + 1}`);
|
|
136
|
+
params.push(operators.arrayOverlaps);
|
|
137
|
+
idx++;
|
|
138
|
+
}
|
|
139
|
+
return { clauses, params, nextIndex: idx };
|
|
140
|
+
}
|
|
141
|
+
function buildFilterClauses(filter, paramOffset) {
|
|
142
|
+
const clauses = [];
|
|
143
|
+
const allParams = [];
|
|
144
|
+
let idx = paramOffset;
|
|
145
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
146
|
+
if (key === "OR" || key === "AND" || key === "NOT") {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const columnRef = resolveColumnRef(key);
|
|
150
|
+
if (isOperatorObject(value)) {
|
|
151
|
+
const result = buildOperatorCondition(columnRef, value, idx);
|
|
152
|
+
clauses.push(...result.clauses);
|
|
153
|
+
allParams.push(...result.params);
|
|
154
|
+
idx = result.nextIndex;
|
|
155
|
+
} else {
|
|
156
|
+
clauses.push(`${columnRef} = $${idx + 1}`);
|
|
157
|
+
allParams.push(value);
|
|
158
|
+
idx++;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (filter.OR !== undefined) {
|
|
162
|
+
if (filter.OR.length === 0) {
|
|
163
|
+
clauses.push("FALSE");
|
|
164
|
+
} else {
|
|
165
|
+
const orClauses = [];
|
|
166
|
+
for (const subFilter of filter.OR) {
|
|
167
|
+
const sub = buildFilterClauses(subFilter, idx);
|
|
168
|
+
const joined = sub.clauses.join(" AND ");
|
|
169
|
+
orClauses.push(sub.clauses.length > 1 ? `(${joined})` : joined);
|
|
170
|
+
allParams.push(...sub.params);
|
|
171
|
+
idx = sub.nextIndex;
|
|
172
|
+
}
|
|
173
|
+
clauses.push(`(${orClauses.join(" OR ")})`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (filter.AND !== undefined) {
|
|
177
|
+
if (filter.AND.length === 0) {
|
|
178
|
+
clauses.push("TRUE");
|
|
179
|
+
} else {
|
|
180
|
+
const andClauses = [];
|
|
181
|
+
for (const subFilter of filter.AND) {
|
|
182
|
+
const sub = buildFilterClauses(subFilter, idx);
|
|
183
|
+
const joined = sub.clauses.join(" AND ");
|
|
184
|
+
andClauses.push(sub.clauses.length > 1 ? `(${joined})` : joined);
|
|
185
|
+
allParams.push(...sub.params);
|
|
186
|
+
idx = sub.nextIndex;
|
|
187
|
+
}
|
|
188
|
+
clauses.push(`(${andClauses.join(" AND ")})`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (filter.NOT !== undefined) {
|
|
192
|
+
const sub = buildFilterClauses(filter.NOT, idx);
|
|
193
|
+
clauses.push(`NOT (${sub.clauses.join(" AND ")})`);
|
|
194
|
+
allParams.push(...sub.params);
|
|
195
|
+
idx = sub.nextIndex;
|
|
196
|
+
}
|
|
197
|
+
return { clauses, params: allParams, nextIndex: idx };
|
|
198
|
+
}
|
|
199
|
+
function buildWhere(filter, paramOffset = 0) {
|
|
200
|
+
if (!filter || Object.keys(filter).length === 0) {
|
|
201
|
+
return { sql: "", params: [] };
|
|
202
|
+
}
|
|
203
|
+
const { clauses, params } = buildFilterClauses(filter, paramOffset);
|
|
204
|
+
return {
|
|
205
|
+
sql: clauses.join(" AND "),
|
|
206
|
+
params
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/sql/delete.ts
|
|
211
|
+
function buildReturningColumnRef(name) {
|
|
212
|
+
const snakeName = camelToSnake(name);
|
|
213
|
+
if (snakeName === name) {
|
|
214
|
+
return `"${name}"`;
|
|
215
|
+
}
|
|
216
|
+
return `"${snakeName}" AS "${name}"`;
|
|
217
|
+
}
|
|
218
|
+
function buildDelete(options) {
|
|
219
|
+
const allParams = [];
|
|
220
|
+
let sql = `DELETE FROM "${options.table}"`;
|
|
221
|
+
if (options.where) {
|
|
222
|
+
const whereResult = buildWhere(options.where);
|
|
223
|
+
if (whereResult.sql.length > 0) {
|
|
224
|
+
sql += ` WHERE ${whereResult.sql}`;
|
|
225
|
+
allParams.push(...whereResult.params);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (options.returning) {
|
|
229
|
+
if (options.returning === "*") {
|
|
230
|
+
sql += " RETURNING *";
|
|
231
|
+
} else {
|
|
232
|
+
const returnCols = options.returning.map(buildReturningColumnRef).join(", ");
|
|
233
|
+
sql += ` RETURNING ${returnCols}`;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return { sql, params: allParams };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/sql/insert.ts
|
|
240
|
+
function buildReturningColumnRef2(name) {
|
|
241
|
+
const snakeName = camelToSnake(name);
|
|
242
|
+
if (snakeName === name) {
|
|
243
|
+
return `"${name}"`;
|
|
244
|
+
}
|
|
245
|
+
return `"${snakeName}" AS "${name}"`;
|
|
246
|
+
}
|
|
247
|
+
function buildInsert(options) {
|
|
248
|
+
const rows = Array.isArray(options.data) ? options.data : [options.data];
|
|
249
|
+
const firstRow = rows[0];
|
|
250
|
+
if (!firstRow) {
|
|
251
|
+
return { sql: `INSERT INTO "${options.table}" DEFAULT VALUES`, params: [] };
|
|
252
|
+
}
|
|
253
|
+
const keys = Object.keys(firstRow);
|
|
254
|
+
const nowSet = new Set(options.nowColumns ?? []);
|
|
255
|
+
const columns = keys.map((k) => `"${camelToSnake(k)}"`).join(", ");
|
|
256
|
+
const allParams = [];
|
|
257
|
+
const valuesClauses = [];
|
|
258
|
+
for (const row of rows) {
|
|
259
|
+
const placeholders = [];
|
|
260
|
+
for (const key of keys) {
|
|
261
|
+
const value = row[key];
|
|
262
|
+
if (nowSet.has(key) && value === "now") {
|
|
263
|
+
placeholders.push("NOW()");
|
|
264
|
+
} else {
|
|
265
|
+
allParams.push(value);
|
|
266
|
+
placeholders.push(`$${allParams.length}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
valuesClauses.push(`(${placeholders.join(", ")})`);
|
|
270
|
+
}
|
|
271
|
+
let sql = `INSERT INTO "${options.table}" (${columns}) VALUES ${valuesClauses.join(", ")}`;
|
|
272
|
+
if (options.onConflict) {
|
|
273
|
+
const conflictCols = options.onConflict.columns.map((c) => `"${camelToSnake(c)}"`).join(", ");
|
|
274
|
+
if (options.onConflict.action === "nothing") {
|
|
275
|
+
sql += ` ON CONFLICT (${conflictCols}) DO NOTHING`;
|
|
276
|
+
} else if (options.onConflict.action === "update" && options.onConflict.updateColumns) {
|
|
277
|
+
if (options.onConflict.updateValues) {
|
|
278
|
+
const updateVals = options.onConflict.updateValues;
|
|
279
|
+
const setClauses = options.onConflict.updateColumns.map((c) => {
|
|
280
|
+
const snakeCol = camelToSnake(c);
|
|
281
|
+
allParams.push(updateVals[c]);
|
|
282
|
+
return `"${snakeCol}" = $${allParams.length}`;
|
|
283
|
+
}).join(", ");
|
|
284
|
+
sql += ` ON CONFLICT (${conflictCols}) DO UPDATE SET ${setClauses}`;
|
|
285
|
+
} else {
|
|
286
|
+
const setClauses = options.onConflict.updateColumns.map((c) => {
|
|
287
|
+
const snakeCol = camelToSnake(c);
|
|
288
|
+
return `"${snakeCol}" = EXCLUDED."${snakeCol}"`;
|
|
289
|
+
}).join(", ");
|
|
290
|
+
sql += ` ON CONFLICT (${conflictCols}) DO UPDATE SET ${setClauses}`;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (options.returning) {
|
|
295
|
+
if (options.returning === "*") {
|
|
296
|
+
sql += " RETURNING *";
|
|
297
|
+
} else {
|
|
298
|
+
const returnCols = options.returning.map(buildReturningColumnRef2).join(", ");
|
|
299
|
+
sql += ` RETURNING ${returnCols}`;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return { sql, params: allParams };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/sql/select.ts
|
|
306
|
+
function buildColumnRef(name) {
|
|
307
|
+
const snakeName = camelToSnake(name);
|
|
308
|
+
if (snakeName === name) {
|
|
309
|
+
return `"${name}"`;
|
|
310
|
+
}
|
|
311
|
+
return `"${snakeName}" AS "${name}"`;
|
|
312
|
+
}
|
|
313
|
+
function buildSelect(options) {
|
|
314
|
+
const parts = [];
|
|
315
|
+
const allParams = [];
|
|
316
|
+
let columnList;
|
|
317
|
+
if (options.columns && options.columns.length > 0) {
|
|
318
|
+
columnList = options.columns.map(buildColumnRef).join(", ");
|
|
319
|
+
} else {
|
|
320
|
+
columnList = "*";
|
|
321
|
+
}
|
|
322
|
+
if (options.withCount) {
|
|
323
|
+
columnList += ', COUNT(*) OVER() AS "totalCount"';
|
|
324
|
+
}
|
|
325
|
+
parts.push(`SELECT ${columnList} FROM "${options.table}"`);
|
|
326
|
+
const whereClauses = [];
|
|
327
|
+
if (options.where) {
|
|
328
|
+
const whereResult = buildWhere(options.where);
|
|
329
|
+
if (whereResult.sql.length > 0) {
|
|
330
|
+
whereClauses.push(whereResult.sql);
|
|
331
|
+
allParams.push(...whereResult.params);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (options.cursor) {
|
|
335
|
+
const cursorEntries = Object.entries(options.cursor);
|
|
336
|
+
if (cursorEntries.length === 1) {
|
|
337
|
+
const [col, value] = cursorEntries[0];
|
|
338
|
+
const snakeCol = camelToSnake(col);
|
|
339
|
+
const dir = options.orderBy?.[col] ?? "asc";
|
|
340
|
+
const op = dir === "desc" ? "<" : ">";
|
|
341
|
+
allParams.push(value);
|
|
342
|
+
whereClauses.push(`"${snakeCol}" ${op} $${allParams.length}`);
|
|
343
|
+
} else if (cursorEntries.length > 1) {
|
|
344
|
+
const cols = [];
|
|
345
|
+
const placeholders = [];
|
|
346
|
+
const firstCol = cursorEntries[0]?.[0] ?? "";
|
|
347
|
+
const dir = options.orderBy?.[firstCol] ?? "asc";
|
|
348
|
+
const op = dir === "desc" ? "<" : ">";
|
|
349
|
+
for (const [col, value] of cursorEntries) {
|
|
350
|
+
cols.push(`"${camelToSnake(col)}"`);
|
|
351
|
+
allParams.push(value);
|
|
352
|
+
placeholders.push(`$${allParams.length}`);
|
|
353
|
+
}
|
|
354
|
+
whereClauses.push(`(${cols.join(", ")}) ${op} (${placeholders.join(", ")})`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (whereClauses.length > 0) {
|
|
358
|
+
parts.push(`WHERE ${whereClauses.join(" AND ")}`);
|
|
359
|
+
}
|
|
360
|
+
if (options.orderBy) {
|
|
361
|
+
const orderClauses = Object.entries(options.orderBy).map(([col, dir]) => `"${camelToSnake(col)}" ${dir.toUpperCase()}`);
|
|
362
|
+
if (orderClauses.length > 0) {
|
|
363
|
+
parts.push(`ORDER BY ${orderClauses.join(", ")}`);
|
|
364
|
+
}
|
|
365
|
+
} else if (options.cursor) {
|
|
366
|
+
const orderClauses = Object.keys(options.cursor).map((col) => `"${camelToSnake(col)}" ASC`);
|
|
367
|
+
if (orderClauses.length > 0) {
|
|
368
|
+
parts.push(`ORDER BY ${orderClauses.join(", ")}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const effectiveLimit = options.take ?? options.limit;
|
|
372
|
+
if (effectiveLimit !== undefined) {
|
|
373
|
+
allParams.push(effectiveLimit);
|
|
374
|
+
parts.push(`LIMIT $${allParams.length}`);
|
|
375
|
+
}
|
|
376
|
+
if (options.offset !== undefined) {
|
|
377
|
+
allParams.push(options.offset);
|
|
378
|
+
parts.push(`OFFSET $${allParams.length}`);
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
sql: parts.join(" "),
|
|
382
|
+
params: allParams
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// src/sql/update.ts
|
|
387
|
+
function buildReturningColumnRef3(name) {
|
|
388
|
+
const snakeName = camelToSnake(name);
|
|
389
|
+
if (snakeName === name) {
|
|
390
|
+
return `"${name}"`;
|
|
391
|
+
}
|
|
392
|
+
return `"${snakeName}" AS "${name}"`;
|
|
393
|
+
}
|
|
394
|
+
function buildUpdate(options) {
|
|
395
|
+
const keys = Object.keys(options.data);
|
|
396
|
+
const nowSet = new Set(options.nowColumns ?? []);
|
|
397
|
+
const allParams = [];
|
|
398
|
+
const setClauses = [];
|
|
399
|
+
for (const key of keys) {
|
|
400
|
+
const snakeCol = camelToSnake(key);
|
|
401
|
+
const value = options.data[key];
|
|
402
|
+
if (nowSet.has(key) && value === "now") {
|
|
403
|
+
setClauses.push(`"${snakeCol}" = NOW()`);
|
|
404
|
+
} else {
|
|
405
|
+
allParams.push(value);
|
|
406
|
+
setClauses.push(`"${snakeCol}" = $${allParams.length}`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
let sql = `UPDATE "${options.table}" SET ${setClauses.join(", ")}`;
|
|
410
|
+
if (options.where) {
|
|
411
|
+
const whereResult = buildWhere(options.where, allParams.length);
|
|
412
|
+
if (whereResult.sql.length > 0) {
|
|
413
|
+
sql += ` WHERE ${whereResult.sql}`;
|
|
414
|
+
allParams.push(...whereResult.params);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (options.returning) {
|
|
418
|
+
if (options.returning === "*") {
|
|
419
|
+
sql += " RETURNING *";
|
|
420
|
+
} else {
|
|
421
|
+
const returnCols = options.returning.map(buildReturningColumnRef3).join(", ");
|
|
422
|
+
sql += ` RETURNING ${returnCols}`;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return { sql, params: allParams };
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export { buildWhere, buildDelete, buildInsert, buildSelect, buildUpdate };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/sql/casing.ts
|
|
2
|
+
function camelToSnake(str) {
|
|
3
|
+
if (str.length === 0)
|
|
4
|
+
return str;
|
|
5
|
+
return str.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").toLowerCase();
|
|
6
|
+
}
|
|
7
|
+
function snakeToCamel(str) {
|
|
8
|
+
if (str.length === 0)
|
|
9
|
+
return str;
|
|
10
|
+
return str.replace(/([a-zA-Z\d])_([a-zA-Z])/g, (_, prev, char) => prev + char.toUpperCase());
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { camelToSnake, snakeToCamel };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// src/diagnostic/index.ts
|
|
2
|
+
var ERROR_PATTERNS = [
|
|
3
|
+
{
|
|
4
|
+
pattern: /Column '([^']+)' does not exist on table '([^']+)'/,
|
|
5
|
+
code: "INVALID_COLUMN",
|
|
6
|
+
explanation: "The column name used in a select, where, or orderBy clause does not exist on the specified table.",
|
|
7
|
+
suggestion: "Check the table schema definition for available column names. Column names are camelCase in TypeScript."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
pattern: /Relation '([^']+)' does not exist/,
|
|
11
|
+
code: "INVALID_RELATION",
|
|
12
|
+
explanation: "The relation name used in an include clause does not match any declared relation on the table entry.",
|
|
13
|
+
suggestion: "Check the relations record passed to the table entry in your database registry."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
pattern: /Filter on '([^']+)' expects type '([^']+)', got '([^']+)'/,
|
|
17
|
+
code: "INVALID_FILTER_TYPE",
|
|
18
|
+
explanation: "The value passed to a where filter does not match the column type.",
|
|
19
|
+
suggestion: "Ensure the filter value matches the column type. For example, use a number for integer columns."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
pattern: /Cannot combine 'not' with explicit field selection/,
|
|
23
|
+
code: "MIXED_SELECT",
|
|
24
|
+
explanation: 'The select option uses both `not: "sensitive"` and explicit field selection like `{ id: true }`, which are mutually exclusive.',
|
|
25
|
+
suggestion: 'Use either `{ not: "sensitive" }` OR `{ id: true, name: true }`, not both.'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
pattern: /Unique constraint violated on ([^.]+)\.([^\s(]+)/,
|
|
29
|
+
code: "UNIQUE_VIOLATION",
|
|
30
|
+
explanation: "An INSERT or UPDATE tried to set a value that already exists in a unique column.",
|
|
31
|
+
suggestion: "Check the violating value and either use a different value or use upsert() to handle conflicts."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
pattern: /Foreign key constraint "([^"]+)" violated on table ([^\s]+)/,
|
|
35
|
+
code: "FK_VIOLATION",
|
|
36
|
+
explanation: "An INSERT or UPDATE references a row in another table that does not exist.",
|
|
37
|
+
suggestion: "Ensure the referenced row exists before creating the dependent row."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
pattern: /Not-null constraint violated on ([^.]+)\.([^\s]+)/,
|
|
41
|
+
code: "NOT_NULL_VIOLATION",
|
|
42
|
+
explanation: "An INSERT or UPDATE tried to set a NOT NULL column to null.",
|
|
43
|
+
suggestion: "Provide a non-null value for the column, or make the column nullable in the schema with `.nullable()`."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
pattern: /Record not found in table ([^\s]+)/,
|
|
47
|
+
code: "NOT_FOUND",
|
|
48
|
+
explanation: "A getOrThrow, update, or delete query did not match any rows.",
|
|
49
|
+
suggestion: "Verify the where clause matches existing rows. Use get() if the record may not exist."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
pattern: /Table "([^"]+)" is not registered in the database/,
|
|
53
|
+
code: "UNREGISTERED_TABLE",
|
|
54
|
+
explanation: "The table name passed to a query method is not in the database registry.",
|
|
55
|
+
suggestion: "Register the table in the `tables` record passed to `createDb()`."
|
|
56
|
+
}
|
|
57
|
+
];
|
|
58
|
+
function diagnoseError(message) {
|
|
59
|
+
for (const entry of ERROR_PATTERNS) {
|
|
60
|
+
if (entry.pattern.test(message)) {
|
|
61
|
+
return {
|
|
62
|
+
code: entry.code,
|
|
63
|
+
explanation: entry.explanation,
|
|
64
|
+
suggestion: entry.suggestion
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
function formatDiagnostic(diag) {
|
|
71
|
+
return [
|
|
72
|
+
`[${diag.code}]`,
|
|
73
|
+
` Explanation: ${diag.explanation}`,
|
|
74
|
+
` Suggestion: ${diag.suggestion}`
|
|
75
|
+
].join(`
|
|
76
|
+
`);
|
|
77
|
+
}
|
|
78
|
+
function explainError(message) {
|
|
79
|
+
const diag = diagnoseError(message);
|
|
80
|
+
if (!diag) {
|
|
81
|
+
return `[UNKNOWN] No diagnostic available for: ${message}`;
|
|
82
|
+
}
|
|
83
|
+
return formatDiagnostic(diag);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { diagnoseError, formatDiagnostic, explainError };
|