@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andymic/pigeon",
3
- "version": "1.6.0",
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": "6562b7ff32a6a9dd9f0ef108026c198db32b0aa9"
38
+ "gitHead": "565fcc64621e0c076f77933662e6f82969ef62e6"
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,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 = clientMaker(0, db);
274
- ts += "\n\n";
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 += createGetAll(table.schema, table.name, table.columns);
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
- 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 = [];
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
- nonDefaults.push(column);
325
+ required.push(column);
311
326
  else if ((column.defaultValue !== null && !column.defaultValue.includes("nextval")) || (column.isIdentity && column.identityGeneration === "BY DEFAULT"))
312
- softDefaults.push(column);
327
+ optional.push(column);
313
328
  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";
329
+ autoGenerated.push(column);
326
330
  }
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;
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
- 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) {
449
+ function createGetTypecast(columns) {
454
450
  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";
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, keys) {
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 += " * 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";
471
+ text += " * Finds and retrieves " + singularize(nameBeautifier(tableName)) + " objects from the database based on specified criteria.\n";
479
472
  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";
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(" ", "") + "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";
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, nonDefaults, softDefaults, hardDefaults) {
504
+ function createAdd(tableSchema, tableName, required, optional, autoGenerated, hasForeign) {
514
505
  let text = "";
515
506
  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
- }
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 the provided " + className + " object to the database.\n";
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
- 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";
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 string An exception in the case of the ";
538
- for (const column of columns)
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 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";
552
+ text += "export async function " + functionName + "(" + paramName + ": " + dataType.replaceAll("'", "\"") + "): Promise<" + className + "> {\n";
557
553
  if (hasForeign) {
558
- for (const column of columns) {
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(" ", "") + "By" + nameBeautifier(column.foreignColumn).replaceAll(" ", "") + "(" + column.name + ");\n";
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(" ", "") + "By" + nameBeautifier(column.foreignColumn).replaceAll(" ", "") + "(" + column.name + ");\n";
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
- 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";
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 = columns.concat(hardDefaults);
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, keys) {
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
- 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
- }
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 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";
615
+ text += " * Updates existing " + className + " objects in the database based on the specified criteria.\n";
624
616
  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
- }
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 + "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";
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 (!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";
635
+ if (column.isNullable) {
636
+ text += "\tif (updates." + column.name + " !== undefined && updates." + column.name + " !== null) {\n";
660
637
  }
661
638
  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";
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 += "\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);
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 += "\t);\n";
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, column, columns) {
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 + " objects from the database by the value of its " + nameBeautifier(column.name) + ".\n";
688
+ text += " * Deletes the " + className + " records from the database based on specified criteria.\n";
699
689
  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";
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 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);
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 += "\t);\n";
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")