@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andymic/pigeon",
3
- "version": "1.6.0",
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": "6562b7ff32a6a9dd9f0ef108026c198db32b0aa9"
38
+ "gitHead": "85641ef9cc7fe7b42c78f7470608793f192b880a"
39
39
  }
package/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { arrayMaker, getCombinations, getJSType, getPGType, nameBeautifier, queryMaker, runQuery, singularize, sleep, tabsInserter } from "./utils.js";
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 = clientMaker(0, db);
274
- ts += "\n\n";
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 += createGetAll(table.schema, table.name, table.columns);
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
- for (const keyCombination of getCombinations(keys)) {
302
- ts += createGet(table.schema, table.name, table.columns, keyCombination);
303
- ts += "\n\n";
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
- nonDefaults.push(column);
323
+ required.push(column);
311
324
  else if ((column.defaultValue !== null && !column.defaultValue.includes("nextval")) || (column.isIdentity && column.identityGeneration === "BY DEFAULT"))
312
- softDefaults.push(column);
325
+ optional.push(column);
313
326
  else if ((column.defaultValue !== null && column.defaultValue.includes("nextval")) || (column.isIdentity && column.identityGeneration === "ALWAYS"))
314
- hardDefaults.push(column);
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 = ts.slice(0, -2);
328
- const regex = /import ({?.*?}?) from "(.*?)";\n/g;
329
- let importObjects = [];
330
- const matches = ts.matchAll(regex);
331
- let charOffset = 0;
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
- export function clientMaker(baseTabs, db) {
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
- const className = singularize(nameBeautifier(tableName)).replaceAll(" ", "");
456
- const varName = nameBeautifier(tableName).replaceAll(" ", "")[0].toLowerCase() + nameBeautifier(tableName).replaceAll(" ", "").substring(1);
457
- text += "/**\n";
458
- text += " * Gets all " + className + " objects from the database.\n";
459
- text += " *\n";
460
- text += " * @returns {Promise<" + className + "[]>} - A Promise object returning an array of " + nameBeautifier(tableName) + ".\n";
461
- text += " */\n";
462
- text += "export async function getAll" + nameBeautifier(tableName).replaceAll(" ", "") + "(): Promise<" + className + "[]> {\n";
463
- text += queryMaker(1, varName, "SELECT * FROM " + tableSchema + "." + tableName + ";", "");
464
- text += "\n\n";
465
- text += arrayMaker(1, varName, className, columns) + "\n";
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, keys) {
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 += " * Gets " + className + " objects from the database by ";
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
- for (const key of keys)
481
- text += " * @param {" + key.jsType + "} " + key.name + " - The " + nameBeautifier(key.name) + " of the " + nameBeautifier(tableName) + " table.\n";
482
- text += " * @returns {Promise<" + className + "[]>} - A Promise object returning an array of " + nameBeautifier(tableName) + ".\n";
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(" ", "") + "By";
485
- for (const key of keys)
486
- text += nameBeautifier(key.name).replaceAll(" ", "") + "And";
487
- text = text.slice(0, -3);
488
- text += "(";
489
- for (const key of keys)
490
- text += key.name + ": " + key.jsType + ", ";
491
- text = text.slice(0, -2);
492
- text += "): Promise<" + className + "[]> {\n";
493
- text += "\tif (";
494
- for (const key of keys)
495
- text += key.name + " === undefined || ";
496
- text = text.slice(0, -4);
497
- text += ")\n" + "\t\tthrow \"Missing Parameters\";\n\n";
498
- let query = "SELECT * FROM " + tableSchema + "." + tableName + " WHERE ";
499
- let parameters = "";
500
- for (let i = 0; i < keys.length; i++) {
501
- query += keys[i].name + " = " + "$" + (i + 1) + "::" + keys[i].pgType + " AND ";
502
- parameters += keys[i].name + ", ";
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, nonDefaults, softDefaults, hardDefaults) {
502
+ function createAdd(tableSchema, tableName, required, optional, autoGenerated, hasForeign) {
514
503
  let text = "";
515
504
  const className = singularize(nameBeautifier(tableName)).replaceAll(" ", "");
516
- let hasForeign = false;
517
- for (const column of nonDefaults.concat(softDefaults).concat(hardDefaults).sort((a, b) => a.position - b.position)) {
518
- if (column.isForeign && column.foreignColumn && column.foreignTable && column.foreignSchema) {
519
- hasForeign = true;
520
- if ((tableSchema === column.foreignSchema) && (tableName === column.foreignTable))
521
- continue;
522
- text += "import {get" + nameBeautifier(column.foreignTable).replaceAll(" ", "") + "By" + nameBeautifier(column.foreignColumn).replaceAll(" ", "") + "} from \".";
523
- if (tableSchema !== column.foreignSchema)
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 the provided " + className + " object to the database.\n";
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
- let columns = nonDefaults.concat(softDefaults);
532
- columns.sort((a, b) => a.position - b.position);
533
- for (const column of columns)
534
- text += " * @param {" + column.jsType + "} " + column.name + " - The " + nameBeautifier(column.name) + " to be inserted into the " + nameBeautifier(tableName) + " table.\n";
535
- text += " * @returns {Promise<" + className + ">} - A Promise object returning the inserted " + nameBeautifier(tableName) + ".\n";
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 string An exception in the case of the ";
538
- for (const column of columns)
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 add" + className;
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 columns) {
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(" ", "") + "By" + nameBeautifier(column.foreignColumn).replaceAll(" ", "") + "(" + column.name + ");\n";
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(" ", "") + "By" + nameBeautifier(column.foreignColumn).replaceAll(" ", "") + "(" + column.name + ");\n";
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
- let query = "INSERT INTO " + tableSchema + "." + tableName + " (";
577
- for (const column of columns)
578
- query += column.name + ", ";
579
- query = query.slice(0, -2);
580
- query += ") VALUES (";
581
- let parameters = "";
582
- for (let i = 0; i < columns.length; i++) {
583
- query += "$" + (i + 1) + "::" + columns[i].pgType + ", ";
584
- parameters += columns[i].name + ", ";
585
- }
586
- query = query.slice(0, -2);
587
- parameters = parameters.slice(0, -2);
588
- query += ") RETURNING *;";
589
- text += queryMaker(1, "insert", query, parameters);
590
- text += "\n\n";
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 = columns.concat(hardDefaults);
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, keys) {
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
- let hasForeign = false;
608
- for (const column of columns) {
609
- if (column.isForeign && column.foreignColumn && column.foreignTable && column.foreignSchema) {
610
- hasForeign = true;
611
- if ((tableSchema === column.foreignSchema) && (tableName === column.foreignTable))
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 the " + className + " objects from the database by ";
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
- for (const key of keys)
626
- text += " * @param {" + key.jsType.replace(" | null", "") + "} " + key.name + " - The " + nameBeautifier(key.name) + " of the " + nameBeautifier(tableName) + " table to be updated.\n";
627
- for (const optional of optionals)
628
- text += " * @param {" + optional.jsType + " | undefined} " + optional.name + " - The value of the" + nameBeautifier(optional.name) + " of the " + nameBeautifier(tableName) + " table to be updated.\n";
629
- text += " * @returns {Promise<" + className + ">} - A Promise object returning the updated " + nameBeautifier(tableName) + ".\n";
630
- if (hasForeign) {
631
- text += " * @throws string An exception in the case of the ";
632
- for (const column of columns)
633
- if (column.isForeign)
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 + "By";
640
- for (const key of keys)
641
- text += nameBeautifier(key.name).replaceAll(" ", "") + "And";
642
- text = text.slice(0, -3);
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 (!keys.includes(column)) {
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 += "\tconst verify" + name + " = await get" + nameBeautifier(column.foreignTable).replaceAll(" ", "") + "By" + nameBeautifier(column.foreignColumn).replaceAll(" ", "") + "(" + column.name + ");\n";
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 += "\tlet set = \"\";\n";
670
- for (let optional of optionals) {
671
- text += "\tif (" + optional.name + " !== undefined)\n";
672
- text += "\t\tset += \"" + optional.name + " = '\" + " + optional.name + " + \"', \";\n";
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 += "\t);\n";
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, column, columns) {
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 + " objects from the database by the value of its " + nameBeautifier(column.name) + ".\n";
686
+ text += " * Deletes the " + className + " records from the database based on specified criteria.\n";
699
687
  text += " *\n";
700
- text += " * @param {" + column.jsType.replace(" | null", "") + "} " + column.name + " - The value of the" + nameBeautifier(column.name) + " of the " + nameBeautifier(tableName) + " table to be updated.\n";
701
- text += " * @returns {Promise<" + className + ">} - A Promise object returning the deleted " + nameBeautifier(tableName) + ".\n";
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 delete" + className + "By" + nameBeautifier(column.name).replaceAll(" ", "") + "(" + column.name + ": " + column.jsType.replace(" | null", "") + "): Promise<" + className + "> {\n";
704
- const query = "DELETE FROM " + tableSchema + "." + tableName + " WHERE " + column.name + " = $1::" + column.pgType + "RETURNING *;";
705
- text += queryMaker(1, "delete", query, column.name);
706
- text += "\n\n";
707
- text += "\treturn new " + className + "(\n";
708
- for (const column of columns)
709
- text += "\t\tdeleteQuery.rows[0]." + column.name + ",\n";
710
- text = text.slice(0, -2);
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 += "\t);\n";
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")