@andymic/pigeon 1.6.0 → 2.0.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.
- package/package.json +2 -2
- package/src/index.js +270 -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.1",
|
|
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": "565fcc64621e0c076f77933662e6f82969ef62e6"
|
|
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,27 @@ 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
|
+
const imports = [];
|
|
276
|
+
for (const column of table.columns) {
|
|
277
|
+
if (column.isForeign && column.foreignColumn && column.foreignTable && column.foreignSchema) {
|
|
278
|
+
if ((table.schema === column.foreignSchema) && (table.name === column.foreignTable))
|
|
279
|
+
continue;
|
|
280
|
+
if (imports.includes(column.foreignSchema + "/" + column.foreignTable))
|
|
281
|
+
continue;
|
|
282
|
+
ts += "import {get" + nameBeautifier(column.foreignTable).replaceAll(" ", "") + "} from \".";
|
|
283
|
+
if (table.schema !== column.foreignSchema)
|
|
284
|
+
ts += "./" + column.foreignSchema;
|
|
285
|
+
ts += "/" + column.foreignTable + ".js\";\n";
|
|
286
|
+
imports.push(column.foreignSchema + "/" + column.foreignTable);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (imports.length !== 0)
|
|
290
|
+
ts += "\n";
|
|
291
|
+
ts += "import query from \"./index.js\";\n\n";
|
|
275
292
|
if (enums) {
|
|
276
293
|
for (const cEnum of enums) {
|
|
277
294
|
for (const column of table.columns) {
|
|
@@ -292,85 +309,75 @@ export function runGeneration(dir, db, tables, enums) {
|
|
|
292
309
|
}
|
|
293
310
|
ts += createClass(table.name, table.columns);
|
|
294
311
|
ts += "\n\n";
|
|
295
|
-
ts +=
|
|
312
|
+
ts += createGetTypecast(table.columns);
|
|
313
|
+
ts += "\n\n";
|
|
314
|
+
ts += createGet(table.schema, table.name, table.columns);
|
|
296
315
|
ts += "\n\n";
|
|
297
316
|
let keys = [];
|
|
298
317
|
for (const column of table.columns)
|
|
299
318
|
if (column.isPrimary || column.isForeign || column.isUnique)
|
|
300
319
|
keys.push(column);
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
let nonDefaults = [];
|
|
306
|
-
let softDefaults = [];
|
|
307
|
-
let hardDefaults = [];
|
|
320
|
+
let required = [];
|
|
321
|
+
let optional = [];
|
|
322
|
+
let autoGenerated = [];
|
|
308
323
|
for (const column of table.columns) {
|
|
309
324
|
if (column.defaultValue === null && !column.isIdentity)
|
|
310
|
-
|
|
325
|
+
required.push(column);
|
|
311
326
|
else if ((column.defaultValue !== null && !column.defaultValue.includes("nextval")) || (column.isIdentity && column.identityGeneration === "BY DEFAULT"))
|
|
312
|
-
|
|
327
|
+
optional.push(column);
|
|
313
328
|
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";
|
|
329
|
+
autoGenerated.push(column);
|
|
326
330
|
}
|
|
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;
|
|
331
|
+
ts += createAdd(table.schema, table.name, required, optional, autoGenerated, imports.length !== 0);
|
|
332
|
+
ts += "\n\n";
|
|
333
|
+
ts += createUpdate(table.schema, table.name, table.columns, autoGenerated, imports.length !== 0);
|
|
334
|
+
ts += "\n\n";
|
|
335
|
+
ts += createDelete(table.schema, table.name, table.columns);
|
|
371
336
|
fs.writeFileSync(path.join(dir, table.schema, table.name + ".ts"), ts);
|
|
372
337
|
}
|
|
373
338
|
}
|
|
339
|
+
function createIndex(db) {
|
|
340
|
+
let text = "";
|
|
341
|
+
text += "import pg from \"pg\";\n";
|
|
342
|
+
text += "const { Client, Pool } = pg;\n";
|
|
343
|
+
text += "\n";
|
|
344
|
+
text += "const pool = new Pool({\n";
|
|
345
|
+
text += "\thost: \"" + db.host + "\",\n";
|
|
346
|
+
text += "\tport: " + db.port + ",\n";
|
|
347
|
+
text += "\tdatabase: \"" + db.db + "\",\n";
|
|
348
|
+
text += "\tuser: \"" + db.user + "\",\n";
|
|
349
|
+
text += "\tpassword: \"" + db.pass + "\",\n";
|
|
350
|
+
text += "\tmax: 20,\n";
|
|
351
|
+
text += "\tidleTimeoutMillis: 30000,\n";
|
|
352
|
+
text += "\tconnectionTimeoutMillis: 2000\n";
|
|
353
|
+
text += "});\n";
|
|
354
|
+
text += "\n";
|
|
355
|
+
text += "const client = new Client({\n";
|
|
356
|
+
text += "\thost: \"" + db.host + "\",\n";
|
|
357
|
+
text += "\tport: " + db.port + ",\n";
|
|
358
|
+
text += "\tdatabase: \"" + db.db + "\",\n";
|
|
359
|
+
text += "\tuser: \"" + db.user + "\",\n";
|
|
360
|
+
text += "\tpassword: \"" + db.pass + "\"\n";
|
|
361
|
+
text += "});\n";
|
|
362
|
+
text += "\n";
|
|
363
|
+
text += "/*\n";
|
|
364
|
+
text += "export default (sql: string, params: any[]) => pool.query(sql, params);\n";
|
|
365
|
+
text += "*/\n";
|
|
366
|
+
text += "\n";
|
|
367
|
+
text += "/*\n";
|
|
368
|
+
text += "export default async (sql: string, params: any[]) => {\n";
|
|
369
|
+
text += "\ttry {\n";
|
|
370
|
+
text += "\t\tawait client.connect();\n";
|
|
371
|
+
text += "\t\treturn await client.query(sql, params);\n";
|
|
372
|
+
text += "\t} catch (error: any) {\n";
|
|
373
|
+
text += "\t\tthrow error;\n";
|
|
374
|
+
text += "\t} finally {\n";
|
|
375
|
+
text += "\t\tawait client.end();\n";
|
|
376
|
+
text += "\t}\n";
|
|
377
|
+
text += "}\n";
|
|
378
|
+
text += "*/";
|
|
379
|
+
return text;
|
|
380
|
+
}
|
|
374
381
|
function createClass(tableName, columns) {
|
|
375
382
|
let text = "";
|
|
376
383
|
text += "export class " + singularize(nameBeautifier(tableName)).replaceAll(" ", "") + " {\n";
|
|
@@ -439,157 +446,153 @@ function createClass(tableName, columns) {
|
|
|
439
446
|
text += "}";
|
|
440
447
|
return text;
|
|
441
448
|
}
|
|
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) {
|
|
449
|
+
function createGetTypecast(columns) {
|
|
454
450
|
let text = "";
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
text += "\
|
|
465
|
-
text +=
|
|
466
|
-
text += "\treturn " + varName + ";\n";
|
|
451
|
+
text += "function getTypecast(columnName: string): string {\n";
|
|
452
|
+
text += "\tswitch (columnName) {\n";
|
|
453
|
+
const typeColumns = columns.toSorted((a, b) => a.pgType.localeCompare(b.pgType));
|
|
454
|
+
for (let i = 0; i < typeColumns.length; i++) {
|
|
455
|
+
text += "\t\tcase \"" + typeColumns[i].name + "\":\n";
|
|
456
|
+
if (i !== typeColumns.length - 1 && typeColumns[i].pgType === typeColumns[i + 1].pgType)
|
|
457
|
+
continue;
|
|
458
|
+
text += "\t\t\treturn \"::" + typeColumns[i].pgType + "\";\n";
|
|
459
|
+
}
|
|
460
|
+
text += "\t}\n";
|
|
461
|
+
text += "\treturn \"\";\n";
|
|
467
462
|
text += "}";
|
|
468
463
|
return text;
|
|
469
464
|
}
|
|
470
|
-
function createGet(tableSchema, tableName, columns
|
|
465
|
+
function createGet(tableSchema, tableName, columns) {
|
|
471
466
|
let text = "";
|
|
472
467
|
const className = singularize(nameBeautifier(tableName)).replaceAll(" ", "");
|
|
473
468
|
const varName = nameBeautifier(tableName).replaceAll(" ", "")[0].toLowerCase() + nameBeautifier(tableName).replaceAll(" ", "").substring(1);
|
|
469
|
+
const functionName = "get" + nameBeautifier(tableName).replaceAll(" ", "");
|
|
474
470
|
text += "/**\n";
|
|
475
|
-
text += " *
|
|
476
|
-
for (const key of keys)
|
|
477
|
-
text += key.name + " and ";
|
|
478
|
-
text = text.slice(0, -5) + ".\n";
|
|
471
|
+
text += " * Finds and retrieves " + singularize(nameBeautifier(tableName)) + " objects from the database based on specified criteria.\n";
|
|
479
472
|
text += " *\n";
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
text += " * @
|
|
473
|
+
text += " * @async\n";
|
|
474
|
+
text += " * @function " + functionName + "\n";
|
|
475
|
+
text += " * @param {Partial<" + className + ">} criteria - An object containing properties of the " + className + " to filter by.\n";
|
|
476
|
+
text += " * Only " + nameBeautifier(tableName).toLowerCase() + " matching all provided criteria will be returned.\n";
|
|
477
|
+
text += " * An empty object or undefined criteria will fetch all " + className + " objects.\n";
|
|
478
|
+
text += " * @returns {Promise<" + className + "[]>} - A Promise object returning an array of " + nameBeautifier(tableName) + " matching the criteria.\n";
|
|
483
479
|
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";
|
|
480
|
+
text += "export async function get" + nameBeautifier(tableName).replaceAll(" ", "") + "(criteria: Partial<" + className + ">): Promise<" + className + "[]> {\n";
|
|
481
|
+
text += "\tconst whereClauses: string[] = [];\n";
|
|
482
|
+
text += "\tconst values: any[] = [];\n";
|
|
483
|
+
text += "\tlet paramIndex = 1;\n";
|
|
484
|
+
text += "\n";
|
|
485
|
+
text += "\tfor (const key in criteria) {\n";
|
|
486
|
+
text += "\t\tif (Object.prototype.hasOwnProperty.call(criteria, key) && criteria[key as keyof typeof criteria] !== undefined) {\n";
|
|
487
|
+
text += "\t\t\twhereClauses.push(\"\\\"\" + key + \"\\\" = $\" + paramIndex + getTypecast(key));\n";
|
|
488
|
+
text += "\t\t\tvalues.push(criteria[key as keyof typeof criteria]);\n";
|
|
489
|
+
text += "\t\t\tparamIndex++;\n";
|
|
490
|
+
text += "\t\t}\n";
|
|
491
|
+
text += "\t}\n";
|
|
492
|
+
text += "\n";
|
|
493
|
+
text += "\tlet sql = \"SELECT * FROM " + tableSchema + "." + tableName + "\";\n";
|
|
494
|
+
text += "\tif (whereClauses.length > 0)\n";
|
|
495
|
+
text += "\t\tsql += \" WHERE \" + whereClauses.join(\" AND \");\n";
|
|
496
|
+
text += "\tsql += \";\";\n";
|
|
497
|
+
text += "\n";
|
|
498
|
+
text += "\tconst " + varName + "Query = await query(sql, values);\n";
|
|
508
499
|
text += arrayMaker(1, varName, className, columns) + "\n";
|
|
509
500
|
text += "\treturn " + varName + ";\n";
|
|
510
501
|
text += "}";
|
|
511
502
|
return text;
|
|
512
503
|
}
|
|
513
|
-
function createAdd(tableSchema, tableName,
|
|
504
|
+
function createAdd(tableSchema, tableName, required, optional, autoGenerated, hasForeign) {
|
|
514
505
|
let text = "";
|
|
515
506
|
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
|
-
}
|
|
507
|
+
const paramName = className.slice(0, 1).toLowerCase() + className.slice(1);
|
|
508
|
+
const functionName = "add" + singularize(nameBeautifier(tableName).replaceAll(" ", ""));
|
|
509
|
+
const dataType = "Omit<" + className + ", '" + autoGenerated.concat(optional).map((column) => {
|
|
510
|
+
return column.name;
|
|
511
|
+
}).join("\' | \'") + "'>" + (optional.length > 0 ? " & Partial<Pick<" + className + ", '" + optional.map((column) => {
|
|
512
|
+
return column.name;
|
|
513
|
+
}).join("\' | \'") + "'>>" : "");
|
|
514
|
+
const editableColumns = required.concat(optional);
|
|
528
515
|
text += "/**\n";
|
|
529
|
-
text += " * Adds
|
|
516
|
+
text += " * Adds a new " + singularize(nameBeautifier(tableName)) + " object to the database.\n";
|
|
517
|
+
if (autoGenerated.length !== 0)
|
|
518
|
+
text += " * The field" + (autoGenerated.length > 1 ? "s" : "") + " `" + autoGenerated.map((column) => {
|
|
519
|
+
return column.name;
|
|
520
|
+
}).join("\`, \`") + "` " + (autoGenerated.length > 1 ? "are" : "is") + " auto-generated.\n";
|
|
521
|
+
if (optional.length !== 0)
|
|
522
|
+
text += " * The field" + (optional.length > 1 ? "s" : "") + " `" + optional.map((column) => {
|
|
523
|
+
return column.name;
|
|
524
|
+
}).join("`, `") + "` " + (optional.length > 1 ? "are" : "is") + " optional and will use database defaults if not provided.\n";
|
|
530
525
|
text += " *\n";
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
for
|
|
534
|
-
|
|
535
|
-
|
|
526
|
+
text += " * @async\n";
|
|
527
|
+
text += " * @function " + functionName + "\n";
|
|
528
|
+
text += " * @param {" + dataType + "} " + paramName + " - An object containing the data for the new " + className + ".\n";
|
|
529
|
+
if (required.length !== 0)
|
|
530
|
+
text += " * - The " + (required.length > 1 ? "properties" : "property") + " from `" + className + "\` like \`" + required.map((column) => {
|
|
531
|
+
return column.name;
|
|
532
|
+
}).join("`, `") + "` " + (required.length > 1 ? "are" : "is") + " required.\n";
|
|
533
|
+
const nullable = editableColumns.filter((column) => column.isNullable);
|
|
534
|
+
if (nullable.length !== 0)
|
|
535
|
+
text += " * - The nullable " + (nullable.length > 1 ? "fields" : "field") + " like \`" + nullable.map((column) => {
|
|
536
|
+
return column.name;
|
|
537
|
+
}).join("`, `") + "` can be provided as `null` or " + (nullable.length > 1 ? "their" : "its") + " respective type.\n";
|
|
538
|
+
if (optional.length !== 0)
|
|
539
|
+
text += " * - The field" + (optional.length > 1 ? "s" : "") + " `" + optional.map((column) => {
|
|
540
|
+
return column.name;
|
|
541
|
+
}).join("`, `") + "` " + (optional.length > 1 ? "are" : "is") + " optional.\n";
|
|
542
|
+
text += " * @returns {Promise<" + className + ">} A Promise returning the newly created " + className + " object.\n";
|
|
536
543
|
if (hasForeign) {
|
|
537
|
-
text += " * @throws
|
|
538
|
-
for (const column of
|
|
544
|
+
text += " * @throws {Error} An exception in the case of the ";
|
|
545
|
+
for (const column of editableColumns)
|
|
539
546
|
if (column.isForeign)
|
|
540
547
|
text += nameBeautifier(column.name) + " or the ";
|
|
541
548
|
text = text.slice(0, -8);
|
|
542
|
-
text += " not existing in their table.\n";
|
|
549
|
+
text += " not existing in their table, or if other pre-insertion validation fails.\n";
|
|
543
550
|
}
|
|
544
551
|
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";
|
|
552
|
+
text += "export async function " + functionName + "(" + paramName + ": " + dataType.replaceAll("'", "\"") + "): Promise<" + className + "> {\n";
|
|
557
553
|
if (hasForeign) {
|
|
558
|
-
for (const column of
|
|
554
|
+
for (const column of editableColumns) {
|
|
559
555
|
if (column.isForeign && column.foreignColumn && column.foreignTable && column.foreignSchema) {
|
|
560
556
|
const name = nameBeautifier(column.name).replaceAll(" ", "");
|
|
561
557
|
if (column.isNullable) {
|
|
562
|
-
text += "\tif (" + column.name + ") {\n";
|
|
563
|
-
text += "\t\tconst verify" + name + " = await get" + nameBeautifier(column.foreignTable).replaceAll(" ", "") + "
|
|
558
|
+
text += "\tif (" + paramName + "." + column.name + " !== undefined && " + paramName + "." + column.name + " !== null) {\n";
|
|
559
|
+
text += "\t\tconst verify" + name + " = await get" + nameBeautifier(column.foreignTable).replaceAll(" ", "") + "({" + column.foreignColumn + ": " + paramName + "." + column.name + "});\n";
|
|
564
560
|
text += "\t\tif (verify" + name + ".length === 0)\n";
|
|
565
|
-
text += "\t\t\tthrow \"The " + nameBeautifier(column.name) + " provided does not exist.\";\n";
|
|
561
|
+
text += "\t\t\tthrow new Error(\"The " + nameBeautifier(column.name) + " provided does not exist.\");\n";
|
|
566
562
|
text += "\t}\n\n";
|
|
567
563
|
}
|
|
568
564
|
else {
|
|
569
|
-
text += "\tconst verify" + name + " = await get" + nameBeautifier(column.foreignTable).replaceAll(" ", "") + "
|
|
565
|
+
text += "\tconst verify" + name + " = await get" + nameBeautifier(column.foreignTable).replaceAll(" ", "") + "({" + column.foreignColumn + ": " + paramName + "." + column.name + "});\n";
|
|
570
566
|
text += "\tif (verify" + name + ".length === 0)\n";
|
|
571
|
-
text += "\t\tthrow \"The " + nameBeautifier(column.name) + " provided does not exist.\";\n\n";
|
|
567
|
+
text += "\t\t\tthrow new Error(\"The " + nameBeautifier(column.name) + " provided does not exist.\");\n\n";
|
|
572
568
|
}
|
|
573
569
|
}
|
|
574
570
|
}
|
|
575
571
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
text +=
|
|
590
|
-
text += "\
|
|
572
|
+
text += "\tconst intoClauses: string[] = [];\n";
|
|
573
|
+
text += "\tconst valuesClauses: string[] = [];\n";
|
|
574
|
+
text += "\tconst values: any[] = [];\n";
|
|
575
|
+
text += "\tlet paramIndex = 1;\n";
|
|
576
|
+
text += "\n";
|
|
577
|
+
text += "\tfor (const key in " + paramName + ") {\n";
|
|
578
|
+
text += "\t\tif (Object.prototype.hasOwnProperty.call(" + paramName + ", key)) {\n";
|
|
579
|
+
text += "\t\t\tconst value = " + paramName + "[key as keyof typeof " + paramName + "];\n";
|
|
580
|
+
text += "\t\t\tif (value !== undefined) {\n";
|
|
581
|
+
text += "\t\t\t\tintoClauses.push(\"\\\"\"+ key + \"\\\"\");\n";
|
|
582
|
+
text += "\t\t\t\tvalues.push(value);\n";
|
|
583
|
+
text += "\t\t\t\tvaluesClauses.push(\"$\" + paramIndex + getTypecast(key));\n";
|
|
584
|
+
text += "\t\t\t\tparamIndex++;\n";
|
|
585
|
+
text += "\t\t\t}\n";
|
|
586
|
+
text += "\t\t}\n";
|
|
587
|
+
text += "\t}\n";
|
|
588
|
+
text += "\n";
|
|
589
|
+
text += "\tif (intoClauses.length === 0)\n";
|
|
590
|
+
text += "\t\tthrow new Error(\"No data provided for " + paramName + " creation.\");\n";
|
|
591
|
+
text += "\n";
|
|
592
|
+
text += "\tconst sql = \"INSERT INTO " + tableSchema + "." + tableName + " (\" + intoClauses.join(\", \") + \") VALUES (\" + valuesClauses.join(\", \") + \") RETURNING *;\";\n";
|
|
593
|
+
text += "\tconst insertQuery = await query(sql, values);\n";
|
|
591
594
|
text += "\treturn new " + className + "(\n";
|
|
592
|
-
columns =
|
|
595
|
+
let columns = editableColumns.concat(autoGenerated);
|
|
593
596
|
columns = [...new Set(columns)];
|
|
594
597
|
columns.sort((a, b) => a.position - b.position);
|
|
595
598
|
for (const column of columns)
|
|
@@ -600,116 +603,117 @@ function createAdd(tableSchema, tableName, nonDefaults, softDefaults, hardDefaul
|
|
|
600
603
|
text += "}";
|
|
601
604
|
return text;
|
|
602
605
|
}
|
|
603
|
-
function createUpdate(tableSchema, tableName, columns,
|
|
604
|
-
const optionals = columns.filter(column => !keys.includes(column));
|
|
606
|
+
function createUpdate(tableSchema, tableName, columns, autoGenerated, hasForeign) {
|
|
605
607
|
let text = "";
|
|
606
608
|
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
|
-
}
|
|
609
|
+
const functionName = "update" + nameBeautifier(tableName).replaceAll(" ", "");
|
|
610
|
+
const criteriaType = "Partial<" + className + ">";
|
|
611
|
+
const updatesType = "Partial<" + (autoGenerated.length !== 0 ? "Omit<" + className + ", '" + autoGenerated.map((column) => {
|
|
612
|
+
return column.name;
|
|
613
|
+
}).join("' | '") + "'>" : className) + ">";
|
|
619
614
|
text += "/**\n";
|
|
620
|
-
text += " * Updates
|
|
621
|
-
for (const key of keys)
|
|
622
|
-
text += key.name + " and ";
|
|
623
|
-
text = text.slice(0, -5) + ".\n";
|
|
615
|
+
text += " * Updates existing " + className + " objects in the database based on the specified criteria.\n";
|
|
624
616
|
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
|
-
}
|
|
617
|
+
text += " * @async\n";
|
|
618
|
+
text += " * @function " + functionName + "\n";
|
|
619
|
+
text += " * @param {" + criteriaType + "} criteria - An object defining the conditions for which " + nameBeautifier(tableName) + " to update.\n";
|
|
620
|
+
text += " * An empty object would typically mean updating ALL " + nameBeautifier(tableName) + ", which should be handled with caution or disallowed.\n";
|
|
621
|
+
text += " * @param {" + updatesType + "} updates - An object containing the new values for the fields to be updated.\n";
|
|
622
|
+
text += " * Only fields present in this object will be updated.\n";
|
|
623
|
+
text += " * @returns {Promise<" + className + "[]>} A promise that resolves to an array of the updated " + className + " objects.\n";
|
|
624
|
+
text += " * Returns an empty array if no records matched the criteria or if no rows were updated.\n";
|
|
625
|
+
text += " * @throws {Error} If updates object is empty" + (hasForeign ? ", or if foreign key checks fail for provided update values" : "") + ".\n";
|
|
638
626
|
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";
|
|
627
|
+
text += "export async function update" + nameBeautifier(tableName).replaceAll(" ", "") + "(criteria: " + criteriaType.replaceAll("'", "\"") + ", updates: " + updatesType.replaceAll("'", "\"") + "): Promise<" + className + "[]> {\n";
|
|
628
|
+
text += "\tif (Object.keys(updates).length === 0)\n";
|
|
629
|
+
text += "\t\tthrow new Error(\"No update data provided.\");\n";
|
|
630
|
+
text += "\n";
|
|
650
631
|
if (hasForeign) {
|
|
651
632
|
for (const column of columns) {
|
|
652
633
|
if (column.isForeign && column.foreignColumn && column.foreignTable && column.foreignSchema) {
|
|
653
634
|
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";
|
|
635
|
+
if (column.isNullable) {
|
|
636
|
+
text += "\tif (updates." + column.name + " !== undefined && updates." + column.name + " !== null) {\n";
|
|
660
637
|
}
|
|
661
638
|
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";
|
|
639
|
+
text += "\tif (updates." + column.name + " !== undefined) {\n";
|
|
665
640
|
}
|
|
641
|
+
text += "\t\tconst verify" + name + " = await get" + nameBeautifier(column.foreignTable).replaceAll(" ", "") + "({" + column.foreignColumn + ": updates." + column.name + "});\n";
|
|
642
|
+
text += "\t\tif (verify" + name + ".length === 0)\n";
|
|
643
|
+
text += "\t\t\tthrow new Error(\"The " + nameBeautifier(column.name) + " provided does not exist.\");\n";
|
|
644
|
+
text += "\t}\n\n";
|
|
666
645
|
}
|
|
667
646
|
}
|
|
668
647
|
}
|
|
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);
|
|
648
|
+
text += "\tconst setClauses: string[] = [];\n";
|
|
649
|
+
text += "\tconst whereClauses: string[] = [];\n";
|
|
650
|
+
text += "\tconst values: any[] = [];\n";
|
|
651
|
+
text += "\tlet paramIndex = 1;\n";
|
|
689
652
|
text += "\n";
|
|
690
|
-
text += "\
|
|
653
|
+
text += "\tfor (const key in updates) {\n";
|
|
654
|
+
text += "\t\tif (Object.prototype.hasOwnProperty.call(updates, key)) {\n";
|
|
655
|
+
text += "\t\t\tconst value = updates[key as keyof typeof updates];\n";
|
|
656
|
+
text += "\t\t\tif (value !== undefined) {\n";
|
|
657
|
+
text += "\t\t\t\tvalues.push(value);\n";
|
|
658
|
+
text += "\t\t\t\tsetClauses.push(\"\\\"\"+ key + \"\\\" = $\" + paramIndex + (value !== null ? getTypecast(key) : \"\"));\n";
|
|
659
|
+
text += "\t\t\t\tparamIndex++;\n";
|
|
660
|
+
text += "\t\t\t}\n";
|
|
661
|
+
text += "\t\t}\n";
|
|
662
|
+
text += "\t}\n";
|
|
663
|
+
text += "\n";
|
|
664
|
+
text += "\tfor (const key in criteria) {\n";
|
|
665
|
+
text += "\t\tif (Object.prototype.hasOwnProperty.call(criteria, key)) {\n";
|
|
666
|
+
text += "\t\t\tconst value = criteria[key as keyof typeof criteria];\n";
|
|
667
|
+
text += "\t\t\tif (value !== undefined) {\n";
|
|
668
|
+
text += "\t\t\t\tvalues.push(value);\n";
|
|
669
|
+
text += "\t\t\t\twhereClauses.push(\"\\\"\"+ key + \"\\\" = $\" + paramIndex + (value !== null ? getTypecast(key) : \"\"));\n";
|
|
670
|
+
text += "\t\t\t\tparamIndex++;\n";
|
|
671
|
+
text += "\t\t\t}\n";
|
|
672
|
+
text += "\t\t}\n";
|
|
673
|
+
text += "\t}\n";
|
|
674
|
+
text += "\n";
|
|
675
|
+
text += "\tlet sql = \"UPDATE " + tableSchema + "." + tableName + " SET \" + setClauses.join(\", \") + (whereClauses.length !== 0 ? \" WHERE \" + whereClauses.join(\" AND \") : \"\") + \" RETURNING *;\";\n";
|
|
676
|
+
text += "\n";
|
|
677
|
+
text += "\tconst updateQuery = await query(sql, values);\n";
|
|
678
|
+
text += arrayMaker(1, "update", className, columns) + "\n";
|
|
679
|
+
text += "\treturn update;\n";
|
|
691
680
|
text += "}";
|
|
692
681
|
return text;
|
|
693
682
|
}
|
|
694
|
-
function createDelete(tableSchema, tableName,
|
|
683
|
+
function createDelete(tableSchema, tableName, columns) {
|
|
695
684
|
let text = "";
|
|
696
685
|
const className = singularize(nameBeautifier(tableName)).replaceAll(" ", "");
|
|
686
|
+
const functionName = "delete" + nameBeautifier(tableName).replaceAll(" ", "");
|
|
697
687
|
text += "/**\n";
|
|
698
|
-
text += " * Deletes the " + className + "
|
|
688
|
+
text += " * Deletes the " + className + " records from the database based on specified criteria.\n";
|
|
699
689
|
text += " *\n";
|
|
700
|
-
text += " * @
|
|
701
|
-
text += " * @
|
|
690
|
+
text += " * @async\n";
|
|
691
|
+
text += " * @function " + functionName + "\n";
|
|
692
|
+
text += " * @param {Partial<" + className + ">} criteria - An object containing properties of the " + className + " to filter by for deletion.\n";
|
|
693
|
+
text += " * An empty object would typically mean deleting ALL " + nameBeautifier(tableName) + ", which should be handled with caution or disallowed.\n";
|
|
694
|
+
text += " * Only accounts matching all provided criteria will be deleted.\n";
|
|
695
|
+
text += " * @returns {Promise<" + className + "[]>} - A Promise object returning the deleted " + nameBeautifier(tableName) + ".\n";
|
|
702
696
|
text += " */\n";
|
|
703
|
-
text += "export async function
|
|
704
|
-
|
|
705
|
-
text +=
|
|
706
|
-
text += "\n
|
|
707
|
-
text += "\
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
text
|
|
697
|
+
text += "export async function " + functionName + "(criteria : Partial<" + className + ">): Promise<" + className + "[]> {\n";
|
|
698
|
+
text += "\tconst whereClauses: string[] = [];\n";
|
|
699
|
+
text += "\tconst values: any[] = [];\n";
|
|
700
|
+
text += "\tlet paramIndex = 1;\n";
|
|
701
|
+
text += "\tfor (const key in criteria) {\n";
|
|
702
|
+
text += "\t\tif (Object.prototype.hasOwnProperty.call(criteria, key)) {\n";
|
|
703
|
+
text += "\t\t\tconst value = criteria[key as keyof typeof criteria];\n";
|
|
704
|
+
text += "\t\t\tif (value !== undefined) {\n";
|
|
705
|
+
text += "\t\t\t\tvalues.push(value);\n";
|
|
706
|
+
text += "\t\t\t\twhereClauses.push(\"\\\"\"+ key + \"\\\" = $\" + paramIndex + (value !== null ? getTypecast(key) : \"\"));\n";
|
|
707
|
+
text += "\t\t\t\tparamIndex++;\n";
|
|
708
|
+
text += "\t\t\t}\n";
|
|
709
|
+
text += "\t\t}\n";
|
|
710
|
+
text += "\t}\n";
|
|
711
711
|
text += "\n";
|
|
712
|
-
text += "\
|
|
712
|
+
text += "\tlet sql = \"DELETE FROM " + tableSchema + "." + tableName + "\" + (whereClauses.length !== 0 ? \" WHERE \" + whereClauses.join(\" AND \") : \"\") + \" RETURNING *;\";\n";
|
|
713
|
+
text += "\n";
|
|
714
|
+
text += "\tconst removeQuery = await query(sql, values);\n";
|
|
715
|
+
text += arrayMaker(1, "remove", className, columns) + "\n";
|
|
716
|
+
text += "\treturn remove;\n";
|
|
713
717
|
text += "}";
|
|
714
718
|
return text;
|
|
715
719
|
}
|
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")
|