@andymic/pigeon 1.6.0 → 2.0.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/package.json +2 -2
- package/src/index.js +268 -266
- package/src/pgAdmin.js +1 -1
- package/src/utils.js +0 -54
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andymic/pigeon",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"author": "Andreas Michael <ateasm03@gmail.com>",
|
|
5
5
|
"description": "Pigeon is a TypeScript-based tool for generating TypeScript classes and methods from PostgreSQL database schemas.",
|
|
6
6
|
"keywords": [
|
|
@@ -35,5 +35,5 @@
|
|
|
35
35
|
"pg": "^8.16.0",
|
|
36
36
|
"prompt-sync": "^4.2.0"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "85641ef9cc7fe7b42c78f7470608793f192b880a"
|
|
39
39
|
}
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { arrayMaker,
|
|
1
|
+
import { arrayMaker, getJSType, getPGType, nameBeautifier, runQuery, singularize, sleep, } from "./utils.js";
|
|
2
2
|
import prompt from "prompt-sync";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import * as path from "node:path";
|
|
@@ -268,10 +268,25 @@ export function runGeneration(dir, db, tables, enums) {
|
|
|
268
268
|
const dirResult = createDir(path.join(dir, schema));
|
|
269
269
|
if (dirResult instanceof PigeonError)
|
|
270
270
|
return dirResult;
|
|
271
|
+
fs.writeFileSync(path.join(dir, schema, "index.ts"), createIndex(db));
|
|
271
272
|
}
|
|
272
273
|
for (const table of tables) {
|
|
273
|
-
let ts =
|
|
274
|
-
|
|
274
|
+
let ts = "";
|
|
275
|
+
let hasForeign = false;
|
|
276
|
+
for (const column of table.columns) {
|
|
277
|
+
if (column.isForeign && column.foreignColumn && column.foreignTable && column.foreignSchema) {
|
|
278
|
+
hasForeign = true;
|
|
279
|
+
if ((table.schema === column.foreignSchema) && (table.name === column.foreignTable))
|
|
280
|
+
continue;
|
|
281
|
+
ts += "import {get" + nameBeautifier(column.foreignTable).replaceAll(" ", "") + "} from \".";
|
|
282
|
+
if (table.schema !== column.foreignSchema)
|
|
283
|
+
ts += "./" + column.foreignSchema;
|
|
284
|
+
ts += "/" + column.foreignTable + ".js\";\n";
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (hasForeign)
|
|
288
|
+
ts += "\n";
|
|
289
|
+
ts += "import query from \"./index.js\";\n\n";
|
|
275
290
|
if (enums) {
|
|
276
291
|
for (const cEnum of enums) {
|
|
277
292
|
for (const column of table.columns) {
|
|
@@ -292,85 +307,75 @@ export function runGeneration(dir, db, tables, enums) {
|
|
|
292
307
|
}
|
|
293
308
|
ts += createClass(table.name, table.columns);
|
|
294
309
|
ts += "\n\n";
|
|
295
|
-
ts +=
|
|
310
|
+
ts += createGetTypecast(table.columns);
|
|
311
|
+
ts += "\n\n";
|
|
312
|
+
ts += createGet(table.schema, table.name, table.columns);
|
|
296
313
|
ts += "\n\n";
|
|
297
314
|
let keys = [];
|
|
298
315
|
for (const column of table.columns)
|
|
299
316
|
if (column.isPrimary || column.isForeign || column.isUnique)
|
|
300
317
|
keys.push(column);
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
let nonDefaults = [];
|
|
306
|
-
let softDefaults = [];
|
|
307
|
-
let hardDefaults = [];
|
|
318
|
+
let required = [];
|
|
319
|
+
let optional = [];
|
|
320
|
+
let autoGenerated = [];
|
|
308
321
|
for (const column of table.columns) {
|
|
309
322
|
if (column.defaultValue === null && !column.isIdentity)
|
|
310
|
-
|
|
323
|
+
required.push(column);
|
|
311
324
|
else if ((column.defaultValue !== null && !column.defaultValue.includes("nextval")) || (column.isIdentity && column.identityGeneration === "BY DEFAULT"))
|
|
312
|
-
|
|
325
|
+
optional.push(column);
|
|
313
326
|
else if ((column.defaultValue !== null && column.defaultValue.includes("nextval")) || (column.isIdentity && column.identityGeneration === "ALWAYS"))
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
ts += createAdd(table.schema, table.name, nonDefaults, [], hardDefaults.concat(softDefaults)) + "\n\n";
|
|
317
|
-
for (const softCombination of getCombinations(softDefaults))
|
|
318
|
-
ts += createAdd(table.schema, table.name, nonDefaults, softCombination, hardDefaults.concat(softDefaults.filter(n => !getCombinations(softDefaults).includes([n])))) + "\n\n";
|
|
319
|
-
for (const keyCombination of getCombinations(keys)) {
|
|
320
|
-
ts += createUpdate(table.schema, table.name, table.columns, keyCombination);
|
|
321
|
-
ts += "\n\n";
|
|
322
|
-
}
|
|
323
|
-
for (const column of table.columns) {
|
|
324
|
-
ts += createDelete(table.schema, table.name, column, table.columns);
|
|
325
|
-
ts += "\n\n";
|
|
327
|
+
autoGenerated.push(column);
|
|
326
328
|
}
|
|
327
|
-
ts
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
for (const match of matches) {
|
|
333
|
-
ts = ts.slice(0, match.index - charOffset) + ts.slice(match.index - charOffset + match[0].length);
|
|
334
|
-
charOffset += match[0].length;
|
|
335
|
-
let fileExists = false;
|
|
336
|
-
const isBrackets = match[1][0] === "{";
|
|
337
|
-
for (const object of importObjects) {
|
|
338
|
-
if (object.file === match[2] && isBrackets === object.brackets) {
|
|
339
|
-
fileExists = true;
|
|
340
|
-
object.functions.push(match[1]);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
if (!fileExists) {
|
|
344
|
-
importObjects.push({
|
|
345
|
-
file: match[2],
|
|
346
|
-
functions: [match[1]],
|
|
347
|
-
brackets: isBrackets
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
let importString = "";
|
|
352
|
-
for (const object of importObjects) {
|
|
353
|
-
object.functions = [...new Set(object.functions)];
|
|
354
|
-
importString += "import ";
|
|
355
|
-
if (object.brackets)
|
|
356
|
-
importString += "{";
|
|
357
|
-
for (const fun of object.functions) {
|
|
358
|
-
if (object.brackets)
|
|
359
|
-
importString += fun.slice(1, -1) + ", ";
|
|
360
|
-
else
|
|
361
|
-
importString += fun + ", ";
|
|
362
|
-
}
|
|
363
|
-
importString = importString.slice(0, -2);
|
|
364
|
-
if (object.brackets)
|
|
365
|
-
importString += "}";
|
|
366
|
-
importString += " from \"" + object.file + "\";\n";
|
|
367
|
-
}
|
|
368
|
-
importString += "import pg from \"pg\";\n\n";
|
|
369
|
-
importString += "const {Client} = pg;\n\n";
|
|
370
|
-
ts = importString + ts;
|
|
329
|
+
ts += createAdd(table.schema, table.name, required, optional, autoGenerated, hasForeign);
|
|
330
|
+
ts += "\n\n";
|
|
331
|
+
ts += createUpdate(table.schema, table.name, table.columns, autoGenerated, hasForeign);
|
|
332
|
+
ts += "\n\n";
|
|
333
|
+
ts += createDelete(table.schema, table.name, table.columns);
|
|
371
334
|
fs.writeFileSync(path.join(dir, table.schema, table.name + ".ts"), ts);
|
|
372
335
|
}
|
|
373
336
|
}
|
|
337
|
+
function createIndex(db) {
|
|
338
|
+
let text = "";
|
|
339
|
+
text += "import pg from \"pg\";\n";
|
|
340
|
+
text += "const { Client, Pool } = pg;\n";
|
|
341
|
+
text += "\n";
|
|
342
|
+
text += "const pool = new Pool({\n";
|
|
343
|
+
text += "\thost: \"" + db.host + "\",\n";
|
|
344
|
+
text += "\tport: " + db.port + ",\n";
|
|
345
|
+
text += "\tdatabase: \"" + db.db + "\",\n";
|
|
346
|
+
text += "\tuser: \"" + db.user + "\",\n";
|
|
347
|
+
text += "\tpassword: \"" + db.pass + "\",\n";
|
|
348
|
+
text += "\tmax: 20,\n";
|
|
349
|
+
text += "\tidleTimeoutMillis: 30000,\n";
|
|
350
|
+
text += "\tconnectionTimeoutMillis: 2000\n";
|
|
351
|
+
text += "});\n";
|
|
352
|
+
text += "\n";
|
|
353
|
+
text += "const client = new Client({\n";
|
|
354
|
+
text += "\thost: \"" + db.host + "\",\n";
|
|
355
|
+
text += "\tport: " + db.port + ",\n";
|
|
356
|
+
text += "\tdatabase: \"" + db.db + "\",\n";
|
|
357
|
+
text += "\tuser: \"" + db.user + "\",\n";
|
|
358
|
+
text += "\tpassword: \"" + db.pass + "\"\n";
|
|
359
|
+
text += "});\n";
|
|
360
|
+
text += "\n";
|
|
361
|
+
text += "/*\n";
|
|
362
|
+
text += "export default (sql: string, params: any[]) => pool.query(sql, params);\n";
|
|
363
|
+
text += "*/\n";
|
|
364
|
+
text += "\n";
|
|
365
|
+
text += "/*\n";
|
|
366
|
+
text += "export default async (sql: string, params: any[]) => {\n";
|
|
367
|
+
text += "\ttry {\n";
|
|
368
|
+
text += "\t\tawait client.connect();\n";
|
|
369
|
+
text += "\t\treturn await client.query(sql, params);\n";
|
|
370
|
+
text += "\t} catch (error: any) {\n";
|
|
371
|
+
text += "\t\tthrow error;\n";
|
|
372
|
+
text += "\t} finally {\n";
|
|
373
|
+
text += "\t\tawait client.end();\n";
|
|
374
|
+
text += "\t}\n";
|
|
375
|
+
text += "}\n";
|
|
376
|
+
text += "*/";
|
|
377
|
+
return text;
|
|
378
|
+
}
|
|
374
379
|
function createClass(tableName, columns) {
|
|
375
380
|
let text = "";
|
|
376
381
|
text += "export class " + singularize(nameBeautifier(tableName)).replaceAll(" ", "") + " {\n";
|
|
@@ -439,157 +444,153 @@ function createClass(tableName, columns) {
|
|
|
439
444
|
text += "}";
|
|
440
445
|
return text;
|
|
441
446
|
}
|
|
442
|
-
|
|
443
|
-
let text = "";
|
|
444
|
-
text += tabsInserter(baseTabs) + "const client = new Client({\n";
|
|
445
|
-
text += tabsInserter(baseTabs + 1) + "host: \"" + db.host + "\",\n";
|
|
446
|
-
text += tabsInserter(baseTabs + 1) + "port: " + db.port + ",\n";
|
|
447
|
-
text += tabsInserter(baseTabs + 1) + "database: \"" + db.db + "\",\n";
|
|
448
|
-
text += tabsInserter(baseTabs + 1) + "user: \"" + db.user + "\",\n";
|
|
449
|
-
text += tabsInserter(baseTabs + 1) + "password: \"" + db.pass + "\"\n";
|
|
450
|
-
text += tabsInserter(baseTabs) + "});";
|
|
451
|
-
return text;
|
|
452
|
-
}
|
|
453
|
-
function createGetAll(tableSchema, tableName, columns) {
|
|
447
|
+
function createGetTypecast(columns) {
|
|
454
448
|
let text = "";
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
text += "\
|
|
465
|
-
text +=
|
|
466
|
-
text += "\treturn " + varName + ";\n";
|
|
449
|
+
text += "function getTypecast(columnName: string): string {\n";
|
|
450
|
+
text += "\tswitch (columnName) {\n";
|
|
451
|
+
const typeColumns = columns.toSorted((a, b) => a.pgType.localeCompare(b.pgType));
|
|
452
|
+
for (let i = 0; i < typeColumns.length; i++) {
|
|
453
|
+
text += "\t\tcase \"" + typeColumns[i].name + "\":\n";
|
|
454
|
+
if (i !== typeColumns.length - 1 && typeColumns[i].pgType === typeColumns[i + 1].pgType)
|
|
455
|
+
continue;
|
|
456
|
+
text += "\t\t\treturn \"::" + typeColumns[i].pgType + "\";\n";
|
|
457
|
+
}
|
|
458
|
+
text += "\t}\n";
|
|
459
|
+
text += "\treturn \"\";\n";
|
|
467
460
|
text += "}";
|
|
468
461
|
return text;
|
|
469
462
|
}
|
|
470
|
-
function createGet(tableSchema, tableName, columns
|
|
463
|
+
function createGet(tableSchema, tableName, columns) {
|
|
471
464
|
let text = "";
|
|
472
465
|
const className = singularize(nameBeautifier(tableName)).replaceAll(" ", "");
|
|
473
466
|
const varName = nameBeautifier(tableName).replaceAll(" ", "")[0].toLowerCase() + nameBeautifier(tableName).replaceAll(" ", "").substring(1);
|
|
467
|
+
const functionName = "get" + nameBeautifier(tableName).replaceAll(" ", "");
|
|
474
468
|
text += "/**\n";
|
|
475
|
-
text += " *
|
|
476
|
-
for (const key of keys)
|
|
477
|
-
text += key.name + " and ";
|
|
478
|
-
text = text.slice(0, -5) + ".\n";
|
|
469
|
+
text += " * Finds and retrieves " + singularize(nameBeautifier(tableName)) + " objects from the database based on specified criteria.\n";
|
|
479
470
|
text += " *\n";
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
text += " * @
|
|
471
|
+
text += " * @async\n";
|
|
472
|
+
text += " * @function " + functionName + "\n";
|
|
473
|
+
text += " * @param {Partial<" + className + ">} criteria - An object containing properties of the " + className + " to filter by.\n";
|
|
474
|
+
text += " * Only " + nameBeautifier(tableName).toLowerCase() + " matching all provided criteria will be returned.\n";
|
|
475
|
+
text += " * An empty object or undefined criteria will fetch all " + className + " objects.\n";
|
|
476
|
+
text += " * @returns {Promise<" + className + "[]>} - A Promise object returning an array of " + nameBeautifier(tableName) + " matching the criteria.\n";
|
|
483
477
|
text += " */\n";
|
|
484
|
-
text += "export async function get" + nameBeautifier(tableName).replaceAll(" ", "") + "
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
text =
|
|
488
|
-
text += "
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
text
|
|
492
|
-
text += "
|
|
493
|
-
text += "\
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
text
|
|
497
|
-
text += "
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
}
|
|
504
|
-
query = query.slice(0, -5) + ";";
|
|
505
|
-
parameters = parameters.slice(0, -2);
|
|
506
|
-
text += queryMaker(1, varName, query, parameters);
|
|
507
|
-
text += "\n\n";
|
|
478
|
+
text += "export async function get" + nameBeautifier(tableName).replaceAll(" ", "") + "(criteria: Partial<" + className + ">): Promise<" + className + "[]> {\n";
|
|
479
|
+
text += "\tconst whereClauses: string[] = [];\n";
|
|
480
|
+
text += "\tconst values: any[] = [];\n";
|
|
481
|
+
text += "\tlet paramIndex = 1;\n";
|
|
482
|
+
text += "\n";
|
|
483
|
+
text += "\tfor (const key in criteria) {\n";
|
|
484
|
+
text += "\t\tif (Object.prototype.hasOwnProperty.call(criteria, key) && criteria[key as keyof typeof criteria] !== undefined) {\n";
|
|
485
|
+
text += "\t\t\twhereClauses.push(\"\\\"\" + key + \"\\\" = $\" + paramIndex + getTypecast(key));\n";
|
|
486
|
+
text += "\t\t\tvalues.push(criteria[key as keyof typeof criteria]);\n";
|
|
487
|
+
text += "\t\t\tparamIndex++;\n";
|
|
488
|
+
text += "\t\t}\n";
|
|
489
|
+
text += "\t}\n";
|
|
490
|
+
text += "\n";
|
|
491
|
+
text += "\tlet sql = \"SELECT * FROM " + tableSchema + "." + tableName + "\";\n";
|
|
492
|
+
text += "\tif (whereClauses.length > 0)\n";
|
|
493
|
+
text += "\t\tsql += \" WHERE \" + whereClauses.join(\" AND \");\n";
|
|
494
|
+
text += "\tsql += \";\";\n";
|
|
495
|
+
text += "\n";
|
|
496
|
+
text += "\tconst " + varName + "Query = await query(sql, values);\n";
|
|
508
497
|
text += arrayMaker(1, varName, className, columns) + "\n";
|
|
509
498
|
text += "\treturn " + varName + ";\n";
|
|
510
499
|
text += "}";
|
|
511
500
|
return text;
|
|
512
501
|
}
|
|
513
|
-
function createAdd(tableSchema, tableName,
|
|
502
|
+
function createAdd(tableSchema, tableName, required, optional, autoGenerated, hasForeign) {
|
|
514
503
|
let text = "";
|
|
515
504
|
const className = singularize(nameBeautifier(tableName)).replaceAll(" ", "");
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
text += "./" + column.foreignSchema;
|
|
525
|
-
text += "/" + column.foreignTable + ".js\";\n";
|
|
526
|
-
}
|
|
527
|
-
}
|
|
505
|
+
const paramName = className.slice(0, 1).toLowerCase() + className.slice(1);
|
|
506
|
+
const functionName = "add" + singularize(nameBeautifier(tableName).replaceAll(" ", ""));
|
|
507
|
+
const dataType = "Omit<" + className + ", '" + autoGenerated.concat(optional).map((column) => {
|
|
508
|
+
return column.name;
|
|
509
|
+
}).join("\' | \'") + "'> & Partial<Pick<" + className + ", '" + optional.map((column) => {
|
|
510
|
+
return column.name;
|
|
511
|
+
}).join("\' | \'") + "'>>";
|
|
512
|
+
const editableColumns = required.concat(optional);
|
|
528
513
|
text += "/**\n";
|
|
529
|
-
text += " * Adds
|
|
514
|
+
text += " * Adds a new " + singularize(nameBeautifier(tableName)) + " object to the database.\n";
|
|
515
|
+
if (autoGenerated.length !== 0)
|
|
516
|
+
text += " * The field" + (autoGenerated.length > 1 ? "s" : "") + " `" + autoGenerated.map((column) => {
|
|
517
|
+
return column.name;
|
|
518
|
+
}).join("\`, \`") + "` " + (autoGenerated.length > 1 ? "are" : "is") + " auto-generated.\n";
|
|
519
|
+
if (optional.length !== 0)
|
|
520
|
+
text += " * The field" + (optional.length > 1 ? "s" : "") + " `" + optional.map((column) => {
|
|
521
|
+
return column.name;
|
|
522
|
+
}).join("`, `") + "` " + (optional.length > 1 ? "are" : "is") + " optional and will use database defaults if not provided.\n";
|
|
530
523
|
text += " *\n";
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
for
|
|
534
|
-
|
|
535
|
-
|
|
524
|
+
text += " * @async\n";
|
|
525
|
+
text += " * @function " + functionName + "\n";
|
|
526
|
+
text += " * @param {" + dataType + "} " + paramName + " - An object containing the data for the new " + className + ".\n";
|
|
527
|
+
if (required.length !== 0)
|
|
528
|
+
text += " * - The " + (required.length > 1 ? "properties" : "property") + " from `" + className + "\` like \`" + required.map((column) => {
|
|
529
|
+
return column.name;
|
|
530
|
+
}).join("`, `") + "` " + (required.length > 1 ? "are" : "is") + " required.\n";
|
|
531
|
+
const nullable = editableColumns.filter((column) => column.isNullable);
|
|
532
|
+
if (nullable.length !== 0)
|
|
533
|
+
text += " * - The nullable " + (nullable.length > 1 ? "fields" : "field") + " like \`" + nullable.map((column) => {
|
|
534
|
+
return column.name;
|
|
535
|
+
}).join("`, `") + "` can be provided as `null` or " + (nullable.length > 1 ? "their" : "its") + " respective type.\n";
|
|
536
|
+
if (optional.length !== 0)
|
|
537
|
+
text += " * - The field" + (optional.length > 1 ? "s" : "") + " `" + optional.map((column) => {
|
|
538
|
+
return column.name;
|
|
539
|
+
}).join("`, `") + "` " + (optional.length > 1 ? "are" : "is") + " optional.\n";
|
|
540
|
+
text += " * @returns {Promise<" + className + ">} A Promise returning the newly created " + className + " object.\n";
|
|
536
541
|
if (hasForeign) {
|
|
537
|
-
text += " * @throws
|
|
538
|
-
for (const column of
|
|
542
|
+
text += " * @throws {Error} An exception in the case of the ";
|
|
543
|
+
for (const column of editableColumns)
|
|
539
544
|
if (column.isForeign)
|
|
540
545
|
text += nameBeautifier(column.name) + " or the ";
|
|
541
546
|
text = text.slice(0, -8);
|
|
542
|
-
text += " not existing in their table.\n";
|
|
547
|
+
text += " not existing in their table, or if other pre-insertion validation fails.\n";
|
|
543
548
|
}
|
|
544
549
|
text += " */\n";
|
|
545
|
-
text += "export async function
|
|
546
|
-
if (softDefaults.length > 0) {
|
|
547
|
-
text += "With";
|
|
548
|
-
for (const softDefault of softDefaults)
|
|
549
|
-
text += nameBeautifier(softDefault.name).replaceAll(" ", "") + "And";
|
|
550
|
-
text = text.slice(0, -3);
|
|
551
|
-
}
|
|
552
|
-
text += "(";
|
|
553
|
-
for (const column of columns)
|
|
554
|
-
text += column.name + ": " + column.jsType + ", ";
|
|
555
|
-
text = text.slice(0, -2);
|
|
556
|
-
text += "): Promise<" + className + "> {\n";
|
|
550
|
+
text += "export async function " + functionName + "( " + paramName + ": " + dataType.replaceAll("'", "\"") + "): Promise<" + className + "> {\n";
|
|
557
551
|
if (hasForeign) {
|
|
558
|
-
for (const column of
|
|
552
|
+
for (const column of editableColumns) {
|
|
559
553
|
if (column.isForeign && column.foreignColumn && column.foreignTable && column.foreignSchema) {
|
|
560
554
|
const name = nameBeautifier(column.name).replaceAll(" ", "");
|
|
561
555
|
if (column.isNullable) {
|
|
562
|
-
text += "\tif (" + column.name + ") {\n";
|
|
563
|
-
text += "\t\tconst verify" + name + " = await get" + nameBeautifier(column.foreignTable).replaceAll(" ", "") + "
|
|
556
|
+
text += "\tif (" + paramName + "." + column.name + " !== undefined && " + paramName + "." + column.name + " !== null) {\n";
|
|
557
|
+
text += "\t\tconst verify" + name + " = await get" + nameBeautifier(column.foreignTable).replaceAll(" ", "") + "({" + column.foreignColumn + ": " + paramName + "." + column.name + "});\n";
|
|
564
558
|
text += "\t\tif (verify" + name + ".length === 0)\n";
|
|
565
|
-
text += "\t\t\tthrow \"The " + nameBeautifier(column.name) + " provided does not exist.\";\n";
|
|
559
|
+
text += "\t\t\tthrow new Error(\"The " + nameBeautifier(column.name) + " provided does not exist.\");\n";
|
|
566
560
|
text += "\t}\n\n";
|
|
567
561
|
}
|
|
568
562
|
else {
|
|
569
|
-
text += "\tconst verify" + name + " = await get" + nameBeautifier(column.foreignTable).replaceAll(" ", "") + "
|
|
563
|
+
text += "\tconst verify" + name + " = await get" + nameBeautifier(column.foreignTable).replaceAll(" ", "") + "({" + column.foreignColumn + ": " + paramName + "." + column.name + "});\n";
|
|
570
564
|
text += "\tif (verify" + name + ".length === 0)\n";
|
|
571
|
-
text += "\t\tthrow \"The " + nameBeautifier(column.name) + " provided does not exist.\";\n\n";
|
|
565
|
+
text += "\t\t\tthrow new Error(\"The " + nameBeautifier(column.name) + " provided does not exist.\");\n\n";
|
|
572
566
|
}
|
|
573
567
|
}
|
|
574
568
|
}
|
|
575
569
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
text +=
|
|
590
|
-
text += "\
|
|
570
|
+
text += "\tconst intoClauses: string[] = [];\n";
|
|
571
|
+
text += "\tconst valuesClauses: string[] = [];\n";
|
|
572
|
+
text += "\tconst values: any[] = [];\n";
|
|
573
|
+
text += "\tlet paramIndex = 1;\n";
|
|
574
|
+
text += "\n";
|
|
575
|
+
text += "\tfor (const key in " + paramName + ") {\n";
|
|
576
|
+
text += "\t\tif (Object.prototype.hasOwnProperty.call(" + paramName + ", key)) {\n";
|
|
577
|
+
text += "\t\t\tconst value = " + paramName + "[key as keyof typeof " + paramName + "];\n";
|
|
578
|
+
text += "\t\t\tif (value !== undefined) {\n";
|
|
579
|
+
text += "\t\t\t\tintoClauses.push(\"\\\"\"+ key + \"\\\"\");\n";
|
|
580
|
+
text += "\t\t\t\tvalues.push(value);\n";
|
|
581
|
+
text += "\t\t\t\tvaluesClauses.push(\"$\" + paramIndex + getTypecast(key));\n";
|
|
582
|
+
text += "\t\t\t\tparamIndex++;\n";
|
|
583
|
+
text += "\t\t\t}\n";
|
|
584
|
+
text += "\t\t}\n";
|
|
585
|
+
text += "\t}\n";
|
|
586
|
+
text += "\n";
|
|
587
|
+
text += "\tif (intoClauses.length === 0)\n";
|
|
588
|
+
text += "\t\tthrow new Error(\"No data provided for " + paramName + " creation.\");\n";
|
|
589
|
+
text += "\n";
|
|
590
|
+
text += "\tconst sql = \"INSERT INTO " + tableSchema + "." + tableName + " (\" + intoClauses.join(\", \") + \") VALUES (\" + valuesClauses.join(\", \") + \") RETURNING *;\";\n";
|
|
591
|
+
text += "\tconst insertQuery = await query(sql, values);\n";
|
|
591
592
|
text += "\treturn new " + className + "(\n";
|
|
592
|
-
columns =
|
|
593
|
+
let columns = editableColumns.concat(autoGenerated);
|
|
593
594
|
columns = [...new Set(columns)];
|
|
594
595
|
columns.sort((a, b) => a.position - b.position);
|
|
595
596
|
for (const column of columns)
|
|
@@ -600,116 +601,117 @@ function createAdd(tableSchema, tableName, nonDefaults, softDefaults, hardDefaul
|
|
|
600
601
|
text += "}";
|
|
601
602
|
return text;
|
|
602
603
|
}
|
|
603
|
-
function createUpdate(tableSchema, tableName, columns,
|
|
604
|
-
const optionals = columns.filter(column => !keys.includes(column));
|
|
604
|
+
function createUpdate(tableSchema, tableName, columns, autoGenerated, hasForeign) {
|
|
605
605
|
let text = "";
|
|
606
606
|
const className = singularize(nameBeautifier(tableName)).replaceAll(" ", "");
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
continue;
|
|
613
|
-
text += "import {get" + nameBeautifier(column.foreignTable).replaceAll(" ", "") + "By" + nameBeautifier(column.foreignColumn).replaceAll(" ", "") + "} from \".";
|
|
614
|
-
if (tableSchema !== column.foreignSchema)
|
|
615
|
-
text += "./" + column.foreignSchema;
|
|
616
|
-
text += "/" + column.foreignTable + ".js\";\n";
|
|
617
|
-
}
|
|
618
|
-
}
|
|
607
|
+
const functionName = "update" + nameBeautifier(tableName).replaceAll(" ", "");
|
|
608
|
+
const criteriaType = "Partial<" + className + ">";
|
|
609
|
+
const updatesType = "Partial<Omit<" + className + ", '" + autoGenerated.map((column) => {
|
|
610
|
+
return column.name;
|
|
611
|
+
}).join("' | '") + "'>>";
|
|
619
612
|
text += "/**\n";
|
|
620
|
-
text += " * Updates
|
|
621
|
-
for (const key of keys)
|
|
622
|
-
text += key.name + " and ";
|
|
623
|
-
text = text.slice(0, -5) + ".\n";
|
|
613
|
+
text += " * Updates existing " + className + " objects in the database based on the specified criteria.\n";
|
|
624
614
|
text += " *\n";
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
for (
|
|
628
|
-
|
|
629
|
-
text += " * @
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
text += nameBeautifier(column.name) + " or the ";
|
|
635
|
-
text = text.slice(0, -8);
|
|
636
|
-
text += " not existing in their table.\n";
|
|
637
|
-
}
|
|
615
|
+
text += " * @async\n";
|
|
616
|
+
text += " * @function " + functionName + "\n";
|
|
617
|
+
text += " * @param {" + criteriaType + "} criteria - An object defining the conditions for which " + nameBeautifier(tableName) + " to update.\n";
|
|
618
|
+
text += " * An empty object would typically mean updating ALL " + nameBeautifier(tableName) + ", which should be handled with caution or disallowed.\n";
|
|
619
|
+
text += " * @param {" + updatesType + "} updates - An object containing the new values for the fields to be updated.\n";
|
|
620
|
+
text += " * Only fields present in this object will be updated.\n";
|
|
621
|
+
text += " * @returns {Promise<" + className + "[]>} A promise that resolves to an array of the updated " + className + " objects.\n";
|
|
622
|
+
text += " * Returns an empty array if no records matched the criteria or if no rows were updated.\n";
|
|
623
|
+
text += " * @throws {Error} If updates object is empty" + (hasForeign ? ", or if foreign key checks fail for provided update values" : "") + ".\n";
|
|
638
624
|
text += " */\n";
|
|
639
|
-
text += "export async function update" + className + "
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
text
|
|
643
|
-
text += "(";
|
|
644
|
-
for (const key of keys)
|
|
645
|
-
text += key.name + ": " + key.jsType.replace(" | null", "") + ", ";
|
|
646
|
-
for (const optional of optionals)
|
|
647
|
-
text += optional.name + "?: " + optional.jsType + " | undefined, ";
|
|
648
|
-
text = text.slice(0, -2);
|
|
649
|
-
text += "): Promise<" + className + "> {\n";
|
|
625
|
+
text += "export async function update" + nameBeautifier(tableName).replaceAll(" ", "") + "(criteria: " + criteriaType.replaceAll("'", "\"") + ", updates: " + updatesType.replaceAll("'", "\"") + "): Promise<" + className + "[]> {\n";
|
|
626
|
+
text += "\tif (Object.keys(updates).length === 0)\n";
|
|
627
|
+
text += "\t\tthrow new Error(\"No update data provided.\");\n";
|
|
628
|
+
text += "\n";
|
|
650
629
|
if (hasForeign) {
|
|
651
630
|
for (const column of columns) {
|
|
652
631
|
if (column.isForeign && column.foreignColumn && column.foreignTable && column.foreignSchema) {
|
|
653
632
|
const name = nameBeautifier(column.name).replaceAll(" ", "");
|
|
654
|
-
if (
|
|
655
|
-
text += "\tif (" + column.name + ") {\n";
|
|
656
|
-
text += "\t\tconst verify" + name + " = await get" + nameBeautifier(column.foreignTable).replaceAll(" ", "") + "By" + nameBeautifier(column.foreignColumn).replaceAll(" ", "") + "(" + column.name + ");\n";
|
|
657
|
-
text += "\t\tif (verify" + name + ".length === 0)\n";
|
|
658
|
-
text += "\t\t\tthrow \"The " + nameBeautifier(column.name) + " provided does not exist.\";\n";
|
|
659
|
-
text += "\t}\n\n";
|
|
633
|
+
if (column.isNullable) {
|
|
634
|
+
text += "\tif (updates." + column.name + " !== undefined && updates." + column.name + " !== null) {\n";
|
|
660
635
|
}
|
|
661
636
|
else {
|
|
662
|
-
text += "\
|
|
663
|
-
text += "\tif (verify" + name + ".length === 0)\n";
|
|
664
|
-
text += "\t\tthrow \"The " + nameBeautifier(column.name) + " provided does not exist.\";\n\n";
|
|
637
|
+
text += "\tif (updates." + column.name + " !== undefined) {\n";
|
|
665
638
|
}
|
|
639
|
+
text += "\t\tconst verify" + name + " = await get" + nameBeautifier(column.foreignTable).replaceAll(" ", "") + "({" + column.foreignColumn + ": updates." + column.name + "});\n";
|
|
640
|
+
text += "\t\tif (verify" + name + ".length === 0)\n";
|
|
641
|
+
text += "\t\t\tthrow new Error(\"The " + nameBeautifier(column.name) + " provided does not exist.\");\n";
|
|
642
|
+
text += "\t}\n\n";
|
|
666
643
|
}
|
|
667
644
|
}
|
|
668
645
|
}
|
|
669
|
-
text += "\
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
}
|
|
674
|
-
text += "\tset = set.slice(0, -2);\n";
|
|
675
|
-
let parameters = "";
|
|
676
|
-
let query = "UPDATE " + tableSchema + "." + tableName + ` SET \$\{set\} WHERE `;
|
|
677
|
-
for (let i = 0; i < keys.length; i++) {
|
|
678
|
-
query += keys[i].name + " = " + "$" + (i + 1) + "::" + keys[i].pgType + " AND ";
|
|
679
|
-
parameters += keys[i].name + ", ";
|
|
680
|
-
}
|
|
681
|
-
parameters = parameters.slice(0, -2);
|
|
682
|
-
query = query.slice(0, -5) + " RETURNING *;";
|
|
683
|
-
text += queryMaker(1, "update", query, parameters);
|
|
684
|
-
text += "\n\n";
|
|
685
|
-
text += "\treturn new " + className + "(\n";
|
|
686
|
-
for (const column of columns)
|
|
687
|
-
text += "\t\tupdateQuery.rows[0]." + column.name + ",\n";
|
|
688
|
-
text = text.slice(0, -2);
|
|
646
|
+
text += "\tconst setClauses: string[] = [];\n";
|
|
647
|
+
text += "\tconst whereClauses: string[] = [];\n";
|
|
648
|
+
text += "\tconst values: any[] = [];\n";
|
|
649
|
+
text += "\tlet paramIndex = 1;\n";
|
|
689
650
|
text += "\n";
|
|
690
|
-
text += "\
|
|
651
|
+
text += "\tfor (const key in updates) {\n";
|
|
652
|
+
text += "\t\tif (Object.prototype.hasOwnProperty.call(updates, key)) {\n";
|
|
653
|
+
text += "\t\t\tconst value = updates[key as keyof typeof updates];\n";
|
|
654
|
+
text += "\t\t\tif (value !== undefined) {\n";
|
|
655
|
+
text += "\t\t\t\tvalues.push(value);\n";
|
|
656
|
+
text += "\t\t\t\tsetClauses.push(\"\\\"\"+ key + \"\\\" = $\" + paramIndex + (value !== null ? getTypecast(key) : \"\"));\n";
|
|
657
|
+
text += "\t\t\t\tparamIndex++;\n";
|
|
658
|
+
text += "\t\t\t}\n";
|
|
659
|
+
text += "\t\t}\n";
|
|
660
|
+
text += "\t}\n";
|
|
661
|
+
text += "\n";
|
|
662
|
+
text += "\tfor (const key in criteria) {\n";
|
|
663
|
+
text += "\t\tif (Object.prototype.hasOwnProperty.call(criteria, key)) {\n";
|
|
664
|
+
text += "\t\t\tconst value = criteria[key as keyof typeof criteria];\n";
|
|
665
|
+
text += "\t\t\tif (value !== undefined) {\n";
|
|
666
|
+
text += "\t\t\t\tvalues.push(value);\n";
|
|
667
|
+
text += "\t\t\t\twhereClauses.push(\"\\\"\"+ key + \"\\\" = $\" + paramIndex + (value !== null ? getTypecast(key) : \"\"));\n";
|
|
668
|
+
text += "\t\t\t\tparamIndex++;\n";
|
|
669
|
+
text += "\t\t\t}\n";
|
|
670
|
+
text += "\t\t}\n";
|
|
671
|
+
text += "\t}\n";
|
|
672
|
+
text += "\n";
|
|
673
|
+
text += "\tlet sql = \"UPDATE " + tableSchema + "." + tableName + " SET \" + setClauses.join(\", \") + (whereClauses.length !== 0 ? \" WHERE \" + whereClauses.join(\" AND \") : \"\") + \" RETURNING *;\";\n";
|
|
674
|
+
text += "\n";
|
|
675
|
+
text += "\tconst updateQuery = await query(sql, values);\n";
|
|
676
|
+
text += arrayMaker(1, "update", className, columns) + "\n";
|
|
677
|
+
text += "\treturn update;\n";
|
|
691
678
|
text += "}";
|
|
692
679
|
return text;
|
|
693
680
|
}
|
|
694
|
-
function createDelete(tableSchema, tableName,
|
|
681
|
+
function createDelete(tableSchema, tableName, columns) {
|
|
695
682
|
let text = "";
|
|
696
683
|
const className = singularize(nameBeautifier(tableName)).replaceAll(" ", "");
|
|
684
|
+
const functionName = "delete" + nameBeautifier(tableName).replaceAll(" ", "");
|
|
697
685
|
text += "/**\n";
|
|
698
|
-
text += " * Deletes the " + className + "
|
|
686
|
+
text += " * Deletes the " + className + " records from the database based on specified criteria.\n";
|
|
699
687
|
text += " *\n";
|
|
700
|
-
text += " * @
|
|
701
|
-
text += " * @
|
|
688
|
+
text += " * @async\n";
|
|
689
|
+
text += " * @function " + functionName + "\n";
|
|
690
|
+
text += " * @param {Partial<" + className + ">} criteria - An object containing properties of the " + className + " to filter by for deletion.\n";
|
|
691
|
+
text += " * An empty object would typically mean deleting ALL " + nameBeautifier(tableName) + ", which should be handled with caution or disallowed.\n";
|
|
692
|
+
text += " * Only accounts matching all provided criteria will be deleted.\n";
|
|
693
|
+
text += " * @returns {Promise<" + className + "[]>} - A Promise object returning the deleted " + nameBeautifier(tableName) + ".\n";
|
|
702
694
|
text += " */\n";
|
|
703
|
-
text += "export async function
|
|
704
|
-
|
|
705
|
-
text +=
|
|
706
|
-
text += "\n
|
|
707
|
-
text += "\
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
text
|
|
695
|
+
text += "export async function " + functionName + "(criteria : Partial<" + className + ">): Promise<" + className + "[]> {\n";
|
|
696
|
+
text += "\tconst whereClauses: string[] = [];\n";
|
|
697
|
+
text += "\tconst values: any[] = [];\n";
|
|
698
|
+
text += "\tlet paramIndex = 1;\n";
|
|
699
|
+
text += "\tfor (const key in criteria) {\n";
|
|
700
|
+
text += "\t\tif (Object.prototype.hasOwnProperty.call(criteria, key)) {\n";
|
|
701
|
+
text += "\t\t\tconst value = criteria[key as keyof typeof criteria];\n";
|
|
702
|
+
text += "\t\t\tif (value !== undefined) {\n";
|
|
703
|
+
text += "\t\t\t\tvalues.push(value);\n";
|
|
704
|
+
text += "\t\t\t\twhereClauses.push(\"\\\"\"+ key + \"\\\" = $\" + paramIndex + (value !== null ? getTypecast(key) : \"\"));\n";
|
|
705
|
+
text += "\t\t\t\tparamIndex++;\n";
|
|
706
|
+
text += "\t\t\t}\n";
|
|
707
|
+
text += "\t\t}\n";
|
|
708
|
+
text += "\t}\n";
|
|
711
709
|
text += "\n";
|
|
712
|
-
text += "\
|
|
710
|
+
text += "\tlet sql = \"DELETE FROM " + tableSchema + "." + tableName + "\" + (whereClauses.length !== 0 ? \" WHERE \" + whereClauses.join(\" AND \") : \"\") + \" RETURNING *;\";\n";
|
|
711
|
+
text += "\n";
|
|
712
|
+
text += "\tconst removeQuery = await query(sql, values);\n";
|
|
713
|
+
text += arrayMaker(1, "remove", className, columns) + "\n";
|
|
714
|
+
text += "\treturn remove;\n";
|
|
713
715
|
text += "}";
|
|
714
716
|
return text;
|
|
715
717
|
}
|
package/src/pgAdmin.js
CHANGED
|
@@ -57,7 +57,7 @@ export function tableProcessing(tables) {
|
|
|
57
57
|
if (column.attnotnull)
|
|
58
58
|
isNullable = false;
|
|
59
59
|
const jsType = getJSType(types.dataType, types.udtName, isNullable);
|
|
60
|
-
const pgType = getPGType(types.dataType);
|
|
60
|
+
const pgType = getPGType(types.dataType, types.udtName);
|
|
61
61
|
if (column.cltype.includes("serial"))
|
|
62
62
|
column.defval = "nextval('" + data.name + "_" + column.name + "_seq'::regclass)";
|
|
63
63
|
let columnDefault;
|
package/src/utils.js
CHANGED
|
@@ -69,45 +69,6 @@ export function tabsInserter(tabNumber) {
|
|
|
69
69
|
tabs += "\t";
|
|
70
70
|
return tabs;
|
|
71
71
|
}
|
|
72
|
-
export function queryMaker(baseTabs, variableName, command, parameters) {
|
|
73
|
-
let query = "";
|
|
74
|
-
query += tabsInserter(baseTabs) + "let " + variableName + "Query;\n";
|
|
75
|
-
query += tabsInserter(baseTabs) + "try {\n";
|
|
76
|
-
query += tabsInserter(baseTabs + 1) + "await client.connect();\n";
|
|
77
|
-
query += tabsInserter(baseTabs + 1) + variableName + "Query = await client.query(`\n";
|
|
78
|
-
query += queryBeautifier(baseTabs + 2, command);
|
|
79
|
-
query += tabsInserter(baseTabs + 1) + "`, [" + parameters + "]);\n";
|
|
80
|
-
query += tabsInserter(baseTabs) + "} catch (error: any) {\n";
|
|
81
|
-
query += tabsInserter(baseTabs + 1) + "throw error;\n";
|
|
82
|
-
query += tabsInserter(baseTabs) + "} finally {\n";
|
|
83
|
-
query += tabsInserter(baseTabs + 1) + "await client.end();\n";
|
|
84
|
-
query += tabsInserter(baseTabs) + "}";
|
|
85
|
-
return query;
|
|
86
|
-
}
|
|
87
|
-
function queryBeautifier(baseTabs, command) {
|
|
88
|
-
const regex = /(?=((?:SELECT|INSERT|UPDATE|DELETE|FROM|WHERE|AND|VALUES|RETURNING).*?)(?:FROM|WHERE|AND|VALUES|RETURNING|;))/g;
|
|
89
|
-
let match;
|
|
90
|
-
let lines = [];
|
|
91
|
-
while ((match = regex.exec(command)) !== null) {
|
|
92
|
-
lines.push(match[1]);
|
|
93
|
-
regex.lastIndex = regex.lastIndex + 1;
|
|
94
|
-
}
|
|
95
|
-
for (let i = 0; i < lines.length; i++)
|
|
96
|
-
if (lines[i][lines[i].length - 1] === " ")
|
|
97
|
-
lines[i] = lines[i].slice(0, -1);
|
|
98
|
-
lines[lines.length - 1] = lines[lines.length - 1] + ";";
|
|
99
|
-
let maxLength = 0;
|
|
100
|
-
for (const line of lines)
|
|
101
|
-
if (line.split(" ")[0].length > maxLength)
|
|
102
|
-
maxLength = line.split(" ")[0].length;
|
|
103
|
-
for (let i = 0; i < lines.length; i++)
|
|
104
|
-
if (lines[i].split(" ")[0].length < maxLength)
|
|
105
|
-
lines[i] = " ".repeat(maxLength - lines[i].split(" ")[0].length) + lines[i];
|
|
106
|
-
let formated = "";
|
|
107
|
-
for (const line of lines)
|
|
108
|
-
formated += tabsInserter(baseTabs) + line + "\n";
|
|
109
|
-
return formated;
|
|
110
|
-
}
|
|
111
72
|
export function arrayMaker(baseTabs, variableName, className, columns) {
|
|
112
73
|
let array = "";
|
|
113
74
|
array += tabsInserter(baseTabs) + "let " + variableName + ": " + className + "[] = [];\n";
|
|
@@ -131,21 +92,6 @@ export function singularize(word) {
|
|
|
131
92
|
};
|
|
132
93
|
return word.replace(new RegExp(`(${Object.keys(endings).join('|')})$`), r => endings[r]);
|
|
133
94
|
}
|
|
134
|
-
export function getCombinations(valuesArray) {
|
|
135
|
-
let combinations = [];
|
|
136
|
-
let temp = [];
|
|
137
|
-
let possibleCombinations = Math.pow(2, valuesArray.length);
|
|
138
|
-
for (let i = 0; i < possibleCombinations; i++) {
|
|
139
|
-
temp = [];
|
|
140
|
-
for (let j = 0; j < valuesArray.length; j++)
|
|
141
|
-
if ((i & Math.pow(2, j)))
|
|
142
|
-
temp.push(valuesArray[j]);
|
|
143
|
-
if (temp.length > 0)
|
|
144
|
-
combinations.push(temp);
|
|
145
|
-
}
|
|
146
|
-
combinations.sort((a, b) => a.length - b.length);
|
|
147
|
-
return combinations;
|
|
148
|
-
}
|
|
149
95
|
export function getJSType(dataType, udtName, isNullable) {
|
|
150
96
|
dataType = dataType.replace("serial", "int");
|
|
151
97
|
if (dataType === "int")
|